mirror of
https://github.com/zulip/zulip.git
synced 2025-11-05 06:23:38 +00:00
hotkey.js: Add hotkey for drafts.
* 'd' in message view opens drafts. This also adds hotkeys within the drafts UI: * Up/down arrow keys navigate the drafts. * Pressing enter edits the selected draft. * Pressing backspace deletes the selected draft. Some variable names tweaked by tabbott.
This commit is contained in:
@@ -14,6 +14,11 @@
|
|||||||
set_global('activity', {
|
set_global('activity', {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
set_global('drafts', {
|
||||||
|
drafts_handle_events: function () { return; },
|
||||||
|
drafts_overlay_open: function () { return; },
|
||||||
|
});
|
||||||
|
|
||||||
set_global('$', function () {
|
set_global('$', function () {
|
||||||
return {
|
return {
|
||||||
// Hack: Used for reactions hotkeys; may want to restructure.
|
// Hack: Used for reactions hotkeys; may want to restructure.
|
||||||
@@ -139,7 +144,7 @@ function stubbing(func_name_to_stub, test_function) {
|
|||||||
|
|
||||||
// Unmapped keys should immediately return false, without
|
// Unmapped keys should immediately return false, without
|
||||||
// calling any functions outside of hotkey.js.
|
// calling any functions outside of hotkey.js.
|
||||||
assert_unmapped('abdefhlmoptuxyz');
|
assert_unmapped('abefhlmoptuxyz');
|
||||||
assert_unmapped('BEFHILNOQTWXYZ');
|
assert_unmapped('BEFHILNOQTWXYZ');
|
||||||
|
|
||||||
// We have to skip some checks due to the way the code is
|
// We have to skip some checks due to the way the code is
|
||||||
@@ -193,6 +198,7 @@ function stubbing(func_name_to_stub, test_function) {
|
|||||||
assert_mapping('C', 'compose_actions.start');
|
assert_mapping('C', 'compose_actions.start');
|
||||||
assert_mapping('P', 'narrow.by');
|
assert_mapping('P', 'narrow.by');
|
||||||
assert_mapping('g', 'gear_menu.open');
|
assert_mapping('g', 'gear_menu.open');
|
||||||
|
assert_mapping('d', 'drafts.toggle');
|
||||||
|
|
||||||
// Next, test keys that only work on a selected message.
|
// Next, test keys that only work on a selected message.
|
||||||
var message_view_only_keys = '@*+rRjJkKsSvi:GM';
|
var message_view_only_keys = '@*+rRjJkKsSvi:GM';
|
||||||
|
|||||||
@@ -203,9 +203,120 @@ exports.setup_page = function (callback) {
|
|||||||
populate_and_fill();
|
populate_and_fill();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.drafts_overlay_open = function () {
|
||||||
|
return $("#draft_overlay").hasClass("show");
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.drafts_handle_events = function (e, event_key) {
|
||||||
|
var draft_arrow = draft_model.get();
|
||||||
|
var draft_id_arrow = Object.getOwnPropertyNames(draft_arrow);
|
||||||
|
// This detects up arrow key presses when the draft overlay
|
||||||
|
// is open and scrolls through the drafts.
|
||||||
|
if (event_key === "up_arrow") {
|
||||||
|
if ($(".draft-info-box:focus")[0] === undefined) {
|
||||||
|
if (draft_id_arrow.length > 0) {
|
||||||
|
var last_draft = draft_id_arrow[draft_id_arrow.length-1];
|
||||||
|
var last_draft_element = document.querySelectorAll('[data-draft-id="' + last_draft + '"]');
|
||||||
|
var focus_up_element = last_draft_element[0].children[0];
|
||||||
|
focus_up_element.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var focus_draft_up_row = $(".draft-info-box:focus")[0].parentElement;
|
||||||
|
var prev_focus_draft_row = $(focus_draft_up_row).prev();
|
||||||
|
if ($(".draft-info-box:first")[0].parentElement === prev_focus_draft_row[0]) {
|
||||||
|
$(".drafts-list")[0].scrollTop = 0;
|
||||||
|
}
|
||||||
|
if (prev_focus_draft_row[0].children[0] !== undefined) {
|
||||||
|
prev_focus_draft_row[0].children[0].focus();
|
||||||
|
// If the next draft is cut off, scroll more.
|
||||||
|
if (prev_focus_draft_row.position().top < 55) {
|
||||||
|
// 55 is the minimum distance from the top that will require extra scrolling.
|
||||||
|
$(".drafts-list")[0].scrollTop = $(".drafts-list")[0].scrollTop - (55 - prev_focus_draft_row.position().top);
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// This detects down arrow key presses when the draft overlay
|
||||||
|
// is open and scrolls through the drafts.
|
||||||
|
if (event_key === "down_arrow") {
|
||||||
|
if ($(".draft-info-box:focus")[0] === undefined) {
|
||||||
|
if (draft_id_arrow.length > 0) {
|
||||||
|
var first_draft = draft_id_arrow[0];
|
||||||
|
var first_draft_element = document.querySelectorAll('[data-draft-id="' + first_draft + '"]');
|
||||||
|
var focus_down_element = first_draft_element[0].children[0];
|
||||||
|
focus_down_element.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var focus_draft_down_row = $(".draft-info-box:focus")[0].parentElement;
|
||||||
|
var next_focus_draft_row = $(focus_draft_down_row).next();
|
||||||
|
if ($(".draft-info-box:last")[0].parentElement === next_focus_draft_row[0]) {
|
||||||
|
$(".drafts-list")[0].scrollTop = $('.drafts-list')[0].scrollHeight - $('.drafts-list').height();
|
||||||
|
}
|
||||||
|
if (next_focus_draft_row[0] !== undefined) {
|
||||||
|
next_focus_draft_row[0].children[0].focus();
|
||||||
|
// If the next draft is cut off, scroll more.
|
||||||
|
if (next_focus_draft_row.position() !== undefined) {
|
||||||
|
var dist_from_top = next_focus_draft_row.position().top;
|
||||||
|
var total_dist = dist_from_top + next_focus_draft_row[0].clientHeight;
|
||||||
|
var dist_from_bottom = $(".drafts-container")[0].clientHeight - total_dist;
|
||||||
|
if (dist_from_bottom < -4) {
|
||||||
|
//-4 is the min dist from the bottom that will require extra scrolling.
|
||||||
|
$(".drafts-list")[0].scrollTop = $(".drafts-list")[0].scrollTop + 2 - (dist_from_bottom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Allows user to delete drafts with backspace
|
||||||
|
if (event_key === "backspace") {
|
||||||
|
var elt = document.activeElement;
|
||||||
|
if (elt.parentElement.hasAttribute("data-draft-id")) {
|
||||||
|
var focused_draft = $(elt.parentElement)[0].getAttribute("data-draft-id");
|
||||||
|
var focus_draft_back_row = $(elt)[0].parentElement;
|
||||||
|
var backnext_focus_draft_row = $(focus_draft_back_row).next();
|
||||||
|
var backprev_focus_draft_row = $(focus_draft_back_row).prev();
|
||||||
|
var delete_id;
|
||||||
|
if (backnext_focus_draft_row[0] !== undefined) {
|
||||||
|
delete_id = backnext_focus_draft_row[0].getAttribute("data-draft-id");
|
||||||
|
} else if (backprev_focus_draft_row[0] !== undefined) {
|
||||||
|
delete_id = backprev_focus_draft_row[0].getAttribute("data-draft-id");
|
||||||
|
}
|
||||||
|
drafts.draft_model.deleteDraft(focused_draft);
|
||||||
|
document.activeElement.parentElement.remove();
|
||||||
|
var new_focus_element = document.querySelectorAll('[data-draft-id="' + delete_id + '"]');
|
||||||
|
if (new_focus_element[0] !== undefined) {
|
||||||
|
new_focus_element[0].children[0].focus();
|
||||||
|
}
|
||||||
|
if ($("#drafts_table .draft-row").length === 0) {
|
||||||
|
$('#drafts_table .no-drafts').show();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.toggle = function () {
|
||||||
|
if (exports.drafts_overlay_open()) {
|
||||||
|
modals.close_modal("drafts");
|
||||||
|
} else {
|
||||||
|
exports.launch();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
exports.launch = function () {
|
exports.launch = function () {
|
||||||
exports.setup_page(function () {
|
exports.setup_page(function () {
|
||||||
$("#draft_overlay").addClass("show");
|
$("#draft_overlay").addClass("show");
|
||||||
|
var draft_list = drafts.draft_model.get();
|
||||||
|
var draft_id_list = Object.getOwnPropertyNames(draft_list);
|
||||||
|
if (draft_id_list.length > 0) {
|
||||||
|
var last_draft = draft_id_list[draft_id_list.length-1];
|
||||||
|
var last_draft_element = document.querySelectorAll('[data-draft-id="' + last_draft + '"]');
|
||||||
|
var focus_element = last_draft_element[0].children[0];
|
||||||
|
focus_element.focus();
|
||||||
|
$(".drafts-list")[0].scrollTop = $('.drafts-list')[0].scrollHeight - $('.drafts-list').height();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ var keypress_mappings = {
|
|||||||
85: {name: 'keyboard_sub', message_view_only: false}, //'U'
|
85: {name: 'keyboard_sub', message_view_only: false}, //'U'
|
||||||
86: {name: 'view_selected_stream', message_view_only: false}, //'V'
|
86: {name: 'view_selected_stream', message_view_only: false}, //'V'
|
||||||
99: {name: 'compose', message_view_only: true}, // 'c'
|
99: {name: 'compose', message_view_only: true}, // 'c'
|
||||||
|
100: {name: 'open_drafts', message_view_only: false}, // 'd'
|
||||||
103: {name: 'gear_menu', message_view_only: true}, // 'g'
|
103: {name: 'gear_menu', message_view_only: true}, // 'g'
|
||||||
105: {name: 'message_actions', message_view_only: true}, // 'i'
|
105: {name: 'message_actions', message_view_only: true}, // 'i'
|
||||||
106: {name: 'vim_down', message_view_only: true}, // 'j'
|
106: {name: 'vim_down', message_view_only: true}, // 'j'
|
||||||
@@ -207,7 +208,7 @@ exports.process_escape_key = function (e) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($("#draft_overlay").hasClass("show")) {
|
if (drafts.drafts_overlay_open()) {
|
||||||
modals.close_modal("drafts");
|
modals.close_modal("drafts");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -333,6 +334,21 @@ exports.process_enter_key = function (e) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This handles when pressing enter while looking at drafts.
|
||||||
|
// It restores draft that is focused.
|
||||||
|
if (drafts.drafts_overlay_open()) {
|
||||||
|
var draft_list = drafts.draft_model.get();
|
||||||
|
if (document.activeElement.parentElement.hasAttribute("data-draft-id")) {
|
||||||
|
var focused_draft = document.activeElement.parentElement.getAttribute("data-draft-id");
|
||||||
|
drafts.restore_draft(focused_draft);
|
||||||
|
} else {
|
||||||
|
var draft_id_list = Object.getOwnPropertyNames(draft_list);
|
||||||
|
var first_draft = draft_id_list[draft_id_list.length-1];
|
||||||
|
drafts.restore_draft(first_draft);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// If we're on a button or a link and have pressed enter, let the
|
// If we're on a button or a link and have pressed enter, let the
|
||||||
// browser handle the keypress
|
// browser handle the keypress
|
||||||
//
|
//
|
||||||
@@ -435,6 +451,10 @@ exports.process_hotkey = function (e, hotkey) {
|
|||||||
return exports.process_escape_key(e);
|
return exports.process_escape_key(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (drafts.drafts_overlay_open()) {
|
||||||
|
drafts.drafts_handle_events(e, event_name);
|
||||||
|
}
|
||||||
|
|
||||||
if (hotkey.message_view_only && ui_state.home_tab_obscured()) {
|
if (hotkey.message_view_only && ui_state.home_tab_obscured()) {
|
||||||
if ((event_name === 'up_arrow' || event_name === 'down_arrow') && exports.is_subs()) {
|
if ((event_name === 'up_arrow' || event_name === 'down_arrow') && exports.is_subs()) {
|
||||||
subs.switch_rows(event_name);
|
subs.switch_rows(event_name);
|
||||||
@@ -598,6 +618,9 @@ exports.process_hotkey = function (e, hotkey) {
|
|||||||
subs.new_stream_clicked();
|
subs.new_stream_clicked();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
case 'open_drafts':
|
||||||
|
drafts.toggle();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (current_msg_list.empty()) {
|
if (current_msg_list.empty()) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<div class="draft-row" data-draft-id="{{draft_id}}">
|
<div class="draft-row" data-draft-id="{{draft_id}}">
|
||||||
<div class="draft-info-box">
|
<div class="draft-info-box" tabindex="0">
|
||||||
{{#if is_stream}}
|
{{#if is_stream}}
|
||||||
<div class="message_header message_header_stream">
|
<div class="message_header message_header_stream">
|
||||||
<div class="message-header-contents">
|
<div class="message-header-contents">
|
||||||
|
|||||||
@@ -57,6 +57,8 @@ Zulip keyboard shortcuts are divided into four categories:
|
|||||||
shortcut allows the user to send the message that they've written.
|
shortcut allows the user to send the message that they've written.
|
||||||
* **Cancel compose**: `Esc` - This shortcut allows the user to cancel
|
* **Cancel compose**: `Esc` - This shortcut allows the user to cancel
|
||||||
and discard their unsent message.
|
and discard their unsent message.
|
||||||
|
* **View drafts**: `d` - This shortcut allows the user to open the
|
||||||
|
drafts overlay.
|
||||||
|
|
||||||
## Narrowing
|
## Narrowing
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,10 @@
|
|||||||
<td class="hotkey">Esc</td>
|
<td class="hotkey">Esc</td>
|
||||||
<td class="definition">{% trans %}Cancel compose{% endtrans %}</td>
|
<td class="definition">{% trans %}Cancel compose{% endtrans %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="hotkey">d</td>
|
||||||
|
<td class="definition">{% trans %}View drafts{% endtrans %}</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user