diff --git a/resources/celerity/map.php b/resources/celerity/map.php index c920068298..3bd1fd002a 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,11 +7,11 @@ */ return array( 'names' => array( - 'core.pkg.css' => '204cabae', - 'core.pkg.js' => '6972d365', + 'core.pkg.css' => '8aeacc63', + 'core.pkg.js' => '3f15fa62', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '33da0633', - 'differential.pkg.js' => 'd0cd0df6', + 'differential.pkg.js' => '4b7d8f19', 'diffusion.pkg.css' => '91c5d3a6', 'diffusion.pkg.js' => '3a9a8bfa', 'maniphest.pkg.css' => '4845691a', @@ -116,7 +116,7 @@ return array( 'rsrc/css/layout/phabricator-side-menu-view.css' => 'dd849797', 'rsrc/css/layout/phabricator-source-code-view.css' => 'cbeef983', 'rsrc/css/phui/calendar/phui-calendar-day.css' => 'd1cf6f93', - 'rsrc/css/phui/calendar/phui-calendar-list.css' => 'c1c7f338', + 'rsrc/css/phui/calendar/phui-calendar-list.css' => 'e0866209', 'rsrc/css/phui/calendar/phui-calendar-month.css' => '476be7e0', 'rsrc/css/phui/calendar/phui-calendar.css' => 'ccabe893', 'rsrc/css/phui/phui-action-list.css' => 'c5eba19d', @@ -126,7 +126,7 @@ return array( 'rsrc/css/phui/phui-box.css' => '5c8387cf', 'rsrc/css/phui/phui-button.css' => 'a64a8de6', 'rsrc/css/phui/phui-chart.css' => '6bf6f78e', - 'rsrc/css/phui/phui-crumbs-view.css' => '1a1265d4', + 'rsrc/css/phui/phui-crumbs-view.css' => '6b813619', 'rsrc/css/phui/phui-curtain-view.css' => '7148ae25', 'rsrc/css/phui/phui-document-pro.css' => '8419560b', 'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf', @@ -245,7 +245,7 @@ return array( 'rsrc/externals/javelin/lib/URI.js' => 'c989ade3', 'rsrc/externals/javelin/lib/Vector.js' => '2caa8fb8', 'rsrc/externals/javelin/lib/WebSocket.js' => 'e292eaf4', - 'rsrc/externals/javelin/lib/Workflow.js' => '28cfbdd0', + 'rsrc/externals/javelin/lib/Workflow.js' => '0eb34d1d', 'rsrc/externals/javelin/lib/__tests__/Cookie.js' => '5ed109e8', 'rsrc/externals/javelin/lib/__tests__/DOM.js' => 'c984504b', 'rsrc/externals/javelin/lib/__tests__/JSON.js' => '837a7d68', @@ -367,7 +367,7 @@ return array( 'rsrc/js/application/config/behavior-reorder-fields.js' => 'b6993408', 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '01774ab2', 'rsrc/js/application/conpherence/behavior-drag-and-drop-photo.js' => 'cf86d16a', - 'rsrc/js/application/conpherence/behavior-durable-column.js' => 'c72aa091', + 'rsrc/js/application/conpherence/behavior-durable-column.js' => 'd3506890', 'rsrc/js/application/conpherence/behavior-menu.js' => '1d45c74d', 'rsrc/js/application/conpherence/behavior-pontificate.js' => '21ba5861', 'rsrc/js/application/conpherence/behavior-quicksand-blacklist.js' => '7927a7d3', @@ -457,7 +457,7 @@ return array( 'rsrc/js/application/uiexample/gesture-example.js' => '558829c2', 'rsrc/js/application/uiexample/notification-example.js' => '8ce821c5', 'rsrc/js/core/Busy.js' => '59a7976a', - 'rsrc/js/core/DragAndDropFileUpload.js' => '81f182b5', + 'rsrc/js/core/DragAndDropFileUpload.js' => '58dea2fa', 'rsrc/js/core/DraggableList.js' => '5a13c79f', 'rsrc/js/core/FileUpload.js' => '680ea2c8', 'rsrc/js/core/Hovercard.js' => '1bd28176', @@ -467,7 +467,7 @@ return array( 'rsrc/js/core/Notification.js' => 'ccf1cbf8', 'rsrc/js/core/Prefab.js' => 'e67df814', 'rsrc/js/core/ShapedRequest.js' => '7cbe244b', - 'rsrc/js/core/TextAreaUtils.js' => '5813016a', + 'rsrc/js/core/TextAreaUtils.js' => '320810c8', 'rsrc/js/core/Title.js' => 'df5e11d2', 'rsrc/js/core/ToolTip.js' => '6323f942', 'rsrc/js/core/behavior-active-nav.js' => 'e379b58e', @@ -477,8 +477,9 @@ return array( 'rsrc/js/core/behavior-choose-control.js' => '327a00d1', 'rsrc/js/core/behavior-crop.js' => 'fa0f4fc2', 'rsrc/js/core/behavior-dark-console.js' => 'f411b6ae', - 'rsrc/js/core/behavior-device.js' => 'b5b36110', - 'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '4f6a4b4e', + 'rsrc/js/core/behavior-detect-timezone.js' => '4c193c96', + 'rsrc/js/core/behavior-device.js' => 'bb1dd507', + 'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '484a6e22', 'rsrc/js/core/behavior-error-log.js' => '6882e80a', 'rsrc/js/core/behavior-fancy-datepicker.js' => '568931f3', 'rsrc/js/core/behavior-file-tree.js' => '88236f00', @@ -489,14 +490,14 @@ 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' => 'd75709e6', + 'rsrc/js/core/behavior-keyboard-shortcuts.js' => '7835f8c9', 'rsrc/js/core/behavior-lightbox-attachments.js' => 'f8ba29d7', 'rsrc/js/core/behavior-line-linker.js' => '1499a8cb', 'rsrc/js/core/behavior-more.js' => 'a80d0378', 'rsrc/js/core/behavior-object-selector.js' => '49b73b36', 'rsrc/js/core/behavior-oncopy.js' => '2926fff2', 'rsrc/js/core/behavior-phabricator-nav.js' => '56a1ca03', - 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => '340c8eff', + 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => '116cf19b', 'rsrc/js/core/behavior-read-only-warning.js' => 'ba158207', 'rsrc/js/core/behavior-refresh-csrf.js' => 'ab2f381b', 'rsrc/js/core/behavior-remarkup-preview.js' => '4b700e9e', @@ -514,6 +515,7 @@ return array( 'rsrc/js/core/behavior-workflow.js' => '0a3f3021', 'rsrc/js/core/phtize.js' => 'd254d646', 'rsrc/js/phui/behavior-phui-dropdown-menu.js' => '54733475', + 'rsrc/js/phui/behavior-phui-file-upload.js' => 'b003d4fb', 'rsrc/js/phui/behavior-phui-object-box-tabs.js' => '2bfa2836', 'rsrc/js/phui/behavior-phui-profile-menu.js' => '12884df9', 'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8', @@ -578,7 +580,7 @@ return array( 'javelin-behavior-aphlict-status' => 'ea681761', 'javelin-behavior-aphront-basic-tokenizer' => 'b3a4b884', 'javelin-behavior-aphront-crop' => 'fa0f4fc2', - 'javelin-behavior-aphront-drag-and-drop-textarea' => '4f6a4b4e', + 'javelin-behavior-aphront-drag-and-drop-textarea' => '484a6e22', 'javelin-behavior-aphront-form-disable-on-submit' => '5c54cbf3', 'javelin-behavior-aphront-more' => 'a80d0378', 'javelin-behavior-audio-source' => '59b251eb', @@ -600,7 +602,8 @@ return array( 'javelin-behavior-dashboard-tab-panel' => 'd4eecc63', 'javelin-behavior-day-view' => '5c46cff2', 'javelin-behavior-desktop-notifications-control' => 'edd1ba66', - 'javelin-behavior-device' => 'b5b36110', + 'javelin-behavior-detect-timezone' => '4c193c96', + 'javelin-behavior-device' => 'bb1dd507', 'javelin-behavior-differential-add-reviewers-and-ccs' => 'e10f8e18', 'javelin-behavior-differential-comment-jump' => '4fdb476d', 'javelin-behavior-differential-diff-radios' => 'e1ff79b1', @@ -619,7 +622,7 @@ return array( 'javelin-behavior-diffusion-pull-lastmodified' => 'f01586dc', 'javelin-behavior-doorkeeper-tag' => 'e5822781', 'javelin-behavior-drydock-live-operation-status' => '901935ef', - 'javelin-behavior-durable-column' => 'c72aa091', + 'javelin-behavior-durable-column' => 'd3506890', 'javelin-behavior-editengine-reorder-configs' => 'd7a74243', 'javelin-behavior-editengine-reorder-fields' => 'b59e1e96', 'javelin-behavior-error-log' => '6882e80a', @@ -648,13 +651,13 @@ return array( 'javelin-behavior-phabricator-gesture' => '3ab51e2c', 'javelin-behavior-phabricator-gesture-example' => '558829c2', 'javelin-behavior-phabricator-keyboard-pager' => 'a8da01f0', - 'javelin-behavior-phabricator-keyboard-shortcuts' => 'd75709e6', + 'javelin-behavior-phabricator-keyboard-shortcuts' => '7835f8c9', 'javelin-behavior-phabricator-line-linker' => '1499a8cb', 'javelin-behavior-phabricator-nav' => '56a1ca03', 'javelin-behavior-phabricator-notification-example' => '8ce821c5', 'javelin-behavior-phabricator-object-selector' => '49b73b36', 'javelin-behavior-phabricator-oncopy' => '2926fff2', - 'javelin-behavior-phabricator-remarkup-assist' => '340c8eff', + 'javelin-behavior-phabricator-remarkup-assist' => '116cf19b', 'javelin-behavior-phabricator-reveal-content' => '60821bc7', 'javelin-behavior-phabricator-search-typeahead' => '06c32383', 'javelin-behavior-phabricator-show-older-transactions' => 'dbbf48b6', @@ -665,6 +668,7 @@ return array( 'javelin-behavior-pholio-mock-edit' => '246dc085', 'javelin-behavior-pholio-mock-view' => 'fbe497e7', 'javelin-behavior-phui-dropdown-menu' => '54733475', + 'javelin-behavior-phui-file-upload' => 'b003d4fb', 'javelin-behavior-phui-hovercards' => 'bcaccd64', 'javelin-behavior-phui-object-box-tabs' => '2bfa2836', 'javelin-behavior-phui-profile-menu' => '12884df9', @@ -743,7 +747,7 @@ return array( 'javelin-workboard-card' => 'c587b80f', 'javelin-workboard-column' => 'bae58312', 'javelin-workboard-controller' => '55baf5ed', - 'javelin-workflow' => '28cfbdd0', + 'javelin-workflow' => '0eb34d1d', 'lightbox-attachment-css' => '7acac05d', 'maniphest-batch-editor' => 'b0f0b6d5', 'maniphest-report-css' => '9b9580b7', @@ -763,7 +767,7 @@ return array( 'phabricator-core-css' => 'd0801452', 'phabricator-countdown-css' => '16c52f5c', 'phabricator-dashboard-css' => 'bc6f2127', - 'phabricator-drag-and-drop-file-upload' => '81f182b5', + 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => '5a13c79f', 'phabricator-fatal-config-template-css' => '8e6c6fcd', 'phabricator-feed-css' => 'ecd4ec57', @@ -787,7 +791,7 @@ return array( 'phabricator-slowvote-css' => 'a94b7230', 'phabricator-source-code-view-css' => 'cbeef983', 'phabricator-standard-page-view' => 'e709f6d0', - 'phabricator-textareautils' => '5813016a', + 'phabricator-textareautils' => '320810c8', 'phabricator-title' => 'df5e11d2', 'phabricator-tooltip' => '6323f942', 'phabricator-ui-example-css' => '528b19de', @@ -818,10 +822,10 @@ return array( 'phui-button-css' => 'a64a8de6', 'phui-calendar-css' => 'ccabe893', 'phui-calendar-day-css' => 'd1cf6f93', - 'phui-calendar-list-css' => 'c1c7f338', + 'phui-calendar-list-css' => 'e0866209', 'phui-calendar-month-css' => '476be7e0', 'phui-chart-css' => '6bf6f78e', - 'phui-crumbs-view-css' => '1a1265d4', + 'phui-crumbs-view-css' => '6b813619', 'phui-curtain-view-css' => '7148ae25', 'phui-document-summary-view-css' => '9ca48bdf', 'phui-document-view-css' => '715aedfb', @@ -975,10 +979,31 @@ return array( 'javelin-dom', 'javelin-router', ), + '0eb34d1d' => array( + 'javelin-stratcom', + 'javelin-request', + 'javelin-dom', + 'javelin-vector', + 'javelin-install', + 'javelin-util', + 'javelin-mask', + 'javelin-uri', + 'javelin-routable', + ), '0f764c35' => array( 'javelin-install', 'javelin-util', ), + '116cf19b' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'phabricator-phtize', + 'phabricator-textareautils', + 'javelin-workflow', + 'javelin-vector', + 'phuix-autocomplete', + ), '12884df9' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1075,17 +1100,6 @@ return array( 'phabricator-drag-and-drop-file-upload', 'phabricator-draggable-list', ), - '28cfbdd0' => array( - 'javelin-stratcom', - 'javelin-request', - 'javelin-dom', - 'javelin-vector', - 'javelin-install', - 'javelin-util', - 'javelin-mask', - 'javelin-uri', - 'javelin-routable', - ), '2926fff2' => array( 'javelin-behavior', 'javelin-dom', @@ -1116,22 +1130,17 @@ return array( '2ee659ce' => array( 'javelin-install', ), + '320810c8' => array( + 'javelin-install', + 'javelin-dom', + 'javelin-vector', + ), '327a00d1' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', 'javelin-workflow', ), - '340c8eff' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'phabricator-phtize', - 'phabricator-textareautils', - 'javelin-workflow', - 'javelin-vector', - 'phuix-autocomplete', - ), '3ab51e2c' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -1199,6 +1208,12 @@ return array( 'javelin-dom', 'javelin-workflow', ), + '484a6e22' => array( + 'javelin-behavior', + 'javelin-dom', + 'phabricator-drag-and-drop-file-upload', + 'phabricator-textareautils', + ), '49b73b36' => array( 'javelin-behavior', 'javelin-dom', @@ -1211,17 +1226,16 @@ return array( 'javelin-util', 'phabricator-shaped-request', ), + '4c193c96' => array( + 'javelin-behavior', + 'javelin-uri', + 'phabricator-notification', + ), '4e3e79a6' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', ), - '4f6a4b4e' => array( - 'javelin-behavior', - 'javelin-dom', - 'phabricator-drag-and-drop-file-upload', - 'phabricator-textareautils', - ), '4fbbc3e9' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1319,10 +1333,13 @@ return array( 'javelin-request', 'javelin-util', ), - '5813016a' => array( + '58dea2fa' => array( 'javelin-install', + 'javelin-util', + 'javelin-request', 'javelin-dom', - 'javelin-vector', + 'javelin-uri', + 'phabricator-file-upload', ), '59a7976a' => array( 'javelin-install', @@ -1475,6 +1492,13 @@ 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', @@ -1516,14 +1540,6 @@ return array( 'javelin-vector', 'javelin-stratcom', ), - '81f182b5' => array( - 'javelin-install', - 'javelin-util', - 'javelin-request', - 'javelin-dom', - 'javelin-uri', - 'phabricator-file-upload', - ), '834a1173' => array( 'javelin-behavior', 'javelin-scrollbar', @@ -1740,6 +1756,12 @@ return array( 'javelin-util', 'phabricator-busy', ), + 'b003d4fb' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'phuix-dropdown-menu', + ), 'b064af76' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1786,13 +1808,6 @@ return array( 'javelin-dom', 'phabricator-draggable-list', ), - 'b5b36110' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-vector', - 'javelin-install', - ), 'b5c256b8' => array( 'javelin-install', 'javelin-dom', @@ -1819,6 +1834,13 @@ return array( 'javelin-install', 'javelin-workboard-card', ), + 'bb1dd507' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-vector', + 'javelin-install', + ), 'bcaccd64' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -1858,16 +1880,6 @@ return array( 'c587b80f' => array( 'javelin-install', ), - 'c72aa091' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-behavior-device', - 'javelin-scrollbar', - 'javelin-quicksand', - 'phabricator-keyboard-shortcut', - 'conpherence-thread-manager', - ), 'c7ccd872' => array( 'phui-fontkit-css', ), @@ -1933,6 +1945,16 @@ return array( 'd254d646' => array( 'javelin-util', ), + 'd3506890' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-behavior-device', + 'javelin-scrollbar', + 'javelin-quicksand', + 'phabricator-keyboard-shortcut', + 'conpherence-thread-manager', + ), 'd4505101' => array( 'javelin-stratcom', 'javelin-install', @@ -1958,13 +1980,6 @@ return array( 'javelin-json', 'phabricator-prefab', ), - 'd75709e6' => array( - 'javelin-behavior', - 'javelin-workflow', - 'javelin-json', - 'javelin-dom', - 'phabricator-keyboard-shortcut', - ), 'd7a74243' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2324,6 +2339,7 @@ return array( 'javelin-behavior-scrollbar', 'javelin-behavior-durable-column', 'conpherence-thread-manager', + 'javelin-behavior-detect-timezone', ), 'darkconsole.pkg.js' => array( 'javelin-behavior-dark-console', diff --git a/resources/celerity/packages.php b/resources/celerity/packages.php index fa19f50095..b44707f407 100644 --- a/resources/celerity/packages.php +++ b/resources/celerity/packages.php @@ -81,6 +81,7 @@ return array( 'javelin-behavior-scrollbar', 'javelin-behavior-durable-column', 'conpherence-thread-manager', + 'javelin-behavior-detect-timezone', ), 'core.pkg.css' => array( 'phabricator-core-css', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 7df48b8426..6c5ac74685 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1569,6 +1569,7 @@ phutil_register_library_map(array( 'PHUICalendarDayView' => 'view/phui/calendar/PHUICalendarDayView.php', 'PHUICalendarListView' => 'view/phui/calendar/PHUICalendarListView.php', 'PHUICalendarMonthView' => 'view/phui/calendar/PHUICalendarMonthView.php', + 'PHUICalendarWeekView' => 'view/phui/calendar/PHUICalendarWeekView.php', 'PHUICalendarWidgetView' => 'view/phui/calendar/PHUICalendarWidgetView.php', 'PHUIColorPalletteExample' => 'applications/uiexample/examples/PHUIColorPalletteExample.php', 'PHUICrumbView' => 'view/phui/PHUICrumbView.php', @@ -1594,6 +1595,7 @@ phutil_register_library_map(array( 'PHUIFeedStoryExample' => 'applications/uiexample/examples/PHUIFeedStoryExample.php', 'PHUIFeedStoryView' => 'view/phui/PHUIFeedStoryView.php', 'PHUIFormDividerControl' => 'view/form/control/PHUIFormDividerControl.php', + 'PHUIFormFileControl' => 'view/form/control/PHUIFormFileControl.php', 'PHUIFormFreeformDateControl' => 'view/form/control/PHUIFormFreeformDateControl.php', 'PHUIFormIconSetControl' => 'view/form/control/PHUIFormIconSetControl.php', 'PHUIFormInsetView' => 'view/form/PHUIFormInsetView.php', @@ -2381,6 +2383,7 @@ phutil_register_library_map(array( 'PhabricatorEmailVerificationController' => 'applications/auth/controller/PhabricatorEmailVerificationController.php', 'PhabricatorEmbedFileRemarkupRule' => 'applications/files/markup/PhabricatorEmbedFileRemarkupRule.php', 'PhabricatorEmojiRemarkupRule' => 'applications/macro/markup/PhabricatorEmojiRemarkupRule.php', + 'PhabricatorEmojiTranslation' => 'infrastructure/internationalization/translation/PhabricatorEmojiTranslation.php', 'PhabricatorEmptyQueryException' => 'infrastructure/query/PhabricatorEmptyQueryException.php', 'PhabricatorEnv' => 'infrastructure/env/PhabricatorEnv.php', 'PhabricatorEnvTestCase' => 'infrastructure/env/__tests__/PhabricatorEnvTestCase.php', @@ -3329,7 +3332,6 @@ phutil_register_library_map(array( 'PhabricatorSearchNgramsDestructionEngineExtension' => 'applications/search/engineextension/PhabricatorSearchNgramsDestructionEngineExtension.php', 'PhabricatorSearchOrderController' => 'applications/search/controller/PhabricatorSearchOrderController.php', 'PhabricatorSearchOrderField' => 'applications/search/field/PhabricatorSearchOrderField.php', - 'PhabricatorSearchPreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorSearchPreferencesSettingsPanel.php', 'PhabricatorSearchRelationship' => 'applications/search/constants/PhabricatorSearchRelationship.php', 'PhabricatorSearchResultBucket' => 'applications/search/buckets/PhabricatorSearchResultBucket.php', 'PhabricatorSearchResultBucketGroup' => 'applications/search/buckets/PhabricatorSearchResultBucketGroup.php', @@ -3354,6 +3356,7 @@ phutil_register_library_map(array( 'PhabricatorSettingsMainController' => 'applications/settings/controller/PhabricatorSettingsMainController.php', 'PhabricatorSettingsMainMenuBarExtension' => 'applications/settings/extension/PhabricatorSettingsMainMenuBarExtension.php', 'PhabricatorSettingsPanel' => 'applications/settings/panel/PhabricatorSettingsPanel.php', + 'PhabricatorSettingsTimezoneController' => 'applications/settings/controller/PhabricatorSettingsTimezoneController.php', 'PhabricatorSetupCheck' => 'applications/config/check/PhabricatorSetupCheck.php', 'PhabricatorSetupCheckTestCase' => 'applications/config/check/__tests__/PhabricatorSetupCheckTestCase.php', 'PhabricatorSetupIssue' => 'applications/config/issue/PhabricatorSetupIssue.php', @@ -4133,6 +4136,7 @@ phutil_register_library_map(array( 'UserEnableConduitAPIMethod' => 'applications/people/conduit/UserEnableConduitAPIMethod.php', 'UserFindConduitAPIMethod' => 'applications/people/conduit/UserFindConduitAPIMethod.php', 'UserQueryConduitAPIMethod' => 'applications/people/conduit/UserQueryConduitAPIMethod.php', + 'UserSearchConduitAPIMethod' => 'applications/people/conduit/UserSearchConduitAPIMethod.php', 'UserWhoAmIConduitAPIMethod' => 'applications/people/conduit/UserWhoAmIConduitAPIMethod.php', ), 'function' => array( @@ -5971,6 +5975,7 @@ phutil_register_library_map(array( 'PHUICalendarDayView' => 'AphrontView', 'PHUICalendarListView' => 'AphrontTagView', 'PHUICalendarMonthView' => 'AphrontView', + 'PHUICalendarWeekView' => 'AphrontView', 'PHUICalendarWidgetView' => 'AphrontTagView', 'PHUIColorPalletteExample' => 'PhabricatorUIExample', 'PHUICrumbView' => 'AphrontView', @@ -5996,6 +6001,7 @@ phutil_register_library_map(array( 'PHUIFeedStoryExample' => 'PhabricatorUIExample', 'PHUIFeedStoryView' => 'AphrontView', 'PHUIFormDividerControl' => 'AphrontFormControl', + 'PHUIFormFileControl' => 'AphrontFormControl', 'PHUIFormFreeformDateControl' => 'AphrontFormControl', 'PHUIFormIconSetControl' => 'AphrontFormControl', 'PHUIFormInsetView' => 'AphrontView', @@ -6907,6 +6913,7 @@ phutil_register_library_map(array( 'PhabricatorEmailVerificationController' => 'PhabricatorAuthController', 'PhabricatorEmbedFileRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'PhabricatorEmojiRemarkupRule' => 'PhutilRemarkupRule', + 'PhabricatorEmojiTranslation' => 'PhutilTranslation', 'PhabricatorEmptyQueryException' => 'Exception', 'PhabricatorEnv' => 'Phobject', 'PhabricatorEnvTestCase' => 'PhabricatorTestCase', @@ -8038,7 +8045,6 @@ phutil_register_library_map(array( 'PhabricatorSearchNgramsDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension', 'PhabricatorSearchOrderController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchOrderField' => 'PhabricatorSearchField', - 'PhabricatorSearchPreferencesSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorSearchRelationship' => 'Phobject', 'PhabricatorSearchResultBucket' => 'Phobject', 'PhabricatorSearchResultBucketGroup' => 'Phobject', @@ -8063,6 +8069,7 @@ phutil_register_library_map(array( 'PhabricatorSettingsMainController' => 'PhabricatorController', 'PhabricatorSettingsMainMenuBarExtension' => 'PhabricatorMainMenuBarExtension', 'PhabricatorSettingsPanel' => 'Phobject', + 'PhabricatorSettingsTimezoneController' => 'PhabricatorController', 'PhabricatorSetupCheck' => 'Phobject', 'PhabricatorSetupCheckTestCase' => 'PhabricatorTestCase', 'PhabricatorSetupIssue' => 'Phobject', @@ -8309,6 +8316,7 @@ phutil_register_library_map(array( 'PhabricatorFlaggableInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorFulltextInterface', + 'PhabricatorConduitResultInterface', ), 'PhabricatorUserBlurbField' => 'PhabricatorUserCustomField', 'PhabricatorUserCardView' => 'AphrontTagView', @@ -9024,6 +9032,7 @@ phutil_register_library_map(array( 'UserEnableConduitAPIMethod' => 'UserConduitAPIMethod', 'UserFindConduitAPIMethod' => 'UserConduitAPIMethod', 'UserQueryConduitAPIMethod' => 'UserConduitAPIMethod', + 'UserSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'UserWhoAmIConduitAPIMethod' => 'UserConduitAPIMethod', ), )); diff --git a/src/applications/audit/editor/PhabricatorAuditEditor.php b/src/applications/audit/editor/PhabricatorAuditEditor.php index 8018e5314a..d795d4b97d 100644 --- a/src/applications/audit/editor/PhabricatorAuditEditor.php +++ b/src/applications/audit/editor/PhabricatorAuditEditor.php @@ -642,6 +642,12 @@ final class PhabricatorAuditEditor $status_resigned = PhabricatorAuditStatusConstants::RESIGNED; foreach ($object->getAudits() as $audit) { + if (!$audit->isInteresting()) { + // Don't send mail to uninteresting auditors, like packages which + // own this code but which audits have not triggered for. + continue; + } + if ($audit->getAuditStatus() != $status_resigned) { $phids[] = $audit->getAuditorPHID(); } diff --git a/src/applications/config/controller/PhabricatorConfigDatabaseController.php b/src/applications/config/controller/PhabricatorConfigDatabaseController.php index 225312a52f..0619a4a3a1 100644 --- a/src/applications/config/controller/PhabricatorConfigDatabaseController.php +++ b/src/applications/config/controller/PhabricatorConfigDatabaseController.php @@ -4,16 +4,14 @@ abstract class PhabricatorConfigDatabaseController extends PhabricatorConfigController { protected function buildSchemaQuery() { - $conf = PhabricatorEnv::newObjectFromConfig( - 'mysql.configuration-provider', - array($dao = null, 'w')); + $ref = PhabricatorDatabaseRef::getMasterDatabaseRef(); $api = id(new PhabricatorStorageManagementAPI()) - ->setUser($conf->getUser()) - ->setHost($conf->getHost()) - ->setPort($conf->getPort()) + ->setUser($ref->getUser()) + ->setHost($ref->getHost()) + ->setPort($ref->getPort()) ->setNamespace(PhabricatorLiskDAO::getDefaultStorageNamespace()) - ->setPassword($conf->getPassword()); + ->setPassword($ref->getPass()); $query = id(new PhabricatorConfigSchemaQuery()) ->setAPI($api); diff --git a/src/applications/files/controller/PhabricatorFileDropUploadController.php b/src/applications/files/controller/PhabricatorFileDropUploadController.php index 222fc799c7..21714994ff 100644 --- a/src/applications/files/controller/PhabricatorFileDropUploadController.php +++ b/src/applications/files/controller/PhabricatorFileDropUploadController.php @@ -56,7 +56,7 @@ final class PhabricatorFileDropUploadController $file_phid = $result['filePHID']; if ($file_phid) { $file = $this->loadFile($file_phid); - $result += $this->getFileDictionary($file); + $result += $file->getDragAndDropDictionary(); } return id(new AphrontAjaxResponse())->setContent($result); @@ -84,7 +84,7 @@ final class PhabricatorFileDropUploadController } else { $result = array( 'complete' => true, - ) + $this->getFileDictionary($file); + ) + $file->getDragAndDropDictionary(); } return id(new AphrontAjaxResponse())->setContent($result); @@ -99,18 +99,10 @@ final class PhabricatorFileDropUploadController 'isExplicitUpload' => true, )); - $result = $this->getFileDictionary($file); + $result = $file->getDragAndDropDictionary(); return id(new AphrontAjaxResponse())->setContent($result); } - private function getFileDictionary(PhabricatorFile $file) { - return array( - 'id' => $file->getID(), - 'phid' => $file->getPHID(), - 'uri' => $file->getBestURI(), - ); - } - private function loadFile($file_phid) { $viewer = $this->getViewer(); diff --git a/src/applications/files/controller/PhabricatorFileUploadDialogController.php b/src/applications/files/controller/PhabricatorFileUploadDialogController.php index cf13f4d694..77fff2561f 100644 --- a/src/applications/files/controller/PhabricatorFileUploadDialogController.php +++ b/src/applications/files/controller/PhabricatorFileUploadDialogController.php @@ -6,12 +6,51 @@ final class PhabricatorFileUploadDialogController public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); - return $this->newDialog() - ->setTitle(pht('Upload File')) - ->appendChild(pht( - 'To add files, drag and drop them into the comment text area.')) - ->addCancelButton('/', pht('Close')); + $e_file = true; + $errors = array(); + if ($request->isDialogFormPost()) { + $file_phids = $request->getStrList('filePHIDs'); + if ($file_phids) { + $files = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs($file_phids) + ->setRaisePolicyExceptions(true) + ->execute(); + } else { + $files = array(); + } + if ($files) { + $results = array(); + foreach ($files as $file) { + $results[] = $file->getDragAndDropDictionary(); + } + + $content = array( + 'files' => $results, + ); + + return id(new AphrontAjaxResponse())->setContent($content); + } else { + $e_file = pht('Required'); + $errors[] = pht('You must choose a file to upload.'); + } + } + + $form = id(new AphrontFormView()) + ->appendChild( + id(new PHUIFormFileControl()) + ->setName('filePHIDs') + ->setLabel(pht('Upload File')) + ->setAllowMultiple(true) + ->setError($e_file)); + + return $this->newDialog() + ->setTitle(pht('File')) + ->setErrors($errors) + ->appendForm($form) + ->addSubmitButton(pht('Upload')) + ->addCancelButton('/'); } } diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index ff2a4d27ac..d545aa1de0 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -851,6 +851,14 @@ final class PhabricatorFile extends PhabricatorFileDAO return $supported; } + public function getDragAndDropDictionary() { + return array( + 'id' => $this->getID(), + 'phid' => $this->getPHID(), + 'uri' => $this->getBestURI(), + ); + } + public function instantiateStorageEngine() { return self::buildEngine($this->getStorageEngine()); } diff --git a/src/applications/owners/storage/PhabricatorOwnersOwner.php b/src/applications/owners/storage/PhabricatorOwnersOwner.php index 07d5b59a20..ddca80b099 100644 --- a/src/applications/owners/storage/PhabricatorOwnersOwner.php +++ b/src/applications/owners/storage/PhabricatorOwnersOwner.php @@ -45,25 +45,37 @@ final class PhabricatorOwnersOwner extends PhabricatorOwnersDAO { 'packageID IN (%Ls)', $package_ids); - $all_phids = phid_group_by_type(mpull($owners, 'getUserPHID')); + $type_user = PhabricatorPeopleUserPHIDType::TYPECONST; + $type_project = PhabricatorProjectProjectPHIDType::TYPECONST; - $user_phids = idx($all_phids, - PhabricatorPeopleUserPHIDType::TYPECONST, - array()); - - if ($user_phids) { - $projects = id(new PhabricatorProjectQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withMemberPHIDs($user_phids) - ->withIsMilestone(false) - ->execute(); - $project_phids = mpull($projects, 'getPHID'); - } else { - $project_phids = array(); + $user_phids = array(); + $project_phids = array(); + foreach ($owners as $owner) { + $owner_phid = $owner->getUserPHID(); + switch (phid_get_type($owner_phid)) { + case PhabricatorPeopleUserPHIDType::TYPECONST: + $user_phids[] = $owner_phid; + break; + case PhabricatorProjectProjectPHIDType::TYPECONST: + $project_phids[] = $owner_phid; + break; + } } - $all_phids = array_fuse($user_phids) + array_fuse($project_phids); + if ($project_phids) { + $projects = id(new PhabricatorProjectQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withPHIDs($project_phids) + ->needMembers(true) + ->execute(); + foreach ($projects as $project) { + foreach ($project->getMemberPHIDs() as $member_phid) { + $user_phids[] = $member_phid; + } + } + } - return array_values($all_phids); + $user_phids = array_fuse($user_phids); + return array_values($user_phids); } } diff --git a/src/applications/people/conduit/UserSearchConduitAPIMethod.php b/src/applications/people/conduit/UserSearchConduitAPIMethod.php new file mode 100644 index 0000000000..5d007386a9 --- /dev/null +++ b/src/applications/people/conduit/UserSearchConduitAPIMethod.php @@ -0,0 +1,18 @@ +buildProjectsView($user); $badges = $this->buildBadgesView($user); + $calendar = $this->buildCalendarDayView($user); require_celerity_resource('project-view-css'); $home = id(new PHUITwoColumnView()) @@ -73,6 +74,7 @@ final class PhabricatorPeopleProfileViewController array( $projects, $badges, + $calendar, )); $nav = $this->getProfileMenu(); @@ -172,6 +174,73 @@ final class PhabricatorPeopleProfileViewController return $box; } + private function buildCalendarDayView(PhabricatorUser $user) { + $viewer = $this->getViewer(); + $class = 'PhabricatorCalendarApplication'; + + if (!PhabricatorApplication::isClassInstalledForViewer($class, $viewer)) { + return null; + } + + $midnight = PhabricatorTime::getTodayMidnightDateTime($viewer); + $week_end = clone $midnight; + $week_end = $week_end->modify('+3 days'); + + $range_start = $midnight->format('U'); + $range_end = $week_end->format('U'); + + $query = id(new PhabricatorCalendarEventQuery()) + ->setViewer($viewer) + ->withDateRange($range_start, $range_end) + ->withInvitedPHIDs(array($user->getPHID())) + ->withIsCancelled(false); + + $statuses = $query->execute(); + $phids = mpull($statuses, 'getUserPHID'); + $events = array(); + + foreach ($statuses as $status) { + $viewer_is_invited = $status->getIsUserInvited($user->getPHID()); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $status, + PhabricatorPolicyCapability::CAN_EDIT); + + $event = id(new AphrontCalendarEventView()) + ->setCanEdit($can_edit) + ->setEventID($status->getID()) + ->setEpochRange($status->getDateFrom(), $status->getDateTo()) + ->setIsAllDay($status->getIsAllDay()) + ->setIcon($status->getIcon()) + ->setViewerIsInvited($viewer_is_invited) + ->setName($status->getName()) + ->setURI($status->getURI()); + $events[] = $event; + } + + $events = msort($events, 'getEpochStart'); + $day_view = id(new PHUICalendarWeekView()) + ->setViewer($viewer) + ->setView('week') + ->setEvents($events) + ->setWeekLength(3) + ->render(); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Calendar')) + ->setHref( + urisprintf( + '/calendar/?invitedPHIDs=%s#R', + $user->getPHID())); + $box = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->appendChild($day_view) + ->setBackground(PHUIObjectBoxView::GREY); + + return $box; + } + private function buildBadgesView(PhabricatorUser $user) { $viewer = $this->getViewer(); diff --git a/src/applications/people/engine/PhabricatorPeopleProfilePanelEngine.php b/src/applications/people/engine/PhabricatorPeopleProfilePanelEngine.php index af8608900d..db362d2049 100644 --- a/src/applications/people/engine/PhabricatorPeopleProfilePanelEngine.php +++ b/src/applications/people/engine/PhabricatorPeopleProfilePanelEngine.php @@ -26,23 +26,6 @@ final class PhabricatorPeopleProfilePanelEngine ->setBuiltinKey(self::PANEL_PROFILE) ->setPanelKey(PhabricatorPeopleDetailsProfilePanel::PANELKEY); - // TODO: Convert this into a proper panel type. - $have_calendar = PhabricatorApplication::isClassInstalledForViewer( - 'PhabricatorCalendarApplication', - $viewer); - if ($have_calendar) { - $uri = urisprintf( - '/p/%s/calendar/', - $object->getUsername()); - - $panels[] = $this->newPanel() - ->setBuiltinKey('calendar') - ->setPanelKey(PhabricatorLinkProfilePanel::PANELKEY) - ->setPanelProperty('icon', 'calendar') - ->setPanelProperty('name', pht('Calendar')) - ->setPanelProperty('uri', $uri); - } - $have_maniphest = PhabricatorApplication::isClassInstalledForViewer( 'PhabricatorManiphestApplication', $viewer); diff --git a/src/applications/people/query/PhabricatorPeopleSearchEngine.php b/src/applications/people/query/PhabricatorPeopleSearchEngine.php index 8aa38651a3..ce448b0bc9 100644 --- a/src/applications/people/query/PhabricatorPeopleSearchEngine.php +++ b/src/applications/people/query/PhabricatorPeopleSearchEngine.php @@ -22,51 +22,79 @@ final class PhabricatorPeopleSearchEngine id(new PhabricatorSearchStringListField()) ->setLabel(pht('Usernames')) ->setKey('usernames') - ->setAliases(array('username')), + ->setAliases(array('username')) + ->setDescription(pht('Find users by exact username.')), id(new PhabricatorSearchTextField()) ->setLabel(pht('Name Contains')) - ->setKey('nameLike'), + ->setKey('nameLike') + ->setDescription( + pht('Find users whose usernames contain a substring.')), id(new PhabricatorSearchThreeStateField()) ->setLabel(pht('Administrators')) ->setKey('isAdmin') ->setOptions( pht('(Show All)'), pht('Show Only Administrators'), - pht('Hide Administrators')), + pht('Hide Administrators')) + ->setDescription( + pht( + 'Pass true to find only administrators, or false to omit '. + 'administrators.')), id(new PhabricatorSearchThreeStateField()) ->setLabel(pht('Disabled')) ->setKey('isDisabled') ->setOptions( pht('(Show All)'), pht('Show Only Disabled Users'), - pht('Hide Disabled Users')), + pht('Hide Disabled Users')) + ->setDescription( + pht( + 'Pass true to find only disabled users, or false to omit '. + 'disabled users.')), id(new PhabricatorSearchThreeStateField()) ->setLabel(pht('Bots')) - ->setKey('isSystemAgent') + ->setKey('isBot') + ->setAliases(array('isSystemAgent')) ->setOptions( pht('(Show All)'), pht('Show Only Bots'), - pht('Hide Bots')), + pht('Hide Bots')) + ->setDescription( + pht( + 'Pass true to find only bots, or false to omit bots.')), id(new PhabricatorSearchThreeStateField()) ->setLabel(pht('Mailing Lists')) ->setKey('isMailingList') ->setOptions( pht('(Show All)'), pht('Show Only Mailing Lists'), - pht('Hide Mailing Lists')), + pht('Hide Mailing Lists')) + ->setDescription( + pht( + 'Pass true to find only mailing lists, or false to omit '. + 'mailing lists.')), id(new PhabricatorSearchThreeStateField()) ->setLabel(pht('Needs Approval')) ->setKey('needsApproval') ->setOptions( pht('(Show All)'), pht('Show Only Unapproved Users'), - pht('Hide Unappproved Users')), + pht('Hide Unappproved Users')) + ->setDescription( + pht( + 'Pass true to find only users awaiting administrative approval, '. + 'or false to omit these users.')), id(new PhabricatorSearchDateField()) ->setKey('createdStart') - ->setLabel(pht('Joined After')), + ->setLabel(pht('Joined After')) + ->setDescription( + pht('Find user accounts created after a given time.')), id(new PhabricatorSearchDateField()) ->setKey('createdEnd') - ->setLabel(pht('Joined Before')), + ->setLabel(pht('Joined Before')) + ->setDescription( + pht('Find user accounts created before a given time.')), + ); } @@ -115,8 +143,8 @@ final class PhabricatorPeopleSearchEngine $query->withIsMailingList($map['isMailingList']); } - if ($map['isSystemAgent'] !== null) { - $query->withIsSystemAgent($map['isSystemAgent']); + if ($map['isBot'] !== null) { + $query->withIsSystemAgent($map['isBot']); } if ($map['needsApproval'] !== null) { diff --git a/src/applications/people/storage/PhabricatorUser.php b/src/applications/people/storage/PhabricatorUser.php index bdb2415733..464c14492f 100644 --- a/src/applications/people/storage/PhabricatorUser.php +++ b/src/applications/people/storage/PhabricatorUser.php @@ -16,7 +16,8 @@ final class PhabricatorUser PhabricatorSSHPublicKeyInterface, PhabricatorFlaggableInterface, PhabricatorApplicationTransactionInterface, - PhabricatorFulltextInterface { + PhabricatorFulltextInterface, + PhabricatorConduitResultInterface { const SESSION_TABLE = 'phabricator_session'; const NAMETOKEN_TABLE = 'user_nametoken'; @@ -755,6 +756,17 @@ final class PhabricatorUser return new DateTimeZone($this->getTimezoneIdentifier()); } + public function getTimeZoneOffset() { + $timezone = $this->getTimeZone(); + $now = new DateTime('@'.PhabricatorTime::getNow()); + $offset = $timezone->getOffset($now); + + // Javascript offsets are in minutes and have the opposite sign. + $offset = -(int)($offset / 60); + + return $offset; + } + public function formatShortDateTime($when, $now = null) { if ($now === null) { $now = PhabricatorTime::getNow(); @@ -1378,4 +1390,68 @@ final class PhabricatorUser return new PhabricatorUserFulltextEngine(); } + +/* -( PhabricatorConduitResultInterface )---------------------------------- */ + + + public function getFieldSpecificationsForConduit() { + return array( + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('username') + ->setType('string') + ->setDescription(pht("The user's username.")), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('realName') + ->setType('string') + ->setDescription(pht("The user's real name.")), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('roles') + ->setType('list') + ->setDescription(pht('List of acccount roles.')), + ); + } + + public function getFieldValuesForConduit() { + $roles = array(); + + if ($this->getIsDisabled()) { + $roles[] = 'disabled'; + } + + if ($this->getIsSystemAgent()) { + $roles[] = 'bot'; + } + + if ($this->getIsMailingList()) { + $roles[] = 'list'; + } + + if ($this->getIsAdmin()) { + $roles[] = 'admin'; + } + + if ($this->getIsEmailVerified()) { + $roles[] = 'verified'; + } + + if ($this->getIsApproved()) { + $roles[] = 'approved'; + } + + if ($this->isUserActivated()) { + $roles[] = 'activated'; + } + + return array( + 'username' => $this->getUsername(), + 'realName' => $this->getRealName(), + 'roles' => $roles, + ); + } + + public function getConduitSearchAttachments() { + return array(); + } + + } diff --git a/src/applications/project/typeahead/PhabricatorProjectDatasource.php b/src/applications/project/typeahead/PhabricatorProjectDatasource.php index 40d8dbb0e6..b51c55ee0a 100644 --- a/src/applications/project/typeahead/PhabricatorProjectDatasource.php +++ b/src/applications/project/typeahead/PhabricatorProjectDatasource.php @@ -82,8 +82,11 @@ final class PhabricatorProjectDatasource $closed = pht('Archived'); } - $all_strings = mpull($proj->getSlugs(), 'getSlug'); + $all_strings = array(); $all_strings[] = $proj->getDisplayName(); + foreach ($proj->getSlugs() as $project_slug) { + $all_strings[] = $project_slug->getSlug(); + } $all_strings = implode(' ', $all_strings); $proj_result = id(new PhabricatorTypeaheadResult()) diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index bc786e46a1..c95a4361e1 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -2078,7 +2078,13 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO PhabricatorRepositoryURI::BUILTIN_IDENTIFIER_ID => true, ); - $allow_http = PhabricatorEnv::getEnvConfig('diffusion.allow-http-auth'); + // If the view policy of the repository is public, support anonymous HTTP + // even if authenticated HTTP is not supported. + if ($this->getViewPolicy() === PhabricatorPolicies::POLICY_PUBLIC) { + $allow_http = true; + } else { + $allow_http = PhabricatorEnv::getEnvConfig('diffusion.allow-http-auth'); + } $base_uri = PhabricatorEnv::getURI('/'); $base_uri = new PhutilURI($base_uri); diff --git a/src/applications/repository/storage/PhabricatorRepositoryURI.php b/src/applications/repository/storage/PhabricatorRepositoryURI.php index 53b9040453..f0326f12a9 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryURI.php +++ b/src/applications/repository/storage/PhabricatorRepositoryURI.php @@ -379,14 +379,40 @@ final class PhabricatorRepositoryURI } private function getForcedPort() { - switch ($this->getBuiltinProtocol()) { - case self::BUILTIN_PROTOCOL_SSH: - return PhabricatorEnv::getEnvConfig('diffusion.ssh-port'); - case self::BUILTIN_PROTOCOL_HTTP: - case self::BUILTIN_PROTOCOL_HTTPS: - default: - return null; + $protocol = $this->getBuiltinProtocol(); + + if ($protocol == self::BUILTIN_PROTOCOL_SSH) { + return PhabricatorEnv::getEnvConfig('diffusion.ssh-port'); } + + // If Phabricator is running on a nonstandard port, use that as the defualt + // port for URIs with the same protocol. + + $is_http = ($protocol == self::BUILTIN_PROTOCOL_HTTP); + $is_https = ($protocol == self::BUILTIN_PROTOCOL_HTTPS); + + if ($is_http || $is_https) { + $uri = PhabricatorEnv::getURI('/'); + $uri = new PhutilURI($uri); + + $port = $uri->getPort(); + if (!$port) { + return null; + } + + $uri_protocol = $uri->getProtocol(); + $use_port = + ($is_http && ($uri_protocol == 'http')) || + ($is_https && ($uri_protocol == 'https')); + + if (!$use_port) { + return null; + } + + return $port; + } + + return null; } private function getForcedPath() { diff --git a/src/applications/search/controller/PhabricatorSearchController.php b/src/applications/search/controller/PhabricatorSearchController.php index d6fbf6356b..a1f6fd68b5 100644 --- a/src/applications/search/controller/PhabricatorSearchController.php +++ b/src/applications/search/controller/PhabricatorSearchController.php @@ -13,14 +13,11 @@ final class PhabricatorSearchController $viewer = $this->getViewer(); if ($request->getStr('jump') != 'no') { - $pref_jump = PhabricatorUserPreferences::PREFERENCE_SEARCHBAR_JUMP; - if ($viewer->loadPreferences($pref_jump, 1)) { - $response = PhabricatorJumpNavHandler::getJumpResponse( - $viewer, - $request->getStr('query')); - if ($response) { - return $response; - } + $response = PhabricatorJumpNavHandler::getJumpResponse( + $viewer, + $request->getStr('query')); + if ($response) { + return $response; } } diff --git a/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php b/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php index d1e8e5f3f8..7757e27d68 100644 --- a/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php +++ b/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php @@ -501,6 +501,7 @@ EOTEXT } $table = id(new AphrontTableView($rows)) + ->setNoDataString(pht('This call does not support any attachments.')) ->setHeaders( array( pht('Key'), @@ -597,6 +598,11 @@ EOTEXT $view = new PHUIRemarkupView($viewer, $remarkup); + $view->setRemarkupOptions( + array( + PHUIRemarkupView::OPTION_PRESERVE_LINEBREAKS => false, + )); + return id(new PHUIBoxView()) ->appendChild($view) ->addPadding(PHUI::PADDING_LARGE); diff --git a/src/applications/search/field/PhabricatorSearchThreeStateField.php b/src/applications/search/field/PhabricatorSearchThreeStateField.php index f7e7a80c5b..e2e899cb94 100644 --- a/src/applications/search/field/PhabricatorSearchThreeStateField.php +++ b/src/applications/search/field/PhabricatorSearchThreeStateField.php @@ -45,4 +45,8 @@ final class PhabricatorSearchThreeStateField return null; } + protected function newConduitParameterType() { + return new ConduitBoolParameterType(); + } + } diff --git a/src/applications/settings/application/PhabricatorSettingsApplication.php b/src/applications/settings/application/PhabricatorSettingsApplication.php index d0d6494c12..66bce7205f 100644 --- a/src/applications/settings/application/PhabricatorSettingsApplication.php +++ b/src/applications/settings/application/PhabricatorSettingsApplication.php @@ -32,6 +32,8 @@ final class PhabricatorSettingsApplication extends PhabricatorApplication { '(?:(?P\d+)/)?(?:panel/(?P[^/]+)/)?' => 'PhabricatorSettingsMainController', 'adjust/' => 'PhabricatorSettingsAdjustController', + 'timezone/(?P[^/]+)/' + => 'PhabricatorSettingsTimezoneController', ), ); } diff --git a/src/applications/settings/controller/PhabricatorSettingsTimezoneController.php b/src/applications/settings/controller/PhabricatorSettingsTimezoneController.php new file mode 100644 index 0000000000..a637401f1f --- /dev/null +++ b/src/applications/settings/controller/PhabricatorSettingsTimezoneController.php @@ -0,0 +1,124 @@ +getViewer(); + + $client_offset = $request->getURIData('offset'); + $client_offset = (int)$client_offset; + + $timezones = DateTimeZone::listIdentifiers(); + $now = new DateTime('@'.PhabricatorTime::getNow()); + + $options = array( + 'ignore' => pht('Ignore Conflict'), + ); + + foreach ($timezones as $identifier) { + $zone = new DateTimeZone($identifier); + $offset = -($zone->getOffset($now) / 60); + if ($offset == $client_offset) { + $options[$identifier] = $identifier; + } + } + + $settings_help = pht( + 'You can change your date and time preferences in Settings.'); + + if ($request->isFormPost()) { + $timezone = $request->getStr('timezone'); + + $pref_ignore = PhabricatorUserPreferences::PREFERENCE_IGNORE_OFFSET; + + $preferences = $viewer->loadPreferences(); + + if ($timezone == 'ignore') { + $preferences + ->setPreference($pref_ignore, $client_offset) + ->save(); + + return $this->newDialog() + ->setTitle(pht('Conflict Ignored')) + ->appendParagraph( + pht( + 'The conflict between your browser and profile timezone '. + 'settings will be ignored.')) + ->appendParagraph($settings_help) + ->addCancelButton('/', pht('Done')); + } + + if (isset($options[$timezone])) { + $preferences + ->setPreference($pref_ignore, null) + ->save(); + + $viewer + ->setTimezoneIdentifier($timezone) + ->save(); + } + } + + $server_offset = $viewer->getTimeZoneOffset(); + + if ($client_offset == $server_offset) { + return $this->newDialog() + ->setTitle(pht('Timezone Calibrated')) + ->appendParagraph( + pht( + 'Your browser timezone and profile timezone are now '. + 'in agreement (%s).', + $this->formatOffset($client_offset))) + ->appendParagraph($settings_help) + ->addCancelButton('/', pht('Done')); + } + + // If we have a guess at the timezone from the client, select it as the + // default. + $guess = $request->getStr('guess'); + if (empty($options[$guess])) { + $guess = 'ignore'; + } + + $current_zone = $viewer->getTimezoneIdentifier(); + $current_zone = phutil_tag('strong', array(), $current_zone); + + $form = id(new AphrontFormView()) + ->appendChild( + id(new AphrontFormMarkupControl()) + ->setLabel(pht('Current Setting')) + ->setValue($current_zone)) + ->appendChild( + id(new AphrontFormSelectControl()) + ->setName('timezone') + ->setLabel(pht('New Setting')) + ->setOptions($options) + ->setValue($guess)); + + return $this->newDialog() + ->setTitle(pht('Adjust Timezone')) + ->setWidth(AphrontDialogView::WIDTH_FORM) + ->appendParagraph( + pht( + 'Your browser timezone (%s) differs from your profile timezone '. + '(%s). You can ignore this conflict or adjust your profile setting '. + 'to match your client.', + $this->formatOffset($client_offset), + $this->formatOffset($server_offset))) + ->appendForm($form) + ->addCancelButton(pht('Cancel')) + ->addSubmitButton(pht('Change Timezone')); + } + + private function formatOffset($offset) { + $offset = $offset / 60; + + if ($offset >= 0) { + return pht('GMT-%d', $offset); + } else { + return pht('GMT+%d', -$offset); + } + } + +} diff --git a/src/applications/settings/panel/PhabricatorAccountSettingsPanel.php b/src/applications/settings/panel/PhabricatorAccountSettingsPanel.php index 72a225249a..274e44f15e 100644 --- a/src/applications/settings/panel/PhabricatorAccountSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorAccountSettingsPanel.php @@ -53,29 +53,7 @@ final class PhabricatorAccountSettingsPanel extends PhabricatorSettingsPanel { PhutilPerson::SEX_FEMALE => $label_her, ); - $locales = PhutilLocale::loadAllLocales(); - $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); - $is_dev = PhabricatorEnv::getEnvConfig('phabricator.developer-mode'); - - $translations = array(); - foreach ($locales as $locale) { - if ($is_serious && $locale->isSillyLocale()) { - // Omit silly locales on serious business installs. - continue; - } - if (!$is_dev && $locale->isTestLocale()) { - // Omit test locales on installs which aren't in development mode. - continue; - } - $translations[$locale->getLocaleCode()] = $locale->getLocaleName(); - } - - asort($translations); - // TODO: Implement "locale.default" and use it here. - $default = 'en_US'; - $translations = array( - '' => pht('Server Default: %s', $locales[$default]->getLocaleName()), - ) + $translations; + $translations = $this->getTranslationOptions(); $form = new AphrontFormView(); $form @@ -107,4 +85,87 @@ final class PhabricatorAccountSettingsPanel extends PhabricatorSettingsPanel { $form_box, ); } + + private function getTranslationOptions() { + $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); + $locales = PhutilLocale::loadAllLocales(); + + $group_labels = array( + 'normal' => pht('Translations'), + 'limited' => pht('Limited Translations'), + 'silly' => pht('Silly Translations'), + 'test' => pht('Developer/Test Translations'), + ); + + $groups = array_fill_keys(array_keys($group_labels), array()); + + $translations = array(); + foreach ($locales as $locale) { + $code = $locale->getLocaleCode(); + + // Get the locale's localized name if it's available. For example, + // "Deutsch" instead of "German". This helps users who do not speak the + // current language to find the correct setting. + $raw_scope = PhabricatorEnv::beginScopedLocale($code); + $name = $locale->getLocaleName(); + unset($raw_scope); + + if ($locale->isSillyLocale()) { + if ($is_serious) { + // Omit silly locales on serious business installs. + continue; + } + $groups['silly'][$code] = $name; + continue; + } + + if ($locale->isTestLocale()) { + $groups['test'][$code] = $name; + continue; + } + + $strings = PhutilTranslation::getTranslationMapForLocale($code); + $size = count($strings); + + // If a translation is English, assume it can fall back to the default + // strings and don't caveat its completeness. + $is_english = (substr($code, 0, 3) == 'en_'); + + // Arbitrarily pick some number of available strings to promote a + // translation out of the "limited" group. The major goal is just to + // keep locales with very few strings out of the main group, so users + // aren't surprised if a locale has no upstream translations available. + if ($size > 512 || $is_english) { + $type = 'normal'; + } else { + $type = 'limited'; + } + + $groups[$type][$code] = $name; + } + + // TODO: Select a default properly. + $default = 'en_US'; + + $results = array(); + foreach ($groups as $key => $group) { + $label = $group_labels[$key]; + if (!$group) { + continue; + } + + asort($group); + + if ($key == 'normal') { + $group = array( + '' => pht('Server Default: %s', $locales[$default]->getLocaleName()), + ) + $group; + } + + $results[$label] = $group; + } + + return $results; + } + } diff --git a/src/applications/settings/panel/PhabricatorDateTimeSettingsPanel.php b/src/applications/settings/panel/PhabricatorDateTimeSettingsPanel.php index 7c70089836..6628f21187 100644 --- a/src/applications/settings/panel/PhabricatorDateTimeSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorDateTimeSettingsPanel.php @@ -21,6 +21,7 @@ final class PhabricatorDateTimeSettingsPanel extends PhabricatorSettingsPanel { $pref_time = PhabricatorUserPreferences::PREFERENCE_TIME_FORMAT; $pref_date = PhabricatorUserPreferences::PREFERENCE_DATE_FORMAT; $pref_week_start = PhabricatorUserPreferences::PREFERENCE_WEEK_START_DAY; + $pref_ignore = PhabricatorUserPreferences::PREFERENCE_IGNORE_OFFSET; $preferences = $user->loadPreferences(); $errors = array(); @@ -41,7 +42,8 @@ final class PhabricatorDateTimeSettingsPanel extends PhabricatorSettingsPanel { $request->getStr($pref_date)) ->setPreference( $pref_week_start, - $request->getStr($pref_week_start)); + $request->getStr($pref_week_start)) + ->setPreference($pref_ignore, null); if (!$errors) { $preferences->save(); diff --git a/src/applications/settings/panel/PhabricatorSearchPreferencesSettingsPanel.php b/src/applications/settings/panel/PhabricatorSearchPreferencesSettingsPanel.php deleted file mode 100644 index f05a5dd9f9..0000000000 --- a/src/applications/settings/panel/PhabricatorSearchPreferencesSettingsPanel.php +++ /dev/null @@ -1,62 +0,0 @@ -getUser(); - $preferences = $user->loadPreferences(); - - $pref_jump = PhabricatorUserPreferences::PREFERENCE_SEARCHBAR_JUMP; - $pref_shortcut = PhabricatorUserPreferences::PREFERENCE_SEARCH_SHORTCUT; - - if ($request->isFormPost()) { - $preferences->setPreference($pref_jump, - $request->getBool($pref_jump)); - - $preferences->setPreference($pref_shortcut, - $request->getBool($pref_shortcut)); - - $preferences->save(); - return id(new AphrontRedirectResponse()) - ->setURI($this->getPanelURI('?saved=true')); - } - - $form = id(new AphrontFormView()) - ->setUser($user) - ->appendChild( - id(new AphrontFormCheckboxControl()) - ->addCheckbox($pref_jump, - 1, - pht('Enable jump nav functionality in all search boxes.'), - $preferences->getPreference($pref_jump, 1)) - ->addCheckbox($pref_shortcut, - 1, - pht("Press '%s' to focus the search input.", '/'), - $preferences->getPreference($pref_shortcut, 1))) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue(pht('Save'))); - - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Search Preferences')) - ->setFormSaved($request->getStr('saved') === 'true') - ->setForm($form); - - return array( - $form_box, - ); - } -} diff --git a/src/applications/settings/storage/PhabricatorUserPreferences.php b/src/applications/settings/storage/PhabricatorUserPreferences.php index d8c4982ccc..c14cc9b910 100644 --- a/src/applications/settings/storage/PhabricatorUserPreferences.php +++ b/src/applications/settings/storage/PhabricatorUserPreferences.php @@ -19,8 +19,6 @@ final class PhabricatorUserPreferences extends PhabricatorUserDAO { const PREFERENCE_VARY_SUBJECT = 'vary-subject'; const PREFERENCE_HTML_EMAILS = 'html-emails'; - const PREFERENCE_SEARCHBAR_JUMP = 'searchbar-jump'; - const PREFERENCE_SEARCH_SHORTCUT = 'search-shortcut'; const PREFERENCE_SEARCH_SCOPE = 'search-scope'; const PREFERENCE_DIFFUSION_BLAME = 'diffusion-blame'; @@ -43,6 +41,7 @@ final class PhabricatorUserPreferences extends PhabricatorUserDAO { const PREFERENCE_PROFILE_MENU_COLLAPSED = 'profile-menu.collapsed'; const PREFERENCE_FAVORITE_POLICIES = 'policy.favorites'; + const PREFERENCE_IGNORE_OFFSET = 'time.offset.ignore'; // These are in an unusual order for historic reasons. const MAILTAG_PREFERENCE_NOTIFY = 0; diff --git a/src/applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php b/src/applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php index b0abe516de..d21e2105fb 100644 --- a/src/applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php +++ b/src/applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php @@ -313,7 +313,14 @@ EOTEXT protected function renderInstructions($corpus) { $viewer = $this->getUser(); - return new PHUIRemarkupView($viewer, $corpus); + $view = new PHUIRemarkupView($viewer, $corpus); + + $view->setRemarkupOptions( + array( + PHUIRemarkupView::OPTION_PRESERVE_LINEBREAKS => false, + )); + + return $view; } } diff --git a/src/docs/user/userguide/diffusion_uris.diviner b/src/docs/user/userguide/diffusion_uris.diviner index 7377250f21..08be97b6b8 100644 --- a/src/docs/user/userguide/diffusion_uris.diviner +++ b/src/docs/user/userguide/diffusion_uris.diviner @@ -173,14 +173,16 @@ SSH clone URIs by examining configuration. **HTTP**: The `http://` clone URI will be available if these conditions are satisfied: - - `diffusion.allow-http-auth` must be enabled. + - `diffusion.allow-http-auth` must be enabled or the repository view policy + must be "Public". - The repository must be a Git or Mercurial repository. - `security.require-https` must be disabled. **HTTPS**: The `https://` clone URI will be available if these conditions are satisfied: - - `diffusion.allow-http-auth` must be enabled. + - `diffusion.allow-http-auth` must be enabled or the repository view policy + must be "Public". - The repository must be a Git or Mercurial repository. - The `phabricator.base-uri` protocol must be `https://`. diff --git a/src/infrastructure/internationalization/management/PhabricatorInternationalizationManagementExtractWorkflow.php b/src/infrastructure/internationalization/management/PhabricatorInternationalizationManagementExtractWorkflow.php index b22040de1a..7714ad81ab 100644 --- a/src/infrastructure/internationalization/management/PhabricatorInternationalizationManagementExtractWorkflow.php +++ b/src/infrastructure/internationalization/management/PhabricatorInternationalizationManagementExtractWorkflow.php @@ -35,7 +35,7 @@ final class PhabricatorInternationalizationManagementExtractWorkflow } } - $console->writeOut( + $console->writeErr( "%s\n", pht('Found %s file(s)...', phutil_count($futures))); @@ -44,14 +44,24 @@ final class PhabricatorInternationalizationManagementExtractWorkflow $bar = id(new PhutilConsoleProgressBar()) ->setTotal(count($futures)); + $messages = array(); + $futures = id(new FutureIterator($futures)) ->limit(8); foreach ($futures as $full_path => $future) { $bar->update(1); - $tree = XHPASTTree::newFromDataAndResolvedExecFuture( - Filesystem::readFile($full_path), - $future->resolve()); + try { + $tree = XHPASTTree::newFromDataAndResolvedExecFuture( + Filesystem::readFile($full_path), + $future->resolve()); + } catch (Exception $ex) { + $messages[] = pht( + 'WARNING: Failed to extract strings from file "%s": %s', + $full_path, + $ex->getMessage()); + continue; + } $root = $tree->getRootNode(); $calls = $root->selectDescendantsOfType('n_FUNCTION_CALL'); @@ -69,7 +79,11 @@ final class PhabricatorInternationalizationManagementExtractWorkflow 'line' => $string_line, ); } catch (Exception $ex) { - // TODO: Deal with this junks. + $messages[] = pht( + 'WARNING: Failed to evaluate pht() call on line %d in "%s": %s', + $call->getLineNumber(), + $full_path, + $ex->getMessage()); } } } @@ -78,6 +92,10 @@ final class PhabricatorInternationalizationManagementExtractWorkflow } $bar->done(); + foreach ($messages as $message) { + $console->writeErr("%s\n", $message); + } + ksort($results); $out = array(); diff --git a/src/infrastructure/internationalization/translation/PhabricatorEmojiTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorEmojiTranslation.php new file mode 100644 index 0000000000..40ea2e8829 --- /dev/null +++ b/src/infrastructure/internationalization/translation/PhabricatorEmojiTranslation.php @@ -0,0 +1,15 @@ + "\xF0\x9F\x92\xAC (\xF0\x9F\x8C\x8D)", + ); + } +} diff --git a/src/infrastructure/internationalization/translation/PhabricatorVeryWowEnglishTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorVeryWowEnglishTranslation.php index b269fcfed2..cbb3e3bc4a 100644 --- a/src/infrastructure/internationalization/translation/PhabricatorVeryWowEnglishTranslation.php +++ b/src/infrastructure/internationalization/translation/PhabricatorVeryWowEnglishTranslation.php @@ -33,6 +33,7 @@ final class PhabricatorVeryWowEnglishTranslation 'Prototype' => 'Chew Toy', 'Continue' => 'Bark And Run', 'Countdown to Events' => 'To the Moon!', + 'English (Very Wow)' => 'Such English', ); } } diff --git a/src/infrastructure/markup/PhabricatorMarkupEngine.php b/src/infrastructure/markup/PhabricatorMarkupEngine.php index 92e027f86d..dc3809f873 100644 --- a/src/infrastructure/markup/PhabricatorMarkupEngine.php +++ b/src/infrastructure/markup/PhabricatorMarkupEngine.php @@ -470,6 +470,7 @@ final class PhabricatorMarkupEngine extends Phobject { $engine = new PhutilRemarkupEngine(); $engine->setConfig('preserve-linebreaks', $options['preserve-linebreaks']); + $engine->setConfig('pygments.enabled', $options['pygments']); $engine->setConfig( 'uri.allowed-protocols', diff --git a/src/infrastructure/markup/PhabricatorMarkupOneOff.php b/src/infrastructure/markup/PhabricatorMarkupOneOff.php index 0bfba1722f..6bffcf7e2b 100644 --- a/src/infrastructure/markup/PhabricatorMarkupOneOff.php +++ b/src/infrastructure/markup/PhabricatorMarkupOneOff.php @@ -10,6 +10,7 @@ final class PhabricatorMarkupOneOff private $content; private $preserveLinebreaks; private $engineRuleset; + private $engine; private $disableCache; public function setEngineRuleset($engine_ruleset) { @@ -35,6 +36,15 @@ final class PhabricatorMarkupOneOff return $this->content; } + public function setEngine(PhutilMarkupEngine $engine) { + $this->engine = $engine; + return $this; + } + + public function getEngine() { + return $this->engine; + } + public function setDisableCache($disable_cache) { $this->disableCache = $disable_cache; return $this; @@ -49,6 +59,10 @@ final class PhabricatorMarkupOneOff } public function newMarkupEngine($field) { + if ($this->engine) { + return $this->engine; + } + if ($this->engineRuleset) { return PhabricatorMarkupEngine::getEngine($this->engineRuleset); } else if ($this->preserveLinebreaks) { diff --git a/src/infrastructure/markup/view/PHUIRemarkupView.php b/src/infrastructure/markup/view/PHUIRemarkupView.php index 0a07e64a87..e30c09ce7c 100644 --- a/src/infrastructure/markup/view/PHUIRemarkupView.php +++ b/src/infrastructure/markup/view/PHUIRemarkupView.php @@ -12,21 +12,19 @@ final class PHUIRemarkupView extends AphrontView { private $corpus; - private $markupType; private $contextObject; + private $options; - const DOCUMENT = 'document'; + // TODO: In the long run, rules themselves should define available options. + // For now, just define constants here so we can more easily replace things + // later once this is cleaned up. + const OPTION_PRESERVE_LINEBREAKS = 'preserve-linebreaks'; public function __construct(PhabricatorUser $viewer, $corpus) { $this->setUser($viewer); $this->corpus = $corpus; } - private function setMarkupType($type) { - $this->markupType($type); - return $this; - } - public function setContextObject($context_object) { $this->contextObject = $context_object; return $this; @@ -36,29 +34,63 @@ final class PHUIRemarkupView extends AphrontView { return $this->contextObject; } + public function setRemarkupOption($key, $value) { + $this->options[$key] = $value; + return $this; + } + + public function setRemarkupOptions(array $options) { + foreach ($options as $key => $value) { + $this->setRemarkupOption($key, $value); + } + return $this; + } + public function render() { - $viewer = $this->getUser(); + $viewer = $this->getViewer(); $corpus = $this->corpus; $context = $this->getContextObject(); + $options = $this->options; + + $oneoff = id(new PhabricatorMarkupOneOff()) + ->setContent($corpus); + + if ($options) { + $oneoff->setEngine($this->getEngine()); + } else { + $oneoff->setPreserveLinebreaks(true); + } + $content = PhabricatorMarkupEngine::renderOneObject( - id(new PhabricatorMarkupOneOff()) - ->setPreserveLinebreaks(true) - ->setContent($corpus), + $oneoff, 'default', $viewer, $context); - if ($this->markupType == self::DOCUMENT) { - return phutil_tag( - 'div', - array( - 'class' => 'phabricator-remarkup phui-document-view', - ), - $content); - } - return $content; } + private function getEngine() { + $options = $this->options; + $viewer = $this->getViewer(); + + $viewer_key = $viewer->getCacheFragment(); + + ksort($options); + $engine_key = serialize($options); + $engine_key = PhabricatorHash::digestForIndex($engine_key); + + $cache = PhabricatorCaches::getRequestCache(); + $cache_key = "remarkup.engine({$viewer}, {$engine_key})"; + + $engine = $cache->getKey($cache_key); + if (!$engine) { + $engine = PhabricatorMarkupEngine::newMarkupEngine($options); + $cache->setKey($cache_key, $engine); + } + + return $engine; + } + } diff --git a/src/view/form/AphrontFormView.php b/src/view/form/AphrontFormView.php index ecd4c1206e..3c92f72c88 100644 --- a/src/view/form/AphrontFormView.php +++ b/src/view/form/AphrontFormView.php @@ -84,9 +84,20 @@ final class AphrontFormView extends AphrontView { } public function appendRemarkupInstructions($remarkup) { - return $this->appendInstructions( - new PHUIRemarkupView($this->getViewer(), $remarkup)); + $view = $this->newInstructionsRemarkupView($remarkup); + return $this->appendInstructions($view); + } + public function newInstructionsRemarkupView($remarkup) { + $viewer = $this->getViewer(); + $view = new PHUIRemarkupView($viewer, $remarkup); + + $view->setRemarkupOptions( + array( + PHUIRemarkupView::OPTION_PRESERVE_LINEBREAKS => false, + )); + + return $view; } public function buildLayoutView() { diff --git a/src/view/form/PHUIFormLayoutView.php b/src/view/form/PHUIFormLayoutView.php index ffc0eb31d5..682c6c62cf 100644 --- a/src/view/form/PHUIFormLayoutView.php +++ b/src/view/form/PHUIFormLayoutView.php @@ -31,14 +31,11 @@ final class PHUIFormLayoutView extends AphrontView { } public function appendRemarkupInstructions($remarkup) { - if ($this->getUser() === null) { - throw new PhutilInvalidStateException('setUser'); - } + $view = id(new AphrontFormView()) + ->setViewer($this->getViewer()) + ->newInstructionsRemarkupView($remarkup); - $viewer = $this->getUser(); - $instructions = new PHUIRemarkupView($viewer, $remarkup); - - return $this->appendInstructions($instructions); + return $this->appendInstructions($view); } public function render() { diff --git a/src/view/form/control/PHUIFormFileControl.php b/src/view/form/control/PHUIFormFileControl.php new file mode 100644 index 0000000000..57f5d30bf5 --- /dev/null +++ b/src/view/form/control/PHUIFormFileControl.php @@ -0,0 +1,44 @@ +allowMultiple = $allow_multiple; + return $this; + } + + public function getAllowMultiple() { + return $this->allowMultiple; + } + + protected function renderInput() { + $file_id = $this->getID(); + + Javelin::initBehavior( + 'phui-file-upload', + array( + 'fileInputID' => $file_id, + 'inputName' => $this->getName(), + 'uploadURI' => '/file/dropupload/', + 'chunkThreshold' => PhabricatorFileStorageEngine::getChunkThreshold(), + )); + + return phutil_tag( + 'input', + array( + 'type' => 'file', + 'multiple' => $this->getAllowMultiple() ? 'multiple' : null, + 'name' => $this->getName().'.raw', + 'id' => $file_id, + 'disabled' => $this->getDisabled() ? 'disabled' : null, + )); + } + +} diff --git a/src/view/page/PhabricatorStandardPageView.php b/src/view/page/PhabricatorStandardPageView.php index 08f0c0b3c4..25779dad98 100644 --- a/src/view/page/PhabricatorStandardPageView.php +++ b/src/view/page/PhabricatorStandardPageView.php @@ -223,6 +223,30 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView } if ($user) { + if ($user->isLoggedIn()) { + $offset = $user->getTimeZoneOffset(); + + $preferences = $user->loadPreferences(); + $ignore_key = PhabricatorUserPreferences::PREFERENCE_IGNORE_OFFSET; + + $ignore = $preferences->getPreference($ignore_key); + if (!strlen($ignore)) { + $ignore = null; + } + + Javelin::initBehavior( + 'detect-timezone', + array( + 'offset' => $offset, + 'uri' => '/settings/timezone/', + 'message' => pht( + 'Your browser timezone setting differs from the timezone '. + 'setting in your profile, click to reconcile.'), + 'ignoreKey' => $ignore_key, + 'ignore' => $ignore, + )); + } + $default_img_uri = celerity_get_resource_uri( 'rsrc/image/icon/fatcow/document_black.png'); diff --git a/src/view/page/menu/PhabricatorMainMenuView.php b/src/view/page/menu/PhabricatorMainMenuView.php index e804573ff0..69ff522d5b 100644 --- a/src/view/page/menu/PhabricatorMainMenuView.php +++ b/src/view/page/menu/PhabricatorMainMenuView.php @@ -186,11 +186,6 @@ final class PhabricatorMainMenuView extends AphrontView { } $result = $search; - - $pref_shortcut = PhabricatorUserPreferences::PREFERENCE_SEARCH_SHORTCUT; - if ($viewer->loadPreferences()->getPreference($pref_shortcut, true)) { - $keyboard_config['searchID'] = $search->getID(); - } } Javelin::initBehavior('phabricator-keyboard-shortcuts', $keyboard_config); diff --git a/src/view/phui/calendar/PHUICalendarDayView.php b/src/view/phui/calendar/PHUICalendarDayView.php index d71554abe5..d7b5f9bda2 100644 --- a/src/view/phui/calendar/PHUICalendarDayView.php +++ b/src/view/phui/calendar/PHUICalendarDayView.php @@ -145,7 +145,11 @@ final class PHUICalendarDayView extends AphrontView { } $header = $this->renderDayViewHeader(); - $sidebar = $this->renderSidebar(); + $sidebar = id(new PHUICalendarWeekView()) + ->setViewer($this->getViewer()) + ->setEvents($this->events) + ->setDateTime($this->getDateTime()) + ->render(); $warnings = $this->getQueryRangeWarning(); $table_id = celerity_generate_unique_node_id(); @@ -242,91 +246,6 @@ final class PHUICalendarDayView extends AphrontView { return $errors; } - private function renderSidebar() { - $this->events = msort($this->events, 'getEpochStart'); - $week_of_boxes = $this->getWeekOfBoxes(); - $filled_boxes = array(); - - foreach ($week_of_boxes as $day_box) { - $box_start = $day_box['start']; - $box_end = id(clone $box_start)->modify('+1 day'); - - $box_start = $box_start->format('U'); - $box_end = $box_end->format('U'); - - $box_events = array(); - - foreach ($this->events as $event) { - $event_start = $event->getEpochStart(); - $event_end = $event->getEpochEnd(); - - if ($event_start < $box_end && $event_end > $box_start) { - $box_events[] = $event; - } - } - - $filled_boxes[] = $this->renderSidebarBox( - $box_events, - $day_box['title']); - } - - return $filled_boxes; - } - - private function renderSidebarBox($events, $title) { - $widget = id(new PHUICalendarWidgetView()) - ->addClass('calendar-day-view-sidebar'); - - $list = id(new PHUICalendarListView()) - ->setUser($this->getViewer()) - ->setView('day'); - - if (count($events) == 0) { - $list->showBlankState(true); - } else { - $sorted_events = msort($events, 'getEpochStart'); - foreach ($sorted_events as $event) { - $list->addEvent($event); - } - } - - $widget - ->setCalendarList($list) - ->setHeader($title); - return $widget; - } - - private function getWeekOfBoxes() { - $sidebar_day_boxes = array(); - - $display_start_day = $this->getDateTime(); - $display_end_day = id(clone $display_start_day)->modify('+6 day'); - - $box_start_time = clone $display_start_day; - - $today_time = PhabricatorTime::getTodayMidnightDateTime($this->getViewer()); - $tomorrow_time = clone $today_time; - $tomorrow_time->modify('+1 day'); - - while ($box_start_time <= $display_end_day) { - if ($box_start_time == $today_time) { - $title = pht('Today'); - } else if ($box_start_time == $tomorrow_time) { - $title = pht('Tomorrow'); - } else { - $title = $box_start_time->format('l'); - } - - $sidebar_day_boxes[] = array( - 'title' => $title, - 'start' => clone $box_start_time, - ); - - $box_start_time->modify('+1 day'); - } - return $sidebar_day_boxes; - } - private function renderDayViewHeader() { $button_bar = null; $uri = $this->getBrowseURI(); diff --git a/src/view/phui/calendar/PHUICalendarListView.php b/src/view/phui/calendar/PHUICalendarListView.php index 9f9bc86a80..fc2929adae 100644 --- a/src/view/phui/calendar/PHUICalendarListView.php +++ b/src/view/phui/calendar/PHUICalendarListView.php @@ -85,7 +85,13 @@ final class PHUICalendarListView extends AphrontTagView { } $tip = $this->getEventTooltip($event); - $tip_align = ($this->getView() == 'day') ? 'E' : 'N'; + if ($this->getView() == 'day') { + $tip_align = 'E'; + } else if ($this->getView() == 'month') { + $tip_align = 'N'; + } else { + $tip_align = 'W'; + } $content = javelin_tag( 'a', array( diff --git a/src/view/phui/calendar/PHUICalendarMonthView.php b/src/view/phui/calendar/PHUICalendarMonthView.php index d40736494e..396947cdbe 100644 --- a/src/view/phui/calendar/PHUICalendarMonthView.php +++ b/src/view/phui/calendar/PHUICalendarMonthView.php @@ -90,8 +90,9 @@ final class PHUICalendarMonthView extends AphrontView { $max_daily = 15; $counter = 0; - $list = new PHUICalendarListView(); - $list->setViewer($viewer); + $list = id(new PHUICalendarListView()) + ->setViewer($viewer) + ->setView('month'); foreach ($all_day_events as $item) { if ($counter <= $max_daily) { $list->addEvent($item); diff --git a/src/view/phui/calendar/PHUICalendarWeekView.php b/src/view/phui/calendar/PHUICalendarWeekView.php new file mode 100644 index 0000000000..6237e5b569 --- /dev/null +++ b/src/view/phui/calendar/PHUICalendarWeekView.php @@ -0,0 +1,131 @@ +events = $events; + return $this; + } + + public function setDateTime($date_time) { + $this->dateTime = $date_time; + return $this; + } + + private function getDateTime() { + if ($this->dateTime) { + return $this->dateTime; + } + return $this->getDefaultDateTime(); + } + + public function setWeekLength($week_length) { + $this->weekLength = $week_length; + return $this; + } + + public function setView($view) { + $this->view = $view; + return $this; + } + + private function getView() { + return $this->view; + } + + public function render() { + $this->events = msort($this->events, 'getEpochStart'); + $week_of_boxes = $this->getWeekOfBoxes(); + $filled_boxes = array(); + + foreach ($week_of_boxes as $day_box) { + $box_start = $day_box['start']; + $box_end = id(clone $box_start)->modify('+1 day'); + + $box_start = $box_start->format('U'); + $box_end = $box_end->format('U'); + + $box_events = array(); + + foreach ($this->events as $event) { + $event_start = $event->getEpochStart(); + $event_end = $event->getEpochEnd(); + + if ($event_start < $box_end && $event_end > $box_start) { + $box_events[] = $event; + } + } + + $filled_boxes[] = $this->renderSidebarBox( + $box_events, + $day_box['title']); + } + + return $filled_boxes; + } + + private function renderSidebarBox($events, $title) { + $widget = id(new PHUICalendarWidgetView()) + ->addClass('calendar-day-view-sidebar'); + + $list = id(new PHUICalendarListView()) + ->setUser($this->getViewer()) + ->setView($this->getView()); + + if (count($events) == 0) { + $list->showBlankState(true); + } else { + $sorted_events = msort($events, 'getEpochStart'); + foreach ($sorted_events as $event) { + $list->addEvent($event); + } + } + + $widget + ->setCalendarList($list) + ->setHeader($title); + return $widget; + } + + private function getWeekOfBoxes() { + $day_boxes = array(); + $week_length = $this->weekLength - 1; + + $display_start_day = $this->getDateTime(); + $display_end_day = id(clone $display_start_day) + ->modify('+'.$week_length.' day'); + + $box_start_time = clone $display_start_day; + + $today_time = PhabricatorTime::getTodayMidnightDateTime($this->getViewer()); + $tomorrow_time = clone $today_time; + $tomorrow_time->modify('+1 day'); + + while ($box_start_time <= $display_end_day) { + if ($box_start_time == $today_time) { + $title = pht('Today'); + } else if ($box_start_time == $tomorrow_time) { + $title = pht('Tomorrow'); + } else { + $title = $box_start_time->format('l'); + } + + $day_boxes[] = array( + 'title' => $title, + 'start' => clone $box_start_time, + ); + + $box_start_time->modify('+1 day'); + } + return $day_boxes; + } + + private function getDefaultDateTime() { + return PhabricatorTime::getTodayMidnightDateTime($this->getViewer()); + } + +} diff --git a/webroot/rsrc/css/phui/calendar/phui-calendar-list.css b/webroot/rsrc/css/phui/calendar/phui-calendar-list.css index c9bf7023d1..f34fa3cb73 100644 --- a/webroot/rsrc/css/phui/calendar/phui-calendar-list.css +++ b/webroot/rsrc/css/phui/calendar/phui-calendar-list.css @@ -2,10 +2,6 @@ * @provides phui-calendar-list-css */ -.phui-calendar-list-container { - width: 300px; -} - .device-phone .phui-calendar-list-container { width: auto; } diff --git a/webroot/rsrc/css/phui/phui-crumbs-view.css b/webroot/rsrc/css/phui/phui-crumbs-view.css index 6e99b70e19..b47418e240 100644 --- a/webroot/rsrc/css/phui/phui-crumbs-view.css +++ b/webroot/rsrc/css/phui/phui-crumbs-view.css @@ -20,7 +20,9 @@ text-decoration: none; } -.device-phone .phui-crumbs-view { +.device-tablet .phui-crumbs-view, +.device-phone .phui-crumbs-view, +.project-board-nav .phui-crumbs-view { padding-left: 8px; padding-right: 0; } diff --git a/webroot/rsrc/externals/javelin/lib/Workflow.js b/webroot/rsrc/externals/javelin/lib/Workflow.js index 50716b1c07..2ee7ef1ff0 100644 --- a/webroot/rsrc/externals/javelin/lib/Workflow.js +++ b/webroot/rsrc/externals/javelin/lib/Workflow.js @@ -25,7 +25,7 @@ JX.install('Workflow', { this.setData(data || {}); }, - events : ['error', 'finally', 'submit'], + events : ['error', 'finally', 'submit', 'start'], statics : { _stack : [], @@ -54,6 +54,9 @@ JX.install('Workflow', { } var workflow = new JX.Workflow(form.getAttribute('action'), {}); + + workflow._form = form; + workflow.setDataWithListOfPairs(pairs); workflow.setMethod(form.getAttribute('method')); workflow.listen('finally', function() { @@ -137,9 +140,14 @@ JX.install('Workflow', { data.push([button.name, button.value || true]); var active = JX.Workflow._getActiveWorkflow(); + + active._form = form; + var e = active.invoke('submit', {form: form, data: data}); if (!e.getStopped()) { - active._destroy(); + // NOTE: Don't remove the current dialog yet because additional + // handlers may still want to access the nodes. + active .setURI(form.getAttribute('action') || active.getURI()) .setDataWithListOfPairs(data) @@ -156,7 +164,41 @@ JX.install('Workflow', { _root : null, _pushed : false, _data : null, + + _form: null, + _paused: 0, + _nextCallback: null, + + getSourceForm: function() { + return this._form; + }, + + pause: function() { + this._paused++; + return this; + }, + + resume: function() { + if (!this._paused) { + JX.$E('Resuming a workflow which is not paused!'); + } + + this._paused--; + + if (!this._paused) { + var next = this._nextCallback; + this._nextCallback = null; + if (next) { + next(); + } + } + + return this; + }, + _onload : function(r) { + this._destroy(); + // It is permissible to send back a falsey redirect to force a page // reload, so we need to take this branch if the key is present. if (r && (typeof r.redirect != 'undefined')) { @@ -247,7 +289,19 @@ JX.install('Workflow', { this._root = null; } }, + start : function() { + var next = JX.bind(this, this._send); + + this.pause(); + this._nextCallback = next; + + this.invoke('start', this); + + this.resume(); + }, + + _send: function() { var uri = this.getURI(); var method = this.getMethod(); var r = new JX.Request(uri, JX.bind(this, this._onload)); @@ -291,6 +345,11 @@ JX.install('Workflow', { return this; }, + addData: function(key, value) { + this._data.push([key, value]); + return this; + }, + setDataWithListOfPairs : function(list_of_pairs) { this._data = list_of_pairs; return this; diff --git a/webroot/rsrc/js/application/conpherence/behavior-durable-column.js b/webroot/rsrc/js/application/conpherence/behavior-durable-column.js index 115daa15c9..9c3d2bba39 100644 --- a/webroot/rsrc/js/application/conpherence/behavior-durable-column.js +++ b/webroot/rsrc/js/application/conpherence/behavior-durable-column.js @@ -33,7 +33,7 @@ JX.behavior('durable-column', function(config, statics) { var columnWidth = (300 + margin); // This is the smallest window size where we'll enable the column. - var minimumViewportWidth = (768 - margin); + var minimumViewportWidth = (920 - margin); var quick = JX.$('phabricator-standard-page-body'); diff --git a/webroot/rsrc/js/core/DragAndDropFileUpload.js b/webroot/rsrc/js/core/DragAndDropFileUpload.js index 08cda15798..644f705965 100644 --- a/webroot/rsrc/js/core/DragAndDropFileUpload.js +++ b/webroot/rsrc/js/core/DragAndDropFileUpload.js @@ -155,7 +155,7 @@ JX.install('PhabricatorDragAndDropFileUpload', { var files = e.getRawEvent().dataTransfer.files; for (var ii = 0; ii < files.length; ii++) { - this._sendRequest(files[ii]); + this.sendRequest(files[ii]); } // Force depth to 0. @@ -216,7 +216,7 @@ JX.install('PhabricatorDragAndDropFileUpload', { if (!spec.name) { spec.name = 'pasted_file'; } - this._sendRequest(spec); + this.sendRequest(spec); } })); } @@ -224,7 +224,7 @@ JX.install('PhabricatorDragAndDropFileUpload', { this.setIsEnabled(true); }, - _sendRequest : function(spec) { + sendRequest : function(spec) { var file = new JX.PhabricatorFileUpload() .setRawFileObject(spec) .setName(spec.name) diff --git a/webroot/rsrc/js/core/TextAreaUtils.js b/webroot/rsrc/js/core/TextAreaUtils.js index 8f8a81067b..56f0789a46 100644 --- a/webroot/rsrc/js/core/TextAreaUtils.js +++ b/webroot/rsrc/js/core/TextAreaUtils.js @@ -62,6 +62,26 @@ JX.install('TextAreaUtils', { JX.TextAreaUtils.setSelectionRange(area, start, end); }, + + /** + * Insert a reference to a given uploaded file into a textarea. + */ + insertFileReference: function(area, file) { + var ref = '{F' + file.getID() + '}'; + + // If we're inserting immediately after a "}" (usually, another file + // reference), put some newlines before our token so that multiple file + // uploads get laid out more nicely. + var range = JX.TextAreaUtils.getSelectionRange(area); + var before = area.value.substring(0, range.start); + if (before.match(/\}$/)) { + ref = '\n\n' + ref; + } + + JX.TextAreaUtils.setSelectionText(area, ref, false); + }, + + /** * Get the document pixel positions of the beginning and end of a character * range in a textarea. diff --git a/webroot/rsrc/js/core/behavior-detect-timezone.js b/webroot/rsrc/js/core/behavior-detect-timezone.js new file mode 100644 index 0000000000..29c565b9f4 --- /dev/null +++ b/webroot/rsrc/js/core/behavior-detect-timezone.js @@ -0,0 +1,65 @@ +/** + * @provides javelin-behavior-detect-timezone + * @requires javelin-behavior + * javelin-uri + * phabricator-notification + */ + +JX.behavior('detect-timezone', function(config) { + + var offset = new Date().getTimezoneOffset(); + var ignore = config.ignore; + + if (ignore !== null) { + // If we're ignoring a client offset and it's the current offset, just + // bail. This means the user has chosen to ignore the clock difference + // between the current client setting and their server setting. + if (offset == ignore) { + return; + } + + // If we're ignoring a client offset but the current offset is different, + // wipe the offset. If you go from SF to NY, ignore the difference, return + // to SF, then travel back to NY a few months later, we want to prompt you + // again. This code will clear the ignored setting upon your return to SF. + new JX.Request('/settings/adjust/', JX.bag) + .setData({key: config.ignoreKey, value: ''}) + .send(); + + ignore = null; + } + + // If the client and server clocks are in sync, we're all set. + if (offset == config.offset) { + return; + } + + var notification = new JX.Notification() + .alterClassName('jx-notification-alert', true) + .setContent(config.message) + .setDuration(0); + + notification.listen('activate', function() { + JX.Stratcom.context().kill(); + notification.hide(); + + var uri = config.uri + offset + '/'; + + // Some browsers (notably, Chrome) expose an "Intl" API which gives us + // direct access to a timezone setting. If we are able to read this, use + // it to guess which timezone the user is in so we can prefill the + // dropdown. + try { + var guess = Intl.DateTimeFormat().resolvedOptions().timeZone; + uri = JX.$U(uri).setQueryParam('guess', guess); + } catch (error) { + // Ignore any errors here, we'll just make the user pick from the big + // list. + } + + new JX.Workflow(uri) + .start(); + }); + + notification.show(); +}); diff --git a/webroot/rsrc/js/core/behavior-device.js b/webroot/rsrc/js/core/behavior-device.js index bc6d6732d2..d74939a7e0 100644 --- a/webroot/rsrc/js/core/behavior-device.js +++ b/webroot/rsrc/js/core/behavior-device.js @@ -10,7 +10,7 @@ JX.install('Device', { statics : { _device : null, - _tabletBreakpoint: 768, + _tabletBreakpoint: 920, setTabletBreakpoint: function(width) { var self = JX.Device; diff --git a/webroot/rsrc/js/core/behavior-drag-and-drop-textarea.js b/webroot/rsrc/js/core/behavior-drag-and-drop-textarea.js index 7ee536edb4..4493ae3b2b 100644 --- a/webroot/rsrc/js/core/behavior-drag-and-drop-textarea.js +++ b/webroot/rsrc/js/core/behavior-drag-and-drop-textarea.js @@ -10,32 +10,23 @@ JX.behavior('aphront-drag-and-drop-textarea', function(config) { var target = JX.$(config.target); - function onupload(f) { - var ref = '{F' + f.getID() + '}'; - - // If we're inserting immediately after a "}" (usually, another file - // reference), put some newlines before our token so that multiple file - // uploads get laid out more nicely. - var range = JX.TextAreaUtils.getSelectionRange(target); - var before = target.value.substring(0, range.start); - if (before.match(/\}$/)) { - ref = '\n\n' + ref; - } - - JX.TextAreaUtils.setSelectionText(target, ref, false); - } - if (JX.PhabricatorDragAndDropFileUpload.isSupported()) { var drop = new JX.PhabricatorDragAndDropFileUpload(target) .setURI(config.uri) .setChunkThreshold(config.chunkThreshold); + drop.listen('didBeginDrag', function() { JX.DOM.alterClass(target, config.activatedClass, true); }); + drop.listen('didEndDrag', function() { JX.DOM.alterClass(target, config.activatedClass, false); }); - drop.listen('didUpload', onupload); + + drop.listen('didUpload', function(file) { + JX.TextAreaUtils.insertFileReference(target, file); + }); + drop.start(); } diff --git a/webroot/rsrc/js/core/behavior-keyboard-shortcuts.js b/webroot/rsrc/js/core/behavior-keyboard-shortcuts.js index 5c296420da..a2f7ca172b 100644 --- a/webroot/rsrc/js/core/behavior-keyboard-shortcuts.js +++ b/webroot/rsrc/js/core/behavior-keyboard-shortcuts.js @@ -30,14 +30,4 @@ JX.behavior('phabricator-keyboard-shortcuts', function(config) { }) .register(); - if (config.searchID) { - desc = 'Give keyboard focus to the search box.'; - new JX.KeyboardShortcut('/', desc) - .setHandler(function() { - var search = JX.$(config.searchID); - search.focus(); - search.select(); - }) - .register(); - } }); diff --git a/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js b/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js index f09070e42e..2b1646968c 100644 --- a/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js +++ b/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js @@ -194,7 +194,21 @@ JX.behavior('phabricator-remarkup-assist', function(config) { .start(); break; case 'fa-cloud-upload': - new JX.Workflow('/file/uploaddialog/').start(); + new JX.Workflow('/file/uploaddialog/') + .setHandler(function(response) { + var files = response.files; + for (var ii = 0; ii < files.length; ii++) { + var file = files[ii]; + + var upload = new JX.PhabricatorFileUpload() + .setID(file.id) + .setPHID(file.phid) + .setURI(file.uri); + + JX.TextAreaUtils.insertFileReference(area, upload); + } + }) + .start(); break; case 'fa-arrows-alt': if (edit_mode == 'fa-arrows-alt') { diff --git a/webroot/rsrc/js/phui/behavior-phui-file-upload.js b/webroot/rsrc/js/phui/behavior-phui-file-upload.js new file mode 100644 index 0000000000..39e3bddff6 --- /dev/null +++ b/webroot/rsrc/js/phui/behavior-phui-file-upload.js @@ -0,0 +1,80 @@ +/** + * @provides javelin-behavior-phui-file-upload + * @requires javelin-behavior + * javelin-stratcom + * javelin-dom + * phuix-dropdown-menu + */ + +JX.behavior('phui-file-upload', function(config) { + + function startUpload(workflow, input) { + var files = input.files; + + if (!files || !files.length) { + return; + } + + var state = { + workflow: workflow, + input: input, + waiting: 0, + phids: [] + }; + + var callback = JX.bind(null, didUpload, state); + + var dummy = input; + var uploader = new JX.PhabricatorDragAndDropFileUpload(dummy) + .setURI(config.uploadURI) + .setChunkThreshold(config.chunkThreshold); + + uploader.listen('didUpload', callback); + uploader.start(); + + workflow.pause(); + for (var ii = 0; ii < files.length; ii++) { + state.waiting++; + uploader.sendRequest(files[ii]); + } + } + + function didUpload(state, file) { + state.phids.push(file.getPHID()); + state.waiting--; + + if (state.waiting) { + return; + } + + state.workflow + .addData(config.inputName, state.phids.join(', ')) + .resume(); + } + + JX.Workflow.listen('start', function(workflow) { + var form = workflow.getSourceForm(); + if (!form) { + return; + } + + var input; + try { + input = JX.$(config.fileInputID); + } catch (ex) { + return; + } + + var local_form = JX.DOM.findAbove(input, 'form'); + if (!local_form) { + return; + } + + if (local_form !== form) { + return; + } + + startUpload(workflow, input); + }); + +});