1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-02 11:42:42 +01:00
phorge-phorge/webroot/rsrc/js/application/transactions/behavior-comment-actions.js
epriestley 75a5dd8d8c Add more accessibility labels for screen readers
Summary:
Depends on D19594. See PHI823. Ref T13164.

  - Add a label for the "X" button in comment areas, like "Remove Action: Change Subscribers".
  - Add a label for the floating header display options menu in Differential.
  - Add `role="button"` to `PHUIButtonView` objects that we render with an `<a ...>` tag.

Test Plan:
Viewed a revision with `?__aural__=true`:

  - Saw "Remove Action: ..." label.
  - Saw "Display Options" label.
  - Used inspector to verify that some `<a class="button" ...>` now have `<a class="button" role="button" ...>`. This isn't exhaustive, but at least improves things. A specific example is the "edit", "reply", etc., actions on inline comments.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13164

Differential Revision: https://secure.phabricator.com/D19595
2018-08-17 13:31:51 -07:00

264 lines
5.9 KiB
JavaScript

/**
* @provides javelin-behavior-comment-actions
* @requires javelin-behavior
* javelin-stratcom
* javelin-workflow
* javelin-dom
* phuix-form-control-view
* phuix-icon-view
* javelin-behavior-phabricator-gesture
*/
JX.behavior('comment-actions', function(config) {
var action_map = config.actions;
var action_node = JX.$(config.actionID);
var form_node = JX.$(config.formID);
var input_node = JX.$(config.inputID);
var place_node = JX.$(config.placeID);
var rows = {};
JX.DOM.listen(action_node, 'change', null, function() {
var option = find_option(action_node.value);
action_node.value = '+';
if (option) {
add_row(option);
}
});
function find_option(key) {
var options = action_node.options;
var option;
for (var ii = 0; ii < options.length; ii++) {
option = options[ii];
if (option.value == key) {
return option;
}
}
return null;
}
function remove_action(key) {
var row = rows[key];
if (row) {
JX.DOM.remove(row.node);
row.option.disabled = false;
delete rows[key];
}
}
function serialize_actions() {
var data = [];
for (var k in rows) {
data.push({
type: k,
value: rows[k].control.getValue(),
initialValue: action_map[k].initialValue || null
});
}
return JX.JSON.stringify(data);
}
function get_data() {
var data = JX.DOM.convertFormToDictionary(form_node);
data.__preview__ = 1;
data[input_node.name] = serialize_actions();
return data;
}
function restore_draft_actions(drafts) {
var draft;
var option;
var control;
for (var ii = 0; ii < drafts.length; ii++) {
draft = drafts[ii];
option = find_option(draft);
if (!option) {
continue;
}
control = add_row(option);
}
}
function onresponse(response) {
if (JX.Device.getDevice() != 'desktop') {
return;
}
var panel = JX.$(config.panelID);
if (!response.xactions.length) {
JX.DOM.hide(panel);
} else {
var preview_root = JX.$(config.timelineID);
JX.DOM.setContent(
preview_root,
[
JX.$H(response.xactions.join('')),
JX.$H(response.previewContent)
]);
JX.DOM.show(panel);
// NOTE: Resonses are currently processed before associated behaviors are
// registered. We need to defer invoking this event so that any behaviors
// accompanying the response are registered.
var invoke_preview = function() {
JX.Stratcom.invoke(
'EditEngine.didCommentPreview',
null,
{
rootNode: preview_root
});
};
setTimeout(invoke_preview, 0);
}
}
function force_preview() {
if (!config.showPreview) {
return;
}
new JX.Request(config.actionURI, onresponse)
.setData(get_data())
.send();
}
function add_row(option) {
var action = action_map[option.value];
if (!action) {
return;
}
// Remove any conflicting actions. For example, "Accept Revision" conflicts
// with "Reject Revision".
var conflict_key = action.conflictKey || null;
if (conflict_key !== null) {
for (var k in action_map) {
if (k === action.key) {
continue;
}
if (action_map[k].conflictKey !== conflict_key) {
continue;
}
if (!(k in rows)) {
continue;
}
remove_action(k);
}
}
option.disabled = true;
var aural = JX.$N('span', {className: 'aural-only'}, action.auralLabel);
var icon = new JX.PHUIXIconView()
.setIcon('fa-times-circle');
var remove = JX.$N('a', {href: '#'}, [aural, icon.getNode()]);
var control = new JX.PHUIXFormControl()
.setLabel(action.label)
.setError(remove)
.setControl(action.type, action.spec)
.setClass('phui-comment-action');
var node = control.getNode();
JX.Stratcom.addSigil(node, 'touchable');
JX.DOM.listen(node, 'gesture.swipe.end', null, function(e) {
var data = e.getData();
if (data.direction != 'left') {
// Didn't swipe left.
return;
}
if (data.length <= (JX.Vector.getDim(node).x / 2)) {
// Didn't swipe far enough.
return;
}
remove_action(action.key);
});
rows[action.key] = {
control: control,
node: node,
option: option
};
JX.DOM.listen(remove, 'click', null, function(e) {
e.kill();
remove_action(action.key);
});
place_node.parentNode.insertBefore(node, place_node);
force_preview();
return control;
}
JX.DOM.listen(form_node, ['submit', 'didSyntheticSubmit'], null, function() {
input_node.value = serialize_actions();
});
if (config.showPreview) {
var request = new JX.PhabricatorShapedRequest(
config.actionURI,
onresponse,
get_data);
var trigger = JX.bind(request, request.trigger);
JX.DOM.listen(form_node, 'keydown', null, trigger);
JX.DOM.listen(form_node, 'shouldRefresh', null, force_preview);
request.start();
var old_device = JX.Device.getDevice();
var ondevicechange = function() {
var new_device = JX.Device.getDevice();
var panel = JX.$(config.panelID);
if (new_device == 'desktop') {
request.setRateLimit(500);
// Force an immediate refresh if we switched from another device type
// to desktop.
if (old_device != new_device) {
force_preview();
}
} else {
// On mobile, don't show live previews and only save drafts every
// 10 seconds.
request.setRateLimit(10000);
JX.DOM.hide(panel);
}
old_device = new_device;
};
ondevicechange();
JX.Stratcom.listen('phabricator-device-change', null, ondevicechange);
}
restore_draft_actions(config.drafts || []);
});