mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-23 15:22:41 +01:00
22551d106d
Summary: Right now, if no comment is selected the JS executes and throws an exception. Instead, if nothing is selected just do nothing. Fixes T6107. Test Plan: opened up a commit in diffusion with an inline comment. pressed 'r' and saw no exceptions and nothing happen. pressed 'n' to select the next inline comment and then 'r' and it worked. opened up a commit in diffusion without any inline comments. pressed 'r' and saw no exceptions and nothing happen. opened up a diff in differential with an inline comment. pressed 'r' and saw no exceptons and nothing happened. pressed 'n' to select the next inline comment and then 'r' and it worked. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T6107 Differential Revision: https://secure.phabricator.com/D10843
280 lines
6.8 KiB
JavaScript
280 lines
6.8 KiB
JavaScript
/**
|
|
* @provides javelin-behavior-differential-keyboard-navigation
|
|
* @requires javelin-behavior
|
|
* javelin-dom
|
|
* javelin-stratcom
|
|
* phabricator-keyboard-shortcut
|
|
*/
|
|
|
|
JX.behavior('differential-keyboard-navigation', function(config) {
|
|
|
|
var cursor = -1;
|
|
var changesets;
|
|
|
|
var selection_begin = null;
|
|
var selection_end = null;
|
|
|
|
var refreshFocus = function() {};
|
|
|
|
function init() {
|
|
if (changesets) {
|
|
return;
|
|
}
|
|
changesets = JX.DOM.scry(document.body, 'div', 'differential-changeset');
|
|
}
|
|
|
|
function getBlocks(cursor) {
|
|
// TODO: This might not be terribly fast; we can't currently memoize it
|
|
// because it can change as ajax requests come in (e.g., content loads).
|
|
|
|
var rows = JX.DOM.scry(changesets[cursor], 'tr');
|
|
var blocks = [[changesets[cursor], changesets[cursor]]];
|
|
var start = null;
|
|
var type;
|
|
var ii;
|
|
|
|
// Don't show code blocks inside a collapsed file.
|
|
var diff = JX.DOM.scry(changesets[cursor], 'table', 'differential-diff');
|
|
if (diff.length == 1 && JX.Stratcom.getData(diff[0]).hidden) {
|
|
return blocks;
|
|
}
|
|
|
|
function push() {
|
|
if (start) {
|
|
blocks.push([start, rows[ii - 1]]);
|
|
}
|
|
start = null;
|
|
}
|
|
|
|
for (ii = 0; ii < rows.length; ii++) {
|
|
type = getRowType(rows[ii]);
|
|
if (type == 'comment') {
|
|
// If we see these types of rows, make a block for each one.
|
|
push();
|
|
}
|
|
if (!type) {
|
|
push();
|
|
} else if (type && !start) {
|
|
start = rows[ii];
|
|
}
|
|
}
|
|
push();
|
|
|
|
return blocks;
|
|
}
|
|
|
|
function getRowType(row) {
|
|
// NOTE: Being somewhat over-general here to allow other types of objects
|
|
// to be easily focused in the future (inline comments, 'show more..').
|
|
|
|
if (row.className.indexOf('inline') !== -1) {
|
|
return 'comment';
|
|
}
|
|
|
|
if (row.className.indexOf('differential-changeset') !== -1) {
|
|
return 'file';
|
|
}
|
|
|
|
var cells = JX.DOM.scry(row, 'td');
|
|
|
|
for (var ii = 0; ii < cells.length; ii++) {
|
|
// NOTE: The semantic use of classnames here is for performance; don't
|
|
// emulate this elsewhere since it's super terrible.
|
|
if (cells[ii].className.indexOf('old') !== -1 ||
|
|
cells[ii].className.indexOf('new') !== -1) {
|
|
return 'change';
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function jump(manager, delta, jump_to_type) {
|
|
init();
|
|
|
|
if (cursor < 0) {
|
|
if (delta < 0) {
|
|
// If the user goes "back" without a selection, just reject the action.
|
|
return;
|
|
} else {
|
|
cursor = 0;
|
|
}
|
|
}
|
|
|
|
while (true) {
|
|
var blocks = getBlocks(cursor);
|
|
var focus;
|
|
if (delta < 0) {
|
|
focus = blocks.length;
|
|
} else {
|
|
focus = -1;
|
|
}
|
|
|
|
for (var ii = 0; ii < blocks.length; ii++) {
|
|
if (blocks[ii][0] == selection_begin) {
|
|
focus = ii;
|
|
break;
|
|
}
|
|
}
|
|
|
|
while (true) {
|
|
focus += delta;
|
|
|
|
if (blocks[focus]) {
|
|
var row_type = getRowType(blocks[focus][0]);
|
|
if (jump_to_type && row_type != jump_to_type) {
|
|
continue;
|
|
}
|
|
|
|
selection_begin = blocks[focus][0];
|
|
selection_end = blocks[focus][1];
|
|
|
|
manager.scrollTo(selection_begin);
|
|
|
|
refreshFocus = function() {
|
|
manager.focusOn(selection_begin, selection_end);
|
|
};
|
|
|
|
refreshFocus();
|
|
|
|
return;
|
|
} else {
|
|
var adjusted = (cursor + delta);
|
|
if (adjusted < 0 || adjusted >= changesets.length) {
|
|
// Stop cursor movement when the user reaches either end.
|
|
return;
|
|
}
|
|
cursor = adjusted;
|
|
|
|
// Break the inner loop and go to the next file.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// When inline comments are updated, wipe out our cache of blocks since
|
|
// comments may have been added or deleted.
|
|
JX.Stratcom.listen(
|
|
null,
|
|
'differential-inline-comment-update',
|
|
function() {
|
|
changesets = null;
|
|
});
|
|
// Same thing when a file is hidden or shown; don't want to highlight
|
|
// invisible code.
|
|
JX.Stratcom.listen(
|
|
'differential-toggle-file-toggled',
|
|
null,
|
|
function() {
|
|
changesets = null;
|
|
init();
|
|
refreshFocus();
|
|
});
|
|
|
|
var haunt_mode = 0;
|
|
function haunt() {
|
|
haunt_mode = (haunt_mode + 1) % 3;
|
|
|
|
var el = JX.$(config.haunt);
|
|
for (var ii = 1; ii <= 2; ii++) {
|
|
JX.DOM.alterClass(el, 'differential-haunt-mode-'+ii, (haunt_mode == ii));
|
|
}
|
|
}
|
|
|
|
new JX.KeyboardShortcut('j', 'Jump to next change.')
|
|
.setHandler(function(manager) {
|
|
jump(manager, 1);
|
|
})
|
|
.register();
|
|
|
|
new JX.KeyboardShortcut('k', 'Jump to previous change.')
|
|
.setHandler(function(manager) {
|
|
jump(manager, -1);
|
|
})
|
|
.register();
|
|
|
|
new JX.KeyboardShortcut('J', 'Jump to next file.')
|
|
.setHandler(function(manager) {
|
|
jump(manager, 1, 'file');
|
|
})
|
|
.register();
|
|
|
|
new JX.KeyboardShortcut('K', 'Jump to previous file.')
|
|
.setHandler(function(manager) {
|
|
jump(manager, -1, 'file');
|
|
})
|
|
.register();
|
|
|
|
new JX.KeyboardShortcut('n', 'Jump to next inline comment.')
|
|
.setHandler(function(manager) {
|
|
jump(manager, 1, 'comment');
|
|
})
|
|
.register();
|
|
|
|
new JX.KeyboardShortcut('p', 'Jump to previous inline comment.')
|
|
.setHandler(function(manager) {
|
|
jump(manager, -1, 'comment');
|
|
})
|
|
.register();
|
|
|
|
|
|
new JX.KeyboardShortcut('t', 'Jump to the table of contents.')
|
|
.setHandler(function(manager) {
|
|
var toc = JX.$('toc');
|
|
manager.scrollTo(toc);
|
|
})
|
|
.register();
|
|
|
|
new JX.KeyboardShortcut(
|
|
'h',
|
|
'Collapse or expand the file display (after jump).')
|
|
.setHandler(function() {
|
|
if (!changesets || !changesets[cursor]) {
|
|
return;
|
|
}
|
|
JX.Stratcom.invoke('differential-toggle-file', null, {
|
|
diff: JX.DOM.scry(changesets[cursor], 'table', 'differential-diff')
|
|
});
|
|
})
|
|
.register();
|
|
|
|
|
|
function inline_op(node, op) {
|
|
// nothing selected
|
|
if (!node) {
|
|
return;
|
|
}
|
|
if (!JX.DOM.scry(node, 'a', 'differential-inline-' + op)) {
|
|
// No link for this operation, e.g. editing a comment you can't edit.
|
|
return;
|
|
}
|
|
|
|
var data = {
|
|
node: JX.DOM.find(node, 'div', 'differential-inline-comment'),
|
|
op: op
|
|
};
|
|
|
|
JX.Stratcom.invoke('differential-inline-action', null, data);
|
|
}
|
|
|
|
new JX.KeyboardShortcut('r', 'Reply to selected inline comment.')
|
|
.setHandler(function() {
|
|
inline_op(selection_begin, 'reply');
|
|
})
|
|
.register();
|
|
|
|
new JX.KeyboardShortcut('e', 'Edit selected inline comment.')
|
|
.setHandler(function() {
|
|
inline_op(selection_begin, 'edit');
|
|
})
|
|
.register();
|
|
|
|
if (config.haunt) {
|
|
new JX.KeyboardShortcut('z', 'Cycle comment panel haunting modes.')
|
|
.setHandler(haunt)
|
|
.register();
|
|
}
|
|
|
|
});
|