Changeset View
Changeset View
Standalone View
Standalone View
webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js
/** | /** | ||||
* @provides javelin-behavior-phabricator-remarkup-assist | * @provides javelin-behavior-phabricator-remarkup-assist | ||||
* @requires javelin-behavior | * @requires javelin-behavior | ||||
* javelin-stratcom | * javelin-stratcom | ||||
* javelin-dom | * javelin-dom | ||||
* phabricator-phtize | * phabricator-phtize | ||||
* phabricator-textareautils | * phabricator-textareautils | ||||
* javelin-workflow | * javelin-workflow | ||||
* javelin-vector | * javelin-vector | ||||
*/ | */ | ||||
JX.behavior('phabricator-remarkup-assist', function(config) { | JX.behavior('phabricator-remarkup-assist', function(config) { | ||||
var pht = JX.phtize(config.pht); | var pht = JX.phtize(config.pht); | ||||
var root = JX.$(config.rootID); | |||||
var area = JX.DOM.find(root, 'textarea'); | |||||
var edit_mode = 'normal'; | var edit_mode = 'normal'; | ||||
var edit_root = null; | var edit_root = null; | ||||
var preview = null; | |||||
function set_edit_mode(root, mode) { | function set_edit_mode(root, mode) { | ||||
if (mode == edit_mode) { | if (mode == edit_mode) { | ||||
return; | return; | ||||
} | } | ||||
// First, disable any active mode. | // First, disable any active mode. | ||||
if (edit_root) { | if (edit_root) { | ||||
if (edit_mode == 'fa-arrows-alt') { | if (edit_mode == 'fa-arrows-alt') { | ||||
JX.DOM.alterClass(edit_root, 'remarkup-control-fullscreen-mode', false); | JX.DOM.alterClass(edit_root, 'remarkup-control-fullscreen-mode', false); | ||||
JX.DOM.alterClass(document.body, 'remarkup-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; | edit_root = root; | ||||
edit_mode = mode; | edit_mode = mode; | ||||
// Now, apply the new mode. | // Now, apply the new mode. | ||||
if (mode == 'fa-arrows-alt') { | if (mode == 'fa-arrows-alt') { | ||||
JX.DOM.alterClass(edit_root, 'remarkup-control-fullscreen-mode', true); | JX.DOM.alterClass(edit_root, 'remarkup-control-fullscreen-mode', true); | ||||
JX.DOM.alterClass(document.body, 'remarkup-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(); | resizearea(); | ||||
if (preview) { | |||||
resize_preview(); | |||||
JX.DOM.hide(area); | |||||
} | |||||
} | } | ||||
JX.DOM.focus(JX.DOM.find(edit_root, 'textarea')); | JX.DOM.focus(area); | ||||
} | } | ||||
function resizearea() { | function resizearea() { | ||||
if (!edit_root) { | if (!edit_root) { | ||||
return; | return; | ||||
} | } | ||||
if (edit_mode != 'fa-arrows-alt') { | if (edit_mode != 'fa-arrows-alt') { | ||||
return; | return; | ||||
} | } | ||||
// In Firefox, a textarea with position "absolute" or "fixed", anchored | // In Firefox, a textarea with position "absolute" or "fixed", anchored | ||||
// "top" and "bottom", and height "auto" renders as two lines high. Force | // "top" and "bottom", and height "auto" renders as two lines high. Force | ||||
// it to the correct height with Javascript. | // it to the correct height with Javascript. | ||||
var area = JX.DOM.find(edit_root, 'textarea'); | |||||
var v = JX.Vector.getViewport(); | var v = JX.Vector.getViewport(); | ||||
v.x = null; | v.x = null; | ||||
v.y -= 26; | v.y -= 26; | ||||
v.setDim(area); | v.setDim(area); | ||||
} | } | ||||
JX.Stratcom.listen('resize', null, resizearea); | JX.Stratcom.listen('resize', null, resizearea); | ||||
JX.Stratcom.listen('keydown', null, function(e) { | JX.Stratcom.listen('keydown', null, function(e) { | ||||
if (e.getSpecialKey() != 'esc') { | if (e.getSpecialKey() != 'esc') { | ||||
return; | return; | ||||
} | } | ||||
if (edit_mode != 'fa-arrows-alt') { | if (edit_mode != 'fa-arrows-alt') { | ||||
return; | return; | ||||
} | } | ||||
Show All 33 Lines | if (ch === '>') { | ||||
sel[i] = ch + sel[i]; | sel[i] = ch + sel[i]; | ||||
} | } | ||||
return sel.join('\n'); | return sel.join('\n'); | ||||
} | } | ||||
return sel.join('\n' + ch); | 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 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 | // if they have a word selected and want to bold it). Otherwise we'll insert | ||||
// generic text. | // generic text. | ||||
var sel = JX.TextAreaUtils.getSelectionText(area); | var sel = JX.TextAreaUtils.getSelectionText(area); | ||||
var r = JX.TextAreaUtils.getSelectionRange(area); | var r = JX.TextAreaUtils.getSelectionRange(area); | ||||
var ch; | var ch; | ||||
switch (action) { | switch (action) { | ||||
▲ Show 20 Lines • Show All 50 Lines • ▼ Show 20 Lines | switch (action) { | ||||
break; | break; | ||||
case 'fa-arrows-alt': | case 'fa-arrows-alt': | ||||
if (edit_mode == 'fa-arrows-alt') { | if (edit_mode == 'fa-arrows-alt') { | ||||
set_edit_mode(root, 'normal'); | set_edit_mode(root, 'normal'); | ||||
} else { | } else { | ||||
set_edit_mode(root, 'fa-arrows-alt'); | set_edit_mode(root, 'fa-arrows-alt'); | ||||
} | } | ||||
break; | 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( | function resize_preview(restore) { | ||||
['click'], | 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', | 'remarkup-assist', | ||||
function(e) { | function(e) { | ||||
var data = e.getNodeData('remarkup-assist'); | var data = e.getNodeData('remarkup-assist'); | ||||
if (!data.action) { | if (!data.action) { | ||||
return; | return; | ||||
} | } | ||||
e.kill(); | e.kill(); | ||||
if (config.disabled) { | if (config.disabled) { | ||||
return; | return; | ||||
} | } | ||||
var root = e.getNode('remarkup-assist-control'); | assist(area, data.action, root, e.getNode('remarkup-assist')); | ||||
var area = JX.DOM.find(root, 'textarea'); | |||||
assist(area, data.action, root); | |||||
}); | }); | ||||
}); | }); |