diff --git a/src/__celerity_resource_map__.php b/src/__celerity_resource_map__.php index 459eccacae..c77c2c199c 100644 --- a/src/__celerity_resource_map__.php +++ b/src/__celerity_resource_map__.php @@ -36,7 +36,7 @@ celerity_register_resource_map(array( ), 'aphront-dialog-view-css' => array( - 'uri' => '/res/79613f9b/rsrc/css/aphront/dialog-view.css', + 'uri' => '/res/61a58113/rsrc/css/aphront/dialog-view.css', 'type' => 'css', 'requires' => array( @@ -45,7 +45,7 @@ celerity_register_resource_map(array( ), 'aphront-error-view-css' => array( - 'uri' => '/res/98c5fc69/rsrc/css/aphront/error-view.css', + 'uri' => '/res/e4c5e4ed/rsrc/css/aphront/error-view.css', 'type' => 'css', 'requires' => array( @@ -54,7 +54,7 @@ celerity_register_resource_map(array( ), 'aphront-form-view-css' => array( - 'uri' => '/res/38a347da/rsrc/css/aphront/form-view.css', + 'uri' => '/res/8ee16aba/rsrc/css/aphront/form-view.css', 'type' => 'css', 'requires' => array( @@ -161,16 +161,6 @@ celerity_register_resource_map(array( ), 'disk' => '/rsrc/css/application/differential/core.css', ), - 0 => - array( - 'uri' => '/res/39de799e/rsrc/js/javelin/docs/Base.js', - 'type' => 'js', - 'requires' => - array( - 0 => 'javelin-install', - ), - 'disk' => '/rsrc/js/javelin/docs/Base.js', - ), 'differential-inline-comment-editor' => array( 'uri' => '/res/5e4f0aa4/rsrc/js/application/differential/DifferentialInlineCommentEditor.js', @@ -305,7 +295,7 @@ celerity_register_resource_map(array( ), 'javelin-behavior-aphront-basic-tokenizer' => array( - 'uri' => '/res/bce3961b/rsrc/js/application/core/behavior-tokenizer.js', + 'uri' => '/res/5e183bd5/rsrc/js/application/core/behavior-tokenizer.js', 'type' => 'js', 'requires' => array( @@ -314,6 +304,7 @@ celerity_register_resource_map(array( 2 => 'javelin-tokenizer', 3 => 'javelin-typeahead-preloaded-source', 4 => 'javelin-dom', + 5 => 'javelin-stratcom', ), 'disk' => '/rsrc/js/application/core/behavior-tokenizer.js', ), @@ -344,7 +335,7 @@ celerity_register_resource_map(array( ), 'javelin-behavior-countdown-timer' => array( - 'uri' => '/res/48477cc8/rsrc/js/application/countdown/timer.js', + 'uri' => '/res/9eef8193/rsrc/js/application/countdown/timer.js', 'type' => 'js', 'requires' => array( @@ -535,6 +526,19 @@ celerity_register_resource_map(array( ), 'disk' => '/rsrc/js/application/herald/herald-rule-editor.js', ), + 'javelin-behavior-maniphest-project-create' => + array( + 'uri' => '/res/85a0eaf9/rsrc/js/application/maniphest/behavior-project-create.js', + 'type' => 'js', + 'requires' => + array( + 0 => 'javelin-behavior', + 1 => 'javelin-dom', + 2 => 'javelin-stratcom', + 3 => 'javelin-workflow', + ), + 'disk' => '/rsrc/js/application/maniphest/behavior-project-create.js', + ), 'javelin-behavior-maniphest-transaction-controls' => array( 'uri' => '/res/94a2a395/rsrc/js/application/maniphest/behavior-transaction-controls.js', @@ -586,6 +590,16 @@ celerity_register_resource_map(array( ), 'disk' => '/rsrc/js/application/owners/owners-path-editor.js', ), + 0 => + array( + 'uri' => '/res/39de799e/rsrc/js/javelin/docs/Base.js', + 'type' => 'js', + 'requires' => + array( + 0 => 'javelin-install', + ), + 'disk' => '/rsrc/js/javelin/docs/Base.js', + ), 'javelin-behavior-phabricator-keyboard-shortcuts' => array( 'uri' => '/res/5a23bcc8/rsrc/js/application/core/behavior-keyboard-shortcuts.js', @@ -1081,6 +1095,30 @@ celerity_register_resource_map(array( ), array ( 'packages' => array ( + '01052905' => + array ( + 'name' => 'core.pkg.css', + 'symbols' => + array ( + 0 => 'phabricator-core-css', + 1 => 'phabricator-core-buttons-css', + 2 => 'phabricator-standard-page-view', + 3 => 'aphront-dialog-view-css', + 4 => 'aphront-form-view-css', + 5 => 'aphront-panel-view-css', + 6 => 'aphront-side-nav-view-css', + 7 => 'aphront-table-view-css', + 8 => 'aphront-crumbs-view-css', + 9 => 'aphront-tokenizer-control-css', + 10 => 'aphront-typeahead-control-css', + 11 => 'aphront-list-filter-view-css', + 12 => 'phabricator-directory-css', + 13 => 'phabricator-remarkup-css', + 14 => 'syntax-highlighting-css', + ), + 'uri' => '/res/pkg/01052905/core.pkg.css', + 'type' => 'css', + ), '03ef179e' => array ( 'name' => 'diffusion.pkg.css', @@ -1108,7 +1146,7 @@ celerity_register_resource_map(array( 'uri' => '/res/pkg/0e6e36aa/differential.pkg.css', 'type' => 'css', ), - '33f413ef' => + '2892314d' => array ( 'name' => 'typeahead.pkg.js', 'symbols' => @@ -1121,7 +1159,7 @@ celerity_register_resource_map(array( 5 => 'javelin-tokenizer', 6 => 'javelin-behavior-aphront-basic-tokenizer', ), - 'uri' => '/res/pkg/33f413ef/typeahead.pkg.js', + 'uri' => '/res/pkg/2892314d/typeahead.pkg.js', 'type' => 'js', ), 'c8f4dac5' => @@ -1140,30 +1178,6 @@ celerity_register_resource_map(array( 'uri' => '/res/pkg/c8f4dac5/workflow.pkg.js', 'type' => 'js', ), - 'ca51195b' => - array ( - 'name' => 'core.pkg.css', - 'symbols' => - array ( - 0 => 'phabricator-core-css', - 1 => 'phabricator-core-buttons-css', - 2 => 'phabricator-standard-page-view', - 3 => 'aphront-dialog-view-css', - 4 => 'aphront-form-view-css', - 5 => 'aphront-panel-view-css', - 6 => 'aphront-side-nav-view-css', - 7 => 'aphront-table-view-css', - 8 => 'aphront-crumbs-view-css', - 9 => 'aphront-tokenizer-control-css', - 10 => 'aphront-typeahead-control-css', - 11 => 'aphront-list-filter-view-css', - 12 => 'phabricator-directory-css', - 13 => 'phabricator-remarkup-css', - 14 => 'syntax-highlighting-css', - ), - 'uri' => '/res/pkg/ca51195b/core.pkg.css', - 'type' => 'css', - ), 'da416e1c' => array ( 'name' => 'differential.pkg.js', @@ -1200,15 +1214,15 @@ celerity_register_resource_map(array( ), 'reverse' => array ( - 'aphront-crumbs-view-css' => 'ca51195b', - 'aphront-dialog-view-css' => 'ca51195b', - 'aphront-form-view-css' => 'ca51195b', - 'aphront-list-filter-view-css' => 'ca51195b', - 'aphront-panel-view-css' => 'ca51195b', - 'aphront-side-nav-view-css' => 'ca51195b', - 'aphront-table-view-css' => 'ca51195b', - 'aphront-tokenizer-control-css' => 'ca51195b', - 'aphront-typeahead-control-css' => 'ca51195b', + 'aphront-crumbs-view-css' => '01052905', + 'aphront-dialog-view-css' => '01052905', + 'aphront-form-view-css' => '01052905', + 'aphront-list-filter-view-css' => '01052905', + 'aphront-panel-view-css' => '01052905', + 'aphront-side-nav-view-css' => '01052905', + 'aphront-table-view-css' => '01052905', + 'aphront-tokenizer-control-css' => '01052905', + 'aphront-typeahead-control-css' => '01052905', 'differential-changeset-view-css' => '0e6e36aa', 'differential-core-view-css' => '0e6e36aa', 'differential-revision-add-comment-css' => '0e6e36aa', @@ -1219,7 +1233,7 @@ celerity_register_resource_map(array( 'differential-table-of-contents-css' => '0e6e36aa', 'diffusion-commit-view-css' => '03ef179e', 'javelin-behavior' => 'db95a6d0', - 'javelin-behavior-aphront-basic-tokenizer' => '33f413ef', + 'javelin-behavior-aphront-basic-tokenizer' => '2892314d', 'javelin-behavior-aphront-form-disable-on-submit' => 'c8f4dac5', 'javelin-behavior-differential-diff-radios' => 'da416e1c', 'javelin-behavior-differential-edit-inline-comments' => 'da416e1c', @@ -1235,23 +1249,23 @@ celerity_register_resource_map(array( 'javelin-mask' => 'c8f4dac5', 'javelin-request' => 'db95a6d0', 'javelin-stratcom' => 'db95a6d0', - 'javelin-tokenizer' => '33f413ef', - 'javelin-typeahead' => '33f413ef', - 'javelin-typeahead-normalizer' => '33f413ef', - 'javelin-typeahead-ondemand-source' => '33f413ef', - 'javelin-typeahead-preloaded-source' => '33f413ef', - 'javelin-typeahead-source' => '33f413ef', + 'javelin-tokenizer' => '2892314d', + 'javelin-typeahead' => '2892314d', + 'javelin-typeahead-normalizer' => '2892314d', + 'javelin-typeahead-ondemand-source' => '2892314d', + 'javelin-typeahead-preloaded-source' => '2892314d', + 'javelin-typeahead-source' => '2892314d', 'javelin-uri' => 'db95a6d0', 'javelin-util' => 'db95a6d0', 'javelin-vector' => 'db95a6d0', 'javelin-workflow' => 'c8f4dac5', - 'phabricator-core-buttons-css' => 'ca51195b', - 'phabricator-core-css' => 'ca51195b', - 'phabricator-directory-css' => 'ca51195b', + 'phabricator-core-buttons-css' => '01052905', + 'phabricator-core-css' => '01052905', + 'phabricator-directory-css' => '01052905', 'phabricator-keyboard-shortcut' => 'c8f4dac5', 'phabricator-keyboard-shortcut-manager' => 'c8f4dac5', - 'phabricator-remarkup-css' => 'ca51195b', - 'phabricator-standard-page-view' => 'ca51195b', - 'syntax-highlighting-css' => 'ca51195b', + 'phabricator-remarkup-css' => '01052905', + 'phabricator-standard-page-view' => '01052905', + 'syntax-highlighting-css' => '01052905', ), )); diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 87d3bb7295..785cf529cc 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -30,6 +30,7 @@ phutil_register_library_map(array( 'AphrontFormDividerControl' => 'view/form/control/divider', 'AphrontFormDragAndDropUploadControl' => 'view/form/control/draganddropupload', 'AphrontFormFileControl' => 'view/form/control/file', + 'AphrontFormLayoutView' => 'view/form/layout', 'AphrontFormMarkupControl' => 'view/form/control/markup', 'AphrontFormPasswordControl' => 'view/form/control/password', 'AphrontFormRecaptchaControl' => 'view/form/control/recaptcha', @@ -426,6 +427,7 @@ phutil_register_library_map(array( 'PhabricatorProjectListController' => 'applications/project/controller/list', 'PhabricatorProjectProfile' => 'applications/project/storage/profile', 'PhabricatorProjectProfileController' => 'applications/project/controller/profile', + 'PhabricatorProjectQuickCreateController' => 'applications/project/controller/quickcreate', 'PhabricatorRedirectController' => 'applications/base/controller/redirect', 'PhabricatorRemarkupRuleDifferential' => 'infrastructure/markup/remarkup/markuprule/differential', 'PhabricatorRemarkupRuleDiffusion' => 'infrastructure/markup/remarkup/markuprule/diffusion', @@ -570,6 +572,7 @@ phutil_register_library_map(array( 'AphrontFormDividerControl' => 'AphrontFormControl', 'AphrontFormDragAndDropUploadControl' => 'AphrontFormControl', 'AphrontFormFileControl' => 'AphrontFormControl', + 'AphrontFormLayoutView' => 'AphrontView', 'AphrontFormMarkupControl' => 'AphrontFormControl', 'AphrontFormPasswordControl' => 'AphrontFormControl', 'AphrontFormRecaptchaControl' => 'AphrontFormControl', @@ -881,6 +884,7 @@ phutil_register_library_map(array( 'PhabricatorProjectListController' => 'PhabricatorProjectController', 'PhabricatorProjectProfile' => 'PhabricatorProjectDAO', 'PhabricatorProjectProfileController' => 'PhabricatorProjectController', + 'PhabricatorProjectQuickCreateController' => 'PhabricatorProjectController', 'PhabricatorRedirectController' => 'PhabricatorController', 'PhabricatorRemarkupRuleDifferential' => 'PhabricatorRemarkupRuleObjectName', 'PhabricatorRemarkupRuleDiffusion' => 'PhutilRemarkupRule', diff --git a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php index 629f455f9f..314cd5cf89 100644 --- a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php +++ b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php @@ -200,6 +200,7 @@ class AphrontDefaultApplicationConfiguration 'view/(?P\d+)/$' => 'PhabricatorProjectProfileController', 'affiliation/(?P\d+)/$' => 'PhabricatorProjectAffiliationEditController', + 'quickcreate/$' => 'PhabricatorProjectQuickCreateController', ), '/r(?P[A-Z]+)(?P[a-z0-9]+)$' diff --git a/src/applications/maniphest/controller/taskedit/ManiphestTaskEditController.php b/src/applications/maniphest/controller/taskedit/ManiphestTaskEditController.php index 0051718021..55ff3a982d 100644 --- a/src/applications/maniphest/controller/taskedit/ManiphestTaskEditController.php +++ b/src/applications/maniphest/controller/taskedit/ManiphestTaskEditController.php @@ -210,6 +210,8 @@ class ManiphestTaskEditController extends ManiphestController { $header_name = 'Create New Task'; } + $project_tokenizer_id = celerity_generate_unique_node_id(); + $form = new AphrontFormView(); $form ->setUser($user) @@ -245,8 +247,22 @@ class ManiphestTaskEditController extends ManiphestController { ->setLabel('Projects') ->setName('projects') ->setValue($projects_value) + ->setID($project_tokenizer_id) + ->setCaption( + javelin_render_tag( + 'a', + array( + 'href' => '/project/quickcreate/', + 'mustcapture' => true, + 'sigil' => 'project-create', + ), + 'Create New Project')) ->setDatasource('/typeahead/common/projects/')); + Javelin::initBehavior('maniphest-project-create', array( + 'tokenizerID' => $project_tokenizer_id, + )); + if ($files) { $file_display = array(); foreach ($files as $file) { diff --git a/src/applications/maniphest/controller/taskedit/__init__.php b/src/applications/maniphest/controller/taskedit/__init__.php index 69d1be5b3f..d524917ca9 100644 --- a/src/applications/maniphest/controller/taskedit/__init__.php +++ b/src/applications/maniphest/controller/taskedit/__init__.php @@ -18,6 +18,9 @@ phutil_require_module('phabricator', 'applications/maniphest/storage/task'); phutil_require_module('phabricator', 'applications/maniphest/storage/transaction'); phutil_require_module('phabricator', 'applications/phid/constants'); phutil_require_module('phabricator', 'applications/phid/handle/data'); +phutil_require_module('phabricator', 'infrastructure/celerity/api'); +phutil_require_module('phabricator', 'infrastructure/javelin/api'); +phutil_require_module('phabricator', 'infrastructure/javelin/markup'); phutil_require_module('phabricator', 'view/form/base'); phutil_require_module('phabricator', 'view/form/control/markup'); phutil_require_module('phabricator', 'view/form/control/select'); diff --git a/src/applications/project/controller/quickcreate/PhabricatorProjectQuickCreateController.php b/src/applications/project/controller/quickcreate/PhabricatorProjectQuickCreateController.php new file mode 100644 index 0000000000..411021ac1d --- /dev/null +++ b/src/applications/project/controller/quickcreate/PhabricatorProjectQuickCreateController.php @@ -0,0 +1,95 @@ +getRequest(); + $user = $request->getUser(); + + $project = new PhabricatorProject(); + $project->setAuthorPHID($user->getPHID()); + $profile = new PhabricatorProjectProfile(); + + $e_name = true; + $errors = array(); + + if ($request->isFormPost()) { + + $project->setName($request->getStr('name')); + $profile->setBlurb($request->getStr('blurb')); + + if (!strlen($project->getName())) { + $e_name = 'Required'; + $errors[] = 'Project name is required.'; + } else { + $e_name = null; + } + + if (!$errors) { + $project->save(); + + $profile->setProjectPHID($project->getPHID()); + $profile->save(); + + return id(new AphrontAjaxResponse()) + ->setContent(array( + 'phid' => $project->getPHID(), + 'name' => $project->getName(), + )); + } + } + + $error_view = null; + if ($errors) { + $error_view = new AphrontErrorView(); + $error_view->setTitle('Form Errors'); + $error_view->setWidth(AphrontErrorView::WIDTH_DIALOG); + $error_view->setErrors($errors); + } + + $form = id(new AphrontFormLayoutView()) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel('Name') + ->setName('name') + ->setValue($project->getName()) + ->setError($e_name)) + ->appendChild( + id(new AphrontFormTextAreaControl()) + ->setLabel('Blurb') + ->setName('blurb') + ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT) + ->setValue($profile->getBlurb())); + + $dialog = id(new AphrontDialogView()) + ->setUser($user) + ->setWidth(AphrontDialogView::WIDTH_FORM) + ->setTitle('Create a New Project') + ->appendChild($error_view) + ->appendChild($form) + ->addSubmitButton('Create Project') + ->addCancelButton('/project/'); + + return id(new AphrontDialogResponse())->setDialog($dialog); + } + +} diff --git a/src/applications/project/controller/quickcreate/__init__.php b/src/applications/project/controller/quickcreate/__init__.php new file mode 100644 index 0000000000..22b2b9b1b9 --- /dev/null +++ b/src/applications/project/controller/quickcreate/__init__.php @@ -0,0 +1,23 @@ +user = $user; return $this; @@ -80,6 +84,11 @@ class AphrontDialogView extends AphrontView { return $this; } + public function setWidth($width) { + $this->width = $width; + return $this; + } + final public function render() { require_celerity_resource('aphront-dialog-view-css'); @@ -114,6 +123,16 @@ class AphrontDialogView extends AphrontView { $more = $this->class; + switch ($this->width) { + case self::WIDTH_FORM: + $more .= ' aphront-dialog-view-width-'.$this->width; + break; + case self::WIDTH_DEFAULT: + break; + default: + throw new Exception("Unknown dialog width '{$this->width}'!"); + } + $attributes = array( 'class' => 'aphront-dialog-view '.$more, 'sigil' => 'jx-dialog', diff --git a/src/view/form/base/AphrontFormView.php b/src/view/form/base/AphrontFormView.php index f4b94c37a1..9adb7f4d27 100644 --- a/src/view/form/base/AphrontFormView.php +++ b/src/view/form/base/AphrontFormView.php @@ -67,18 +67,22 @@ final class AphrontFormView extends AphrontView { Javelin::initBehavior('aphront-form-disable-on-submit'); + $layout = id(new AphrontFormLayoutView()) + ->setBackgroundShading(true) + ->setPadded(true) + ->appendChild($this->renderDataInputs()) + ->appendChild($this->renderChildren()); + return javelin_render_tag( 'form', array( 'action' => $this->action, 'method' => $this->method, - 'class' => 'aphront-form-view', 'enctype' => $this->encType, 'sigil' => $this->workflow ? 'workflow' : null, 'id' => $this->id, ), - $this->renderDataInputs(). - $this->renderChildren()); + $layout->render()); } private function renderDataInputs() { diff --git a/src/view/form/base/__init__.php b/src/view/form/base/__init__.php index 4ff1b13a67..2b61fad475 100644 --- a/src/view/form/base/__init__.php +++ b/src/view/form/base/__init__.php @@ -10,8 +10,10 @@ phutil_require_module('phabricator', 'infrastructure/celerity/api'); phutil_require_module('phabricator', 'infrastructure/javelin/api'); phutil_require_module('phabricator', 'infrastructure/javelin/markup'); phutil_require_module('phabricator', 'view/base'); +phutil_require_module('phabricator', 'view/form/layout'); phutil_require_module('phutil', 'markup'); +phutil_require_module('phutil', 'utils'); phutil_require_source('AphrontFormView.php'); diff --git a/src/view/form/error/AphrontErrorView.php b/src/view/form/error/AphrontErrorView.php index e3dfafe98a..47fa413b2c 100644 --- a/src/view/form/error/AphrontErrorView.php +++ b/src/view/form/error/AphrontErrorView.php @@ -24,6 +24,7 @@ final class AphrontErrorView extends AphrontView { const WIDTH_DEFAULT = 'default'; const WIDTH_WIDE = 'wide'; + const WIDTH_DIALOG = 'dialog'; private $title; private $errors; diff --git a/src/view/form/layout/AphrontFormLayoutView.php b/src/view/form/layout/AphrontFormLayoutView.php new file mode 100644 index 0000000000..2551d6b53b --- /dev/null +++ b/src/view/form/layout/AphrontFormLayoutView.php @@ -0,0 +1,59 @@ + tag. Useful on its own for creating forms in other forms (like + * dialogs) or forms which aren't submittable. + */ +final class AphrontFormLayoutView extends AphrontView { + + private $backgroundShading; + private $padded; + + public function setBackgroundShading($shading) { + $this->backgroundShading = $shading; + return $this; + } + + public function setPadded($padded) { + $this->padded = $padded; + return $this; + } + + public function render() { + $classes = array('aphront-form-view'); + + if ($this->backgroundShading) { + $classes[] = 'aphront-form-view-shaded'; + } + + if ($this->padded) { + $classes[] = 'aphront-form-view-padded'; + } + + $classes = implode(' ', $classes); + + return phutil_render_tag( + 'div', + array( + 'class' => $classes, + ), + $this->renderChildren()); + } +} diff --git a/src/view/form/layout/__init__.php b/src/view/form/layout/__init__.php new file mode 100644 index 0000000000..38c9fa6170 --- /dev/null +++ b/src/view/form/layout/__init__.php @@ -0,0 +1,14 @@ +