diff --git a/resources/celerity/map.php b/resources/celerity/map.php index db83e57fc0..09e0fa2a0e 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,8 +9,8 @@ return array( 'names' => array( 'conpherence.pkg.css' => '0b64e988', 'conpherence.pkg.js' => '6249a1cf', - 'core.pkg.css' => '55d12594', - 'core.pkg.js' => 'e4260032', + 'core.pkg.css' => '404132bb', + 'core.pkg.js' => '28e8cda8', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => 'a4ba74b5', 'differential.pkg.js' => '634399e9', @@ -108,9 +108,9 @@ return array( 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', 'rsrc/css/core/core.css' => 'd0801452', - 'rsrc/css/core/remarkup.css' => '8606d9c6', + 'rsrc/css/core/remarkup.css' => '43e704eb', 'rsrc/css/core/syntax.css' => '769d3498', - 'rsrc/css/core/z-index.css' => 'd1270942', + 'rsrc/css/core/z-index.css' => '5e72c4e0', 'rsrc/css/diviner/diviner-shared.css' => 'aa3656aa', 'rsrc/css/font/font-aleo.css' => '8bdb2835', 'rsrc/css/font/font-awesome.css' => '2b7ebbcc', @@ -515,7 +515,7 @@ return array( 'rsrc/js/core/behavior-object-selector.js' => 'e0ec7f2f', 'rsrc/js/core/behavior-oncopy.js' => '2926fff2', 'rsrc/js/core/behavior-phabricator-nav.js' => '08675c6d', - 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => '116cf19b', + 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => '0c61d4e3', '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', @@ -677,7 +677,7 @@ return array( 'javelin-behavior-phabricator-notification-example' => '8ce821c5', 'javelin-behavior-phabricator-object-selector' => 'e0ec7f2f', 'javelin-behavior-phabricator-oncopy' => '2926fff2', - 'javelin-behavior-phabricator-remarkup-assist' => '116cf19b', + 'javelin-behavior-phabricator-remarkup-assist' => '0c61d4e3', 'javelin-behavior-phabricator-reveal-content' => '60821bc7', 'javelin-behavior-phabricator-search-typeahead' => '06c32383', 'javelin-behavior-phabricator-show-older-transactions' => '94c65b72', @@ -806,7 +806,7 @@ return array( 'phabricator-object-selector-css' => '85ee8ce6', 'phabricator-phtize' => 'd254d646', 'phabricator-prefab' => '8d40ae75', - 'phabricator-remarkup-css' => '8606d9c6', + 'phabricator-remarkup-css' => '43e704eb', 'phabricator-search-results-css' => '64ad079a', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', @@ -826,7 +826,7 @@ return array( 'phabricator-uiexample-reactor-select' => 'a155550f', 'phabricator-uiexample-reactor-sendclass' => '1def2711', 'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee', - 'phabricator-zindex-css' => 'd1270942', + 'phabricator-zindex-css' => '5e72c4e0', 'phame-css' => 'aeb61182', 'pholio-css' => 'ca89d380', 'pholio-edit-css' => '07676f51', @@ -1005,6 +1005,16 @@ return array( 'javelin-dom', 'javelin-router', ), + '0c61d4e3' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'phabricator-phtize', + 'phabricator-textareautils', + 'javelin-workflow', + 'javelin-vector', + 'phuix-autocomplete', + ), '0f764c35' => array( 'javelin-install', 'javelin-util', @@ -1015,16 +1025,6 @@ return array( 'javelin-dom', 'javelin-typeahead-normalizer', ), - '116cf19b' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'phabricator-phtize', - 'phabricator-textareautils', - 'javelin-workflow', - 'javelin-vector', - 'phuix-autocomplete', - ), '12884df9' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/src/applications/differential/parser/DifferentialCommitMessageParser.php b/src/applications/differential/parser/DifferentialCommitMessageParser.php index 4b2ba80e92..7a1625c67a 100644 --- a/src/applications/differential/parser/DifferentialCommitMessageParser.php +++ b/src/applications/differential/parser/DifferentialCommitMessageParser.php @@ -66,7 +66,7 @@ final class DifferentialCommitMessageParser extends Phobject { /** * @task config */ - public function setCommitMessageFields($fields) { + public function setCommitMessageFields(array $fields) { assert_instances_of($fields, 'DifferentialCommitMessageField'); $fields = mpull($fields, null, 'getCommitMessageFieldKey'); $this->commitMessageFields = $fields; diff --git a/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php b/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php index d6963554ce..f752858cc1 100644 --- a/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php +++ b/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php @@ -199,6 +199,7 @@ class PhabricatorApplicationTransactionCommentView extends AphrontView { $comment_box = id(new PHUIObjectBoxView()) ->setFlush(true) ->addClass('phui-comment-form-view') + ->addSigil('phui-comment-form') ->appendChild($image) ->appendChild($wedge) ->appendChild($comment); @@ -355,6 +356,7 @@ class PhabricatorApplicationTransactionCommentView extends AphrontView { ->setID($this->getCommentID()) ->addClass('phui-comment-fullwidth-control') ->addClass('phui-comment-textarea-control') + ->setCanPin(true) ->setName('comment') ->setUser($this->getUser()) ->setValue($draft_comment)) diff --git a/src/view/form/control/PhabricatorRemarkupControl.php b/src/view/form/control/PhabricatorRemarkupControl.php index 86bb416e90..7303f97401 100644 --- a/src/view/form/control/PhabricatorRemarkupControl.php +++ b/src/view/form/control/PhabricatorRemarkupControl.php @@ -1,9 +1,10 @@ disableMacro = $disable; @@ -15,6 +16,15 @@ final class PhabricatorRemarkupControl extends AphrontFormTextAreaControl { return $this; } + public function setCanPin($can_pin) { + $this->canPin = $can_pin; + return $this; + } + + public function getCanPin() { + return $this->canPin; + } + protected function renderInput() { $id = $this->getID(); if (!$id) { @@ -63,7 +73,9 @@ final class PhabricatorRemarkupControl extends AphrontFormTextAreaControl { 'data' => pht('data'), 'name' => pht('name'), 'URL' => pht('URL'), + 'key-help' => pht('Pin or unpin the comment form.'), ), + 'canPin' => $this->getCanPin(), 'disabled' => $this->getDisabled(), 'rootID' => $root_id, 'autocompleteMap' => (object)array( @@ -164,17 +176,28 @@ final class PhabricatorRemarkupControl extends AphrontFormTextAreaControl { 'href' => PhabricatorEnv::getDoclink('Remarkup Reference'), ); + $mode_actions = array(); if (!$this->disableFullScreen) { + $mode_actions['fa-arrows-alt'] = array( + 'tip' => pht('Fullscreen Mode'), + 'align' => 'right', + ); + } + + if ($this->getCanPin()) { + $mode_actions['fa-thumb-tack'] = array( + 'tip' => pht('Pin Form On Screen'), + 'align' => 'right', + ); + } + + if ($mode_actions) { $actions[] = array( 'spacer' => true, 'align' => 'right', ); - - $actions['fa-arrows-alt'] = array( - 'tip' => pht('Fullscreen Mode'), - 'align' => 'right', - ); + $actions += $mode_actions; } $buttons = array(); diff --git a/webroot/rsrc/css/core/remarkup.css b/webroot/rsrc/css/core/remarkup.css index 7cfe713a6e..7fc0ab2b68 100644 --- a/webroot/rsrc/css/core/remarkup.css +++ b/webroot/rsrc/css/core/remarkup.css @@ -728,3 +728,23 @@ var.remarkup-assist-textarea { background: {$sh-bluebackground}; color: #000; } + +.phui-box.phui-object-box.phui-comment-form-view.remarkup-assist-pinned { + position: fixed; + background-color: #ffffff; + border-top: 1px solid {$lightblueborder}; + box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); + + width: 100%; + bottom: 0; + left: 0; + right: 0; + margin: 0; + + overflow: auto; + max-height: 40vh; +} + +.remarkup-assist-pinned-spacer { + position: relative; +} diff --git a/webroot/rsrc/css/core/z-index.css b/webroot/rsrc/css/core/z-index.css index 3bfff11070..a5b95d568b 100644 --- a/webroot/rsrc/css/core/z-index.css +++ b/webroot/rsrc/css/core/z-index.css @@ -106,6 +106,10 @@ div.phui-calendar-day-event { z-index: 8; } +.remarkup-assist-pinned { + z-index: 8; +} + .device-desktop .phabricator-notification-menu { z-index: 9; } diff --git a/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js b/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js index 2b1646968c..6e89a18d88 100644 --- a/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js +++ b/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js @@ -18,6 +18,16 @@ JX.behavior('phabricator-remarkup-assist', function(config) { var edit_mode = 'normal'; var edit_root = null; var preview = null; + var pinned = false; + + // When we pin the comment area to the bottom of the window, we need to put + // an extra spacer element at the bottom of the document so that it is + // possible to scroll down far enough to see content at the end. Otherwise, + // the last part of the document will be hidden behind the comment area when + // the document is fully scrolled. + var pinned_spacer = JX.$N( + 'div', + {className: 'remarkup-assist-pinned-spacer'}); function set_edit_mode(root, mode) { if (mode == edit_mode) { @@ -66,7 +76,42 @@ JX.behavior('phabricator-remarkup-assist', function(config) { JX.DOM.focus(area); } + function set_pinned_mode(root, mode) { + if (mode === pinned) { + return; + } + + pinned = mode; + + var container = get_pinned_container(root); + JX.DOM.alterClass(container, 'remarkup-assist-pinned', pinned); + + if (pinned) { + JX.DOM.appendContent(document.body, pinned_spacer); + } else { + JX.DOM.remove(pinned_spacer); + } + + resizearea(); + + JX.DOM.focus(area); + } + + function get_pinned_container(root) { + return JX.DOM.findAbove(root, 'div', 'phui-comment-form'); + } + function resizearea() { + // If we're in the pinned comment mode, resize the pinned spacer to be the + // same size as the pinned form. This allows users to scroll to the bottom + // of the document by creating extra footer space to scroll through. + if (pinned) { + var container = get_pinned_container(root); + var d = JX.Vector.getDim(container); + d.x = null; + d.setDim(pinned_spacer); + } + if (!edit_root) { return; } @@ -98,6 +143,7 @@ JX.behavior('phabricator-remarkup-assist', function(config) { e.kill(); set_edit_mode(edit_root, 'normal'); + set_pinned_mode(root, false); }); function update(area, l, m, r) { @@ -211,6 +257,7 @@ JX.behavior('phabricator-remarkup-assist', function(config) { .start(); break; case 'fa-arrows-alt': + set_pinned_mode(root, false); if (edit_mode == 'fa-arrows-alt') { set_edit_mode(root, 'normal'); } else { @@ -241,6 +288,14 @@ JX.behavior('phabricator-remarkup-assist', function(config) { JX.DOM.alterClass(button, 'preview-active', false); } break; + case 'fa-thumb-tack': + // If we're pinning, kick us out of fullscreen mode first. + set_edit_mode(edit_root, 'normal'); + + // Now pin or unpin the area. + set_pinned_mode(root, !pinned); + break; + } } @@ -317,4 +372,12 @@ JX.behavior('phabricator-remarkup-assist', function(config) { autocomplete.start(); + if (config.canPin) { + new JX.KeyboardShortcut('z', pht('key-help')) + .setHandler(function() { + set_pinned_mode(root, !pinned); + }) + .register(); + } + });