1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-10 08:52:39 +01:00

Add assistance buttons to Remarkup text areas

Summary: Unblocker for D3547. Adds markup assist UI (buttons which generate remarkup for you -- not WYSIWYG) to Remarkup text areas.

Test Plan: See screenshot. Clicked the buttons a bunch with selected/unselcted text. Results seem broadly reasonable.

Reviewers: btrahan, vrana, teisenbe

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T337

Differential Revision: https://secure.phabricator.com/D3594
This commit is contained in:
epriestley 2012-10-01 18:46:34 -07:00
parent f182d735e9
commit 8763d0ca93
6 changed files with 304 additions and 42 deletions

View file

@ -18,25 +18,112 @@
final class PhabricatorRemarkupControl extends AphrontFormTextAreaControl {
public function getCaption() {
protected function renderInput() {
$caption = parent::getCaption();
if ($caption) {
$caption_suffix = '<br />'.$caption;
} else {
$caption_suffix = '';
}
Javelin::initBehavior('phabricator-remarkup-assist', array());
return phutil_render_tag(
'a',
$actions = array(
'b' => array(
'text' => 'B',
),
'i' => array(
'text' => 'I',
),
'tt' => array(
'text' => 'T',
),
's' => array(
'text' => 'S',
),
array(
'spacer' => true,
),
'ul' => array(
'text' => "\xE2\x80\xA2",
),
'ol' => array(
'text' => '1.',
),
'code' => array(
'text' => '{}',
),
array(
'spacer' => true,
),
'mention' => array(
'text' => '@',
),
array(
'spacer' => true,
),
'h1' => array(
'text' => 'H',
),
array(
'spacer' => true,
),
'help' => array(
'align' => 'right',
'text' => '?',
'href' => PhabricatorEnv::getDoclink(
'article/Remarkup_Reference.html'),
'tabindex' => '-1',
'target' => '_blank',
),
'Formatting Reference') .
$caption_suffix;
);
$buttons = array();
foreach ($actions as $action => $spec) {
if (idx($spec, 'spacer')) {
$buttons[] = '<span> </span>';
continue;
}
$classes = array();
$classes[] = 'button';
$classes[] = 'grey';
$classes[] = 'remarkup-assist-button';
if (idx($spec, 'align') == 'right') {
$classes[] = 'remarkup-assist-right';
}
$href = idx($spec, 'href', '#');
if ($href == '#') {
$meta = array('action' => $action);
$mustcapture = true;
$target = null;
} else {
$meta = null;
$mustcapture = null;
$target = '_blank';
}
$buttons[] = javelin_render_tag(
'a',
array(
'class' => implode(' ', $classes),
'href' => $href,
'sigil' => 'remarkup-assist',
'meta' => $meta,
'mustcapture' => $mustcapture,
'target' => $target,
'tabindex' => -1,
),
phutil_render_tag(
'div',
array(
'class' => 'remarkup-assist remarkup-assist-'.$action,
),
idx($spec, 'text', '')));
}
$buttons = implode('', $buttons);
return javelin_render_tag(
'div',
array(
'sigil' => 'remarkup-assist-control',
),
$buttons.
parent::renderInput());
}
}

View file

@ -49,7 +49,9 @@
.aphront-form-input input,
.aphront-form-input textarea {
font-size: 12px;
display: block;
width: 100%;
box-sizing: border-box;
}

View file

@ -246,3 +246,64 @@ img.phabricator-remarkup-embed-image {
background: #ffffff;
padding: 3px 6px;
}
.remarkup-assist-bar {
padding: 2px 0;
}
a.remarkup-assist-button {
padding-left: 4px;
padding-right: 4px;
padding-bottom: 4px;
margin-bottom: 3px;
position: relative;
overflow: hidden;
height: 16px;
width: 16px;
}
a.remarkup-assist-button + a.remarkup-assist-button {
border-left-width: 0px;
}
.remarkup-assist {
float: left;
height: 16px;
width: 16px;
font-weight: normal;
}
.remarkup-assist-right {
float: right;
}
.remarkup-assist-b {
font-family: "Georgia", serif;
font-weight: bold;
}
.remarkup-assist-i {
font-family: "Georgia", serif;
font-style: italic;
}
.remarkup-assist-code,
.remarkup-assist-tt {
text-align: center;
font-family: monospace;
}
.remarkup-assist-s {
font-family: "Georgia", serif;
text-decoration: line-through;
}
.remarkup-assist-ol {
font-family: "Georgia", serif;
}
.remarkup-assist-h1 {
font-family: "Georgia", serif;
font-weight: bold;
}

View file

@ -0,0 +1,49 @@
/**
* @requires javelin-install
* @provides phabricator-textareautils
* @javelin
*/
JX.install('TextAreaUtils', {
statics : {
getSelectionRange : function(area) {
var v = area.value;
// NOTE: This works well in Safari, Firefox and Chrome. We'll probably get
// less-good behavior on IE.
var s = v.length;
var e = v.length;
if ('selectionStart' in area) {
s = area.selectionStart;
e = area.selectionEnd;
}
return {start: s, end: e};
},
getSelectionText : function(area) {
var v = area.value;
var r = JX.TextAreaUtils.getSelectionRange(area);
return v.substring(r.start, r.end);
},
setSelectionRange : function(area, start, end) {
if ('setSelectionRange' in area) {
area.focus();
area.setSelectionRange(start, end);
}
},
setSelectionText : function(area, text) {
var v = area.value;
var r = JX.TextAreaUtils.getSelectionRange(area);
v = v.substring(0, r.start) + text + v.substring(r.end, v.length);
area.value = v;
JX.TextAreaUtils.setSelectionRange(area, r.start, r.start + text.length);
}
}
});

View file

@ -4,6 +4,7 @@
* javelin-dom
* phabricator-drag-and-drop-file-upload
* phabricator-paste-file-upload
* phabricator-textareautils
*/
JX.behavior('aphront-drag-and-drop-textarea', function(config) {
@ -11,34 +12,7 @@ JX.behavior('aphront-drag-and-drop-textarea', function(config) {
var target = JX.$(config.target);
function onupload(f) {
var v = target.value;
var insert = '{F' + f.id + '}';
// NOTE: This works well in Safari, Firefox and Chrome. We'll probably get
// less-good behavior on IE, but I think IE doesn't support drag-and-drop
// or paste uploads anyway.
// Set the insert position to the end of the text, so we get reasonable
// default behavior.
var s = v.length;
var e = v.length;
// If possible, refine the insert position based on the current selection.
if ('selectionStart' in target) {
s = target.selectionStart;
e = target.selectionEnd;
}
// Build the new text.
v = v.substring(0, s) + insert + v.substring(e, v.length);
// Replace the current value with the new text.
target.value = v;
// If possible, place the cursor after the inserted text.
if ('setSelectionRange' in target) {
target.focus();
target.setSelectionRange(s + insert.length, s + insert.length);
}
JX.TextAreaUtils.setSelectionText(target, '{F' + f.id + '}');
}
if (JX.PhabricatorDragAndDropFileUpload.isSupported()) {

View file

@ -0,0 +1,89 @@
/**
* @provides javelin-behavior-phabricator-remarkup-assist
* @requires javelin-behavior
* javelin-stratcom
* javelin-dom
* phabricator-textareautils
*/
JX.behavior('phabricator-remarkup-assist', function(config) {
function update(area, l, m, r) {
// Replace the selection with the entire assisted text.
JX.TextAreaUtils.setSelectionText(area, l + m + r);
// Now, select just the middle part. For instance, if the user clicked
// "B" to create bold text, we insert '**bold**' but just select the word
// "bold" so if they type stuff they'll be editing the bold text.
var r = JX.TextAreaUtils.getSelectionRange(area);
JX.TextAreaUtils.setSelectionRange(
area,
r.start + l.length,
r.start + l.length + m.length);
}
function assist(area, action) {
// 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.
var sel = JX.TextAreaUtils.getSelectionText(area);
var r = JX.TextAreaUtils.getSelectionRange(area);
switch (action) {
case 'b':
update(area, '**', sel || 'bold text', '**');
break;
case 'i':
update(area, '//', sel || 'italic text', '//');
break;
case 'tt':
update(area, '`', sel || 'monospaced text', '`');
break;
case 's':
update(area, '~~', sel || 'strikethrough text', '~~');
break;
case 'ul':
case 'ol':
var ch = (action == 'ol') ? ' # ' : ' - ';
if (sel) {
sel = sel.split("\n");
} else {
sel = ["List Item"];
}
sel = sel.join("\n" + ch);
update(area, ((r.start == 0) ? "" : "\n\n") + ch, sel, "\n\n");
break;
case 'code':
sel = sel || "foreach ($list as $item) {\n work_miracles($item);\n}";
sel = sel.split("\n");
sel = " " + sel.join("\n ");
update(area, ((r.start == 0) ? "" : "\n\n"), sel, "\n\n");
break;
case 'mention':
update(area, '@', sel || 'username', '');
break;
case 'h1':
sel = sel || 'Header';
update(area, ((r.start == 0) ? "" : "\n\n") + "= ", sel, " =\n\n");
break;
}
}
JX.Stratcom.listen(
['click'],
'remarkup-assist',
function(e) {
var data = e.getNodeData('remarkup-assist');
if (!data) {
return;
}
e.kill();
var root = e.getNode('remarkup-assist-control');
var area = JX.DOM.find(root, 'textarea');
assist(area, data.action);
});
});