diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 9a55c3fe61..cc3db1adb6 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,8 +7,8 @@ */ return array( 'names' => array( - 'core.pkg.css' => '4cf32aa0', - 'core.pkg.js' => '821768c9', + 'core.pkg.css' => '1a2d5480', + 'core.pkg.js' => 'cf262309', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '2de124c9', 'differential.pkg.js' => '64e69521', @@ -104,7 +104,7 @@ return array( 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', 'rsrc/css/core/core.css' => 'a76cefc9', - 'rsrc/css/core/remarkup.css' => '275e362f', + 'rsrc/css/core/remarkup.css' => '72024fc6', 'rsrc/css/core/syntax.css' => '9fd11da8', 'rsrc/css/core/z-index.css' => '57ddcaa2', 'rsrc/css/diviner/diviner-shared.css' => 'aa3656aa', @@ -487,7 +487,7 @@ return array( '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' => '461fd61b', + 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => 'ecddcbe2', 'rsrc/js/core/behavior-refresh-csrf.js' => 'ab2f381b', 'rsrc/js/core/behavior-remarkup-preview.js' => 'f7379f45', 'rsrc/js/core/behavior-reorder-applications.js' => '76b9fc3e', @@ -640,7 +640,7 @@ return array( 'javelin-behavior-phabricator-notification-example' => '8ce821c5', 'javelin-behavior-phabricator-object-selector' => '49b73b36', 'javelin-behavior-phabricator-oncopy' => '2926fff2', - 'javelin-behavior-phabricator-remarkup-assist' => '461fd61b', + 'javelin-behavior-phabricator-remarkup-assist' => 'ecddcbe2', 'javelin-behavior-phabricator-reveal-content' => '60821bc7', 'javelin-behavior-phabricator-search-typeahead' => '048330fa', 'javelin-behavior-phabricator-show-older-transactions' => 'dbbf48b6', @@ -759,7 +759,7 @@ return array( 'phabricator-object-selector-css' => '85ee8ce6', 'phabricator-phtize' => 'd254d646', 'phabricator-prefab' => '666c80c5', - 'phabricator-remarkup-css' => '275e362f', + 'phabricator-remarkup-css' => '72024fc6', 'phabricator-search-results-css' => '7dea472c', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-side-menu-view-css' => 'bec2458e', @@ -1100,15 +1100,6 @@ return array( 'javelin-behavior', 'javelin-dom', ), - '461fd61b' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'phabricator-phtize', - 'phabricator-textareautils', - 'javelin-workflow', - 'javelin-vector', - ), '469c0d9e' => array( 'javelin-behavior', 'javelin-dom', @@ -1961,6 +1952,15 @@ return array( 'phabricator-phtize', 'javelin-dom', ), + 'ecddcbe2' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'phabricator-phtize', + 'phabricator-textareautils', + 'javelin-workflow', + 'javelin-vector', + ), 'edd1ba66' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 3e763cffe8..88334bba88 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1644,6 +1644,7 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionNoEffectResponse' => 'applications/transactions/response/PhabricatorApplicationTransactionNoEffectResponse.php', 'PhabricatorApplicationTransactionPublishWorker' => 'applications/transactions/worker/PhabricatorApplicationTransactionPublishWorker.php', 'PhabricatorApplicationTransactionQuery' => 'applications/transactions/query/PhabricatorApplicationTransactionQuery.php', + 'PhabricatorApplicationTransactionRemarkupPreviewController' => 'applications/transactions/controller/PhabricatorApplicationTransactionRemarkupPreviewController.php', 'PhabricatorApplicationTransactionReplyHandler' => 'applications/transactions/replyhandler/PhabricatorApplicationTransactionReplyHandler.php', 'PhabricatorApplicationTransactionResponse' => 'applications/transactions/response/PhabricatorApplicationTransactionResponse.php', 'PhabricatorApplicationTransactionShowOlderController' => 'applications/transactions/controller/PhabricatorApplicationTransactionShowOlderController.php', @@ -5760,6 +5761,7 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionNoEffectResponse' => 'AphrontProxyResponse', 'PhabricatorApplicationTransactionPublishWorker' => 'PhabricatorWorker', 'PhabricatorApplicationTransactionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorApplicationTransactionRemarkupPreviewController' => 'PhabricatorApplicationTransactionController', 'PhabricatorApplicationTransactionReplyHandler' => 'PhabricatorMailReplyHandler', 'PhabricatorApplicationTransactionResponse' => 'AphrontProxyResponse', 'PhabricatorApplicationTransactionShowOlderController' => 'PhabricatorApplicationTransactionController', diff --git a/src/applications/transactions/application/PhabricatorTransactionsApplication.php b/src/applications/transactions/application/PhabricatorTransactionsApplication.php index 33fbfd84b9..51c88e990d 100644 --- a/src/applications/transactions/application/PhabricatorTransactionsApplication.php +++ b/src/applications/transactions/application/PhabricatorTransactionsApplication.php @@ -33,6 +33,8 @@ final class PhabricatorTransactionsApplication extends PhabricatorApplication { => 'PhabricatorApplicationTransactionShowOlderController', '(?Pold|new)/(?[^/]+)/' => 'PhabricatorApplicationTransactionValueController', + 'remarkuppreview/' + => 'PhabricatorApplicationTransactionRemarkupPreviewController', 'editengine/' => array( $this->getQueryRoutePattern() => 'PhabricatorEditEngineListController', diff --git a/src/applications/transactions/controller/PhabricatorApplicationTransactionRemarkupPreviewController.php b/src/applications/transactions/controller/PhabricatorApplicationTransactionRemarkupPreviewController.php new file mode 100644 index 0000000000..4ba8345d5c --- /dev/null +++ b/src/applications/transactions/controller/PhabricatorApplicationTransactionRemarkupPreviewController.php @@ -0,0 +1,25 @@ +getViewer(); + + $corpus = $request->getStr('corpus'); + + $remarkup = new PHUIRemarkupView($viewer, $corpus); + + $content = array( + 'content' => hsprintf('%s', $remarkup), + ); + + return id(new AphrontAjaxResponse()) + ->setContent($content); + } + +} diff --git a/src/view/form/control/PhabricatorRemarkupControl.php b/src/view/form/control/PhabricatorRemarkupControl.php index 693b2d7ea2..c035becbbe 100644 --- a/src/view/form/control/PhabricatorRemarkupControl.php +++ b/src/view/form/control/PhabricatorRemarkupControl.php @@ -42,6 +42,8 @@ final class PhabricatorRemarkupControl extends AphrontFormTextAreaControl { )); } + $root_id = celerity_generate_unique_node_id(); + Javelin::initBehavior( 'phabricator-remarkup-assist', array( @@ -56,6 +58,7 @@ final class PhabricatorRemarkupControl extends AphrontFormTextAreaControl { 'URL' => pht('URL'), ), 'disabled' => $this->getDisabled(), + 'rootID' => $root_id, )); Javelin::initBehavior('phabricator-tooltips', array()); @@ -114,11 +117,22 @@ final class PhabricatorRemarkupControl extends AphrontFormTextAreaControl { ); } + $actions['fa-eye'] = array( + 'tip' => pht('Preview'), + 'align' => 'right', + ); + + $actions[] = array( + 'spacer' => true, + 'align' => 'right', + ); + $actions['fa-life-bouy'] = array( - 'tip' => pht('Help'), - 'align' => 'right', - 'href' => PhabricatorEnv::getDoclink('Remarkup Reference'), - ); + 'tip' => pht('Help'), + 'align' => 'right', + 'href' => PhabricatorEnv::getDoclink('Remarkup Reference'), + ); + if (!$this->disableFullScreen) { $actions[] = array( @@ -230,6 +244,7 @@ final class PhabricatorRemarkupControl extends AphrontFormTextAreaControl { array( 'sigil' => 'remarkup-assist-control', 'class' => $this->getDisabled() ? 'disabled-control' : null, + 'id' => $root_id, ), array( $buttons, diff --git a/webroot/rsrc/css/core/remarkup.css b/webroot/rsrc/css/core/remarkup.css index 92c44d944d..b94a5ffc2c 100644 --- a/webroot/rsrc/css/core/remarkup.css +++ b/webroot/rsrc/css/core/remarkup.css @@ -498,6 +498,7 @@ var.remarkup-assist-textarea { height: auto; border-width: 1px 0 0 0; outline: none; + resize: none; } .phabricator-image-macro-hero { @@ -523,3 +524,35 @@ var.remarkup-assist-textarea { background-color: {$lightviolet}; padding: 0 4px; } + +.remarkup-inline-preview { + display: block; + position: relative; + background: #fff; + overflow-y: auto; + box-sizing: border-box; + width: 100%; + border: 1px solid {$sky}; + resize: vertical; + padding: 4px 6px; +} + +.remarkup-control-fullscreen-mode .remarkup-inline-preview { + resize: none; +} + +.remarkup-inline-preview * { + resize: none; +} + +.remarkup-assist-button.preview-active { + background: {$sky}; +} + +.remarkup-assist-button.preview-active .phui-icon-view { + color: #ffffff; +} + +.remarkup-assist-button.preview-active:hover .phui-icon-view { + color: {$lightsky}; +} diff --git a/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js b/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js index e0747dce47..293c44ed9b 100644 --- a/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js +++ b/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js @@ -11,9 +11,12 @@ JX.behavior('phabricator-remarkup-assist', function(config) { var pht = JX.phtize(config.pht); + var root = JX.$(config.rootID); + var area = JX.DOM.find(root, 'textarea'); var edit_mode = 'normal'; var edit_root = null; + var preview = null; function set_edit_mode(root, mode) { if (mode == edit_mode) { @@ -26,7 +29,16 @@ JX.behavior('phabricator-remarkup-assist', function(config) { JX.DOM.alterClass(edit_root, 'remarkup-control-fullscreen-mode', false); JX.DOM.alterClass(document.body, 'remarkup-fullscreen-mode', false); } - JX.DOM.find(edit_root, 'textarea').style.height = ''; + + area.style.height = ''; + + // If we're in preview mode, kick the preview back down to default + // size. + if (preview) { + JX.DOM.show(area); + resize_preview(); + JX.DOM.hide(area); + } } edit_root = root; @@ -36,10 +48,21 @@ JX.behavior('phabricator-remarkup-assist', function(config) { if (mode == 'fa-arrows-alt') { JX.DOM.alterClass(edit_root, 'remarkup-control-fullscreen-mode', true); JX.DOM.alterClass(document.body, 'remarkup-fullscreen-mode', true); + + // If we're in preview mode, expand the preview to full-size. + if (preview) { + JX.DOM.show(area); + } + resizearea(); + + if (preview) { + resize_preview(); + JX.DOM.hide(area); + } } - JX.DOM.focus(JX.DOM.find(edit_root, 'textarea')); + JX.DOM.focus(area); } function resizearea() { @@ -54,8 +77,6 @@ JX.behavior('phabricator-remarkup-assist', function(config) { // "top" and "bottom", and height "auto" renders as two lines high. Force // it to the correct height with Javascript. - var area = JX.DOM.find(edit_root, 'textarea'); - var v = JX.Vector.getViewport(); v.x = null; v.y -= 26; @@ -65,7 +86,6 @@ JX.behavior('phabricator-remarkup-assist', function(config) { JX.Stratcom.listen('resize', null, resizearea); - JX.Stratcom.listen('keydown', null, function(e) { if (e.getSpecialKey() != 'esc') { return; @@ -115,7 +135,7 @@ JX.behavior('phabricator-remarkup-assist', function(config) { return sel.join('\n' + ch); } - function assist(area, action, root) { + function assist(area, action, root, button) { // If the user has some text selected, we'll try to use that (for example, // if they have a word selected and want to bold it). Otherwise we'll insert // generic text. @@ -182,11 +202,81 @@ JX.behavior('phabricator-remarkup-assist', function(config) { set_edit_mode(root, 'fa-arrows-alt'); } break; + case 'fa-eye': + if (!preview) { + preview = JX.$N( + 'div', + { + className: 'remarkup-inline-preview' + }, + null); + + area.parentNode.insertBefore(preview, area); + JX.DOM.alterClass(button, 'preview-active', true); + resize_preview(); + JX.DOM.hide(area); + + update_preview(); + } else { + JX.DOM.show(area); + resize_preview(true); + JX.DOM.remove(preview); + preview = null; + + JX.DOM.alterClass(button, 'preview-active', false); + } + break; } } - JX.Stratcom.listen( - ['click'], + function resize_preview(restore) { + if (!preview) { + return; + } + + var src; + var dst; + + if (restore) { + src = preview; + dst = area; + } else { + src = area; + dst = preview; + } + + var d = JX.Vector.getDim(src); + d.x = null; + d.setDim(dst); + } + + function update_preview() { + var value = area.value; + + var data = { + corpus: value + }; + + var onupdate = function(r) { + if (area.value !== value) { + return; + } + + if (!preview) { + return; + } + + JX.DOM.setContent(preview, JX.$H(r.content).getFragment()); + }; + + new JX.Workflow('/transactions/remarkuppreview/', data) + .setHandler(onupdate) + .start(); + } + + JX.DOM.listen( + root, + 'click', 'remarkup-assist', function(e) { var data = e.getNodeData('remarkup-assist'); @@ -200,10 +290,7 @@ JX.behavior('phabricator-remarkup-assist', function(config) { return; } - var root = e.getNode('remarkup-assist-control'); - var area = JX.DOM.find(root, 'textarea'); - - assist(area, data.action, root); + assist(area, data.action, root, e.getNode('remarkup-assist')); }); });