1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-18 19:40:55 +01:00

Put inline previews in remarkup textareas

Summary:
Ref T3967. This gives us a reasonable baseline for doing remarkup previews inline in all contexts, and works in weird/constrained context including:

  - inline comments;
  - conpherence; and
  - custom fields.

It would be nicer to go beyond this in contexts like Phame posts, but this is a start, at least.

Test Plan:
{F1040877}

{F1040878}

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T3967

Differential Revision: https://secure.phabricator.com/D14855
This commit is contained in:
epriestley 2015-12-22 11:32:07 -08:00
parent 551732b962
commit f5ff10fe28
7 changed files with 195 additions and 31 deletions

View file

@ -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',

View file

@ -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',

View file

@ -33,6 +33,8 @@ final class PhabricatorTransactionsApplication extends PhabricatorApplication {
=> 'PhabricatorApplicationTransactionShowOlderController',
'(?P<value>old|new)/(?<phid>[^/]+)/'
=> 'PhabricatorApplicationTransactionValueController',
'remarkuppreview/'
=> 'PhabricatorApplicationTransactionRemarkupPreviewController',
'editengine/' => array(
$this->getQueryRoutePattern()
=> 'PhabricatorEditEngineListController',

View file

@ -0,0 +1,25 @@
<?php
final class PhabricatorApplicationTransactionRemarkupPreviewController
extends PhabricatorApplicationTransactionController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$corpus = $request->getStr('corpus');
$remarkup = new PHUIRemarkupView($viewer, $corpus);
$content = array(
'content' => hsprintf('%s', $remarkup),
);
return id(new AphrontAjaxResponse())
->setContent($content);
}
}

View file

@ -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,

View file

@ -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};
}

View file

@ -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'));
});
});