mirror of https://github.com/zulip/zulip.git
buddy list: Fix and simplify up/down navigation.
This introduces a generic class called list_cursor to handle the main details of navigating the buddy list and wires it into activity.js. It replaces some fairly complicated code that was coupled to stream_list and used lots of jQuery. The new code interacts with the buddy_list API instead of jQuery directly. It also persists the key across redraws, so we don't lose our place when a focus ping happens or we type more characters. Note that we no longer cycle to the top when we hit the bottom, or vice versa. Cycling can be kind of an anti-feature when you want to just lay on the arrow keys until they hit the end. The changes to stream_list.js here do not affect the left sidebar; they only remove code that was used for the right sidebar.
This commit is contained in:
parent
c63f2db25b
commit
fb712027bf
|
@ -146,6 +146,7 @@
|
|||
"user_search": false,
|
||||
"buddy_data": false,
|
||||
"buddy_list": false,
|
||||
"list_cursor": false,
|
||||
"activity": false,
|
||||
"invite": false,
|
||||
"colorspace": false,
|
||||
|
|
|
@ -470,22 +470,23 @@ casper.waitForSelector('#user_presences .highlighted_user', function () {
|
|||
|
||||
// Use arrow keys to navigate through suggestions
|
||||
casper.then(function () {
|
||||
// Down: Cordelia -> Hamlet
|
||||
casper.sendKeys('.user-list-filter', casper.page.event.key.Down, {keepFocus: true});
|
||||
// Up: Hamlet -> Cordelia
|
||||
casper.sendKeys('.user-list-filter', casper.page.event.key.Up, {keepFocus: true});
|
||||
// Up: Cordelia -> aaron
|
||||
casper.sendKeys('.user-list-filter', casper.page.event.key.Up, {keepFocus: true});
|
||||
function arrow(key) {
|
||||
casper.sendKeys('.user-list-filter',
|
||||
casper.page.event.key[key],
|
||||
{keepFocus: true});
|
||||
}
|
||||
arrow('Down'); // Cordelia -> Hamlet
|
||||
arrow('Up'); // Hamlet -> Cordelia
|
||||
arrow('Up'); // already at top
|
||||
arrow('Down'); // Cordelia -> Hamlet
|
||||
});
|
||||
|
||||
casper.waitForSelector('#user_presences li.highlighted_user [data-name="aaron"]', function () {
|
||||
casper.waitForSelector('#user_presences li.highlighted_user [data-name="King Hamlet"]', function () {
|
||||
casper.test.info('Suggestion highlighting - after arrow key navigation');
|
||||
casper.test.assertDoesntExist('#user_presences li.highlighted_user [data-name="Cordelia Lear"]',
|
||||
'User Cordelia Lear not is selected');
|
||||
casper.test.assertDoesntExist('#user_presences li.highlighted_user [data-name="King Hamlet"]',
|
||||
'User King Hamlet is not selected');
|
||||
casper.test.assertExist('#user_presences li.highlighted_user [data-name="aaron"]',
|
||||
'User aaron is selected');
|
||||
casper.test.assertExist('#user_presences li.highlighted_user [data-name="King Hamlet"]',
|
||||
'User King Hamlet is selected');
|
||||
});
|
||||
|
||||
common.then_log_out();
|
||||
|
|
|
@ -39,13 +39,24 @@ zrequire('people');
|
|||
zrequire('buddy_data');
|
||||
zrequire('buddy_list');
|
||||
zrequire('user_search');
|
||||
zrequire('list_cursor');
|
||||
zrequire('activity');
|
||||
zrequire('stream_list');
|
||||
|
||||
set_global('blueslip', {
|
||||
log: () => {},
|
||||
});
|
||||
|
||||
var filter_key_handlers;
|
||||
set_global('keydown_util', {
|
||||
handle: (opts) => {
|
||||
filter_key_handlers = opts.handlers;
|
||||
},
|
||||
});
|
||||
|
||||
set_global('stream_list', {
|
||||
scroll_element_into_container: () => {},
|
||||
});
|
||||
|
||||
set_global('popovers', {
|
||||
hide_all: function () {},
|
||||
show_userlist_sidebar: function () {
|
||||
|
@ -273,33 +284,17 @@ presence.presence_info[norbert.user_id] = { status: activity.ACTIVE };
|
|||
presence.presence_info[zoe.user_id] = { status: activity.ACTIVE };
|
||||
presence.presence_info[me.user_id] = { status: activity.ACTIVE };
|
||||
|
||||
const user_order = [fred.user_id, jill.user_id, norbert.user_id,
|
||||
zoe.user_id, alice.user_id, mark.user_id];
|
||||
const user_count = 6;
|
||||
|
||||
function reset_jquery() {
|
||||
function reset_setup() {
|
||||
set_global('$', global.make_zjquery());
|
||||
activity.set_user_list_filter();
|
||||
|
||||
// Mock the jquery is func
|
||||
$('.user-list-filter').is = function (sel) {
|
||||
if (sel === ':focus') {
|
||||
return $('.user-list-filter').is_focused();
|
||||
}
|
||||
};
|
||||
|
||||
// Mock the jquery first func
|
||||
$('#user_presences li.user_sidebar_entry.narrow-filter').first = function () {
|
||||
return $('li.user_sidebar_entry[data-user-id="' + user_order[0] + '"]');
|
||||
};
|
||||
$('#user_presences li.user_sidebar_entry.narrow-filter').last = function () {
|
||||
return $('li.user_sidebar_entry[data-user-id="' + user_order[user_count - 1] + '"]');
|
||||
};
|
||||
activity.set_cursor_and_filter();
|
||||
|
||||
buddy_list.container = $('#user_presences');
|
||||
const stub = $.create('first elem stub');
|
||||
stub.first = () => [];
|
||||
buddy_list.container.set_find_results('li.user_sidebar_entry', stub);
|
||||
}
|
||||
|
||||
reset_jquery();
|
||||
reset_setup();
|
||||
|
||||
(function test_presence_list_full_update() {
|
||||
$('.user-list-filter').focus();
|
||||
|
@ -355,15 +350,38 @@ reset_jquery();
|
|||
]);
|
||||
}());
|
||||
|
||||
function simulate_right_column_buddy_list() {
|
||||
$('.user-list-filter').closest = function (selector) {
|
||||
assert.equal(selector, ".app-main [class^='column-']");
|
||||
return $.create('right-sidebar').addClass('column-right');
|
||||
};
|
||||
}
|
||||
|
||||
function simulate_left_column_buddy_list() {
|
||||
$('.user-list-filter').closest = function (selector) {
|
||||
assert.equal(selector, ".app-main [class^='column-']");
|
||||
return $.create('left-sidebar').addClass('column-left');
|
||||
};
|
||||
}
|
||||
|
||||
function simulate_list_items(items) {
|
||||
const list = {
|
||||
length: items.length,
|
||||
eq: (i) => items[i],
|
||||
first: () => items[0] || {length: 0},
|
||||
};
|
||||
$('#user_presences').set_find_results('li.user_sidebar_entry', list);
|
||||
|
||||
_.each(items, (item, i) => {
|
||||
item.next = () => items[i+1] || {length: 0};
|
||||
item.prev = () => items[i-1] || {length: 0};
|
||||
});
|
||||
}
|
||||
|
||||
function buddy_list_add(user_id, stub) {
|
||||
if (stub.attr) {
|
||||
stub.attr('data-user-id', user_id);
|
||||
}
|
||||
const sel = `li.user_sidebar_entry[data-user-id='${user_id}']`;
|
||||
$('#user_presences').set_find_results(sel, stub);
|
||||
}
|
||||
|
@ -418,108 +436,96 @@ function buddy_list_add(user_id, stub) {
|
|||
assert.equal(value.text(), '');
|
||||
}());
|
||||
|
||||
reset_jquery();
|
||||
reset_setup();
|
||||
|
||||
(function test_key_input() {
|
||||
let sel_index = 0;
|
||||
// Returns which element is selected
|
||||
$('#user_presences li.user_sidebar_entry.narrow-filter.highlighted_user')
|
||||
.expectOne().attr = function () {
|
||||
return user_order[sel_index];
|
||||
(function test_handlers() {
|
||||
// This is kind of weak coverage; we are mostly making sure that
|
||||
// keys and clicks got mapped to functions that don't crash.
|
||||
|
||||
const alice_li = $.create('alice stub');
|
||||
const fred_li = $.create('fred stub');
|
||||
|
||||
(function test_click_filter() {
|
||||
const e = {
|
||||
stopPropagation: () => {},
|
||||
};
|
||||
|
||||
// Returns element before selected one
|
||||
$('#user_presences li.user_sidebar_entry.narrow-filter.highlighted_user')
|
||||
.expectOne().prev = function () {
|
||||
if (sel_index === 0) {
|
||||
// Top, no prev element
|
||||
return $('div.no_user');
|
||||
}
|
||||
return $('li.user_sidebar_entry[data-user-id="' + user_order[sel_index-1] + '"]');
|
||||
simulate_list_items([alice_li, fred_li]);
|
||||
const handler = $('.user-list-filter').get_on_handler('focus');
|
||||
handler(e);
|
||||
|
||||
simulate_list_items([]);
|
||||
handler(e);
|
||||
}());
|
||||
|
||||
(function test_click_header_filter() {
|
||||
const e = {};
|
||||
const handler = $('#userlist-header').get_on_handler('click');
|
||||
|
||||
simulate_right_column_buddy_list();
|
||||
|
||||
handler(e);
|
||||
// and click again
|
||||
handler(e);
|
||||
}());
|
||||
|
||||
(function test_filter_keys() {
|
||||
simulate_list_items([alice_li, fred_li]);
|
||||
buddy_list_add(alice.user_id, alice_li);
|
||||
buddy_list_add(fred.user_id, fred_li);
|
||||
|
||||
activity.user_cursor.go_to(alice.user_id);
|
||||
filter_key_handlers.down_arrow();
|
||||
filter_key_handlers.up_arrow();
|
||||
filter_key_handlers.up_arrow();
|
||||
filter_key_handlers.down_arrow();
|
||||
filter_key_handlers.down_arrow();
|
||||
|
||||
simulate_list_items([]);
|
||||
filter_key_handlers.down_arrow();
|
||||
filter_key_handlers.up_arrow();
|
||||
}());
|
||||
|
||||
(function test_enter_key() {
|
||||
var narrowed;
|
||||
|
||||
narrow.by = (method, email) => {
|
||||
assert.equal(email, 'alice@zulip.com');
|
||||
narrowed = true;
|
||||
};
|
||||
|
||||
// Returns element after selected one
|
||||
$('#user_presences li.user_sidebar_entry.narrow-filter.highlighted_user')
|
||||
.expectOne().next = function () {
|
||||
if (sel_index === user_count - 1) {
|
||||
// Bottom, no next element
|
||||
return $('div.no_user');
|
||||
}
|
||||
return $('li.user_sidebar_entry[data-user-id="' + user_order[sel_index + 1] + '"]');
|
||||
$('.user-list-filter').val('al');
|
||||
buddy_list_add(alice.user_id, alice_li);
|
||||
activity.user_cursor.go_to(alice.user_id);
|
||||
|
||||
filter_key_handlers.enter_key();
|
||||
assert(narrowed);
|
||||
|
||||
// get line coverage for cleared case
|
||||
activity.user_cursor.clear();
|
||||
filter_key_handlers.enter_key();
|
||||
}());
|
||||
|
||||
(function test_click_handler() {
|
||||
// We wire up the click handler in click_handlers.js,
|
||||
// so this just tests the called function.
|
||||
var narrowed;
|
||||
|
||||
narrow.by = (method, email) => {
|
||||
assert.equal(email, 'alice@zulip.com');
|
||||
narrowed = true;
|
||||
};
|
||||
|
||||
$('li.user_sidebar_entry[data-user-id="' + fred.user_id + '"]').is = function () {
|
||||
return true;
|
||||
};
|
||||
$('li.user_sidebar_entry[data-user-id="' + mark.user_id + '"]').is = function () {
|
||||
return true;
|
||||
};
|
||||
$('li.user_sidebar_entry[data-user-id="' + alice.user_id + '"]').is = function () {
|
||||
return true;
|
||||
};
|
||||
$('div.no_user').is = function () {
|
||||
return false;
|
||||
};
|
||||
buddy_list_add(alice.user_id, alice_li);
|
||||
activity.narrow_for_user({li: alice_li});
|
||||
assert(narrowed);
|
||||
}());
|
||||
|
||||
$('#user_presences li.user_sidebar_entry.narrow-filter').length = user_count;
|
||||
|
||||
// Disable scrolling into place
|
||||
stream_list.scroll_element_into_container = function () {};
|
||||
// up
|
||||
const e = {
|
||||
keyCode: 38,
|
||||
stopPropagation: function () {},
|
||||
preventDefault: function () {},
|
||||
};
|
||||
const keydown_handler = $('.user-list-filter').get_on_handler('keydown');
|
||||
keydown_handler(e);
|
||||
// Now the last element is selected
|
||||
sel_index = user_count - 1;
|
||||
keydown_handler(e);
|
||||
sel_index = sel_index - 1;
|
||||
|
||||
// down
|
||||
e.keyCode = 40;
|
||||
keydown_handler(e);
|
||||
sel_index = sel_index + 1;
|
||||
keydown_handler(e);
|
||||
|
||||
e.keyCode = 13;
|
||||
|
||||
// Enter text and narrow users
|
||||
$(".user-list-filter").expectOne().val('ali');
|
||||
narrow.by = function (method, email) {
|
||||
assert.equal(email, 'alice@zulip.com');
|
||||
};
|
||||
compose_actions.start = function () {};
|
||||
sel_index = 4;
|
||||
|
||||
keydown_handler(e);
|
||||
}());
|
||||
|
||||
(function test_focus_user_filter() {
|
||||
$('#user_presences li.user_sidebar_entry.narrow-filter.highlighted_user').length = 0;
|
||||
|
||||
var first_highlighted;
|
||||
|
||||
stream_list.highlight_first = () => {
|
||||
first_highlighted = true;
|
||||
};
|
||||
|
||||
const e = {
|
||||
stopPropagation: () => {},
|
||||
};
|
||||
|
||||
const handler = $('.user-list-filter').get_on_handler('click');
|
||||
handler(e);
|
||||
|
||||
assert(first_highlighted);
|
||||
}());
|
||||
|
||||
(function test_focusout_user_filter() {
|
||||
const e = { };
|
||||
const handler = $('.user-list-filter').get_on_handler('blur');
|
||||
handler(e);
|
||||
(function test_blur_filter() {
|
||||
const e = {};
|
||||
const handler = $('.user-list-filter').get_on_handler('blur');
|
||||
handler(e);
|
||||
}());
|
||||
}());
|
||||
|
||||
presence.presence_info = {};
|
||||
|
@ -530,12 +536,12 @@ presence.presence_info[mark.user_id] = { status: activity.IDLE };
|
|||
presence.presence_info[norbert.user_id] = { status: activity.ACTIVE };
|
||||
presence.presence_info[zoe.user_id] = { status: activity.ACTIVE };
|
||||
|
||||
reset_setup();
|
||||
|
||||
(function test_filter_user_ids() {
|
||||
const user_filter = $('.user-list-filter');
|
||||
user_filter.val(''); // no search filter
|
||||
|
||||
activity.set_user_list_filter();
|
||||
|
||||
function get_user_ids() {
|
||||
var filter_text = activity.get_filter_text();
|
||||
var user_ids = buddy_data.get_filtered_and_sorted_user_ids(filter_text);
|
||||
|
@ -603,7 +609,7 @@ presence.presence_info[zoe.user_id] = { status: activity.ACTIVE };
|
|||
assert(removed);
|
||||
}());
|
||||
|
||||
reset_jquery();
|
||||
reset_setup();
|
||||
|
||||
(function test_insert_fred_after_alice() {
|
||||
const alice_li = $.create('alice list item');
|
||||
|
@ -634,7 +640,7 @@ reset_jquery();
|
|||
assert(removed);
|
||||
}());
|
||||
|
||||
reset_jquery();
|
||||
reset_setup();
|
||||
|
||||
(function test_insert_fred_before_jill() {
|
||||
const fred_li = $.create('fred-li');
|
||||
|
@ -666,7 +672,7 @@ reset_jquery();
|
|||
}());
|
||||
|
||||
// Reset jquery here.
|
||||
reset_jquery();
|
||||
reset_setup();
|
||||
|
||||
(function test_insert_unfiltered_user_with_filter() {
|
||||
// This test only tests that we do not explode when
|
||||
|
@ -714,19 +720,21 @@ $('.user-list-filter').parent = function () {
|
|||
assert($('#user-list .input-append').hasClass('notdisplayed'));
|
||||
}());
|
||||
|
||||
reset_setup();
|
||||
|
||||
(function () {
|
||||
const alice_li = $.create('alice stub');
|
||||
simulate_list_items([alice_li]);
|
||||
}());
|
||||
|
||||
(function test_initiate_search() {
|
||||
$('.user-list-filter').blur();
|
||||
$('.user-list-filter').closest = function (selector) {
|
||||
assert.equal(selector, ".app-main [class^='column-']");
|
||||
return $.create('right-sidebar').addClass('column-right');
|
||||
};
|
||||
simulate_right_column_buddy_list();
|
||||
activity.initiate_search();
|
||||
assert.equal($('.column-right').hasClass('expanded'), true);
|
||||
assert.equal($('.user-list-filter').is_focused(), true);
|
||||
$('.user-list-filter').closest = function (selector) {
|
||||
assert.equal(selector, ".app-main [class^='column-']");
|
||||
return $.create('left-sidebar').addClass('column-left');
|
||||
};
|
||||
|
||||
simulate_left_column_buddy_list();
|
||||
activity.initiate_search();
|
||||
assert.equal($('.column-left').hasClass('expanded'), true);
|
||||
assert.equal($('.user-list-filter').is_focused(), true);
|
||||
|
@ -775,7 +783,7 @@ $('.user-list-filter').parent = function () {
|
|||
activity.update_huddles = function () {};
|
||||
}());
|
||||
|
||||
reset_jquery();
|
||||
reset_setup();
|
||||
|
||||
(function test_set_user_status() {
|
||||
const server_time = 500;
|
||||
|
|
|
@ -224,6 +224,9 @@ exports.make_new_elem = function (selector, opts) {
|
|||
if (arg === ':visible') {
|
||||
return shown;
|
||||
}
|
||||
if (arg === ':focus') {
|
||||
return focused;
|
||||
}
|
||||
return self;
|
||||
},
|
||||
is_focused: function () {
|
||||
|
|
|
@ -224,12 +224,6 @@ exports.insert_user_into_list = function (user_id) {
|
|||
compose_fade.update_one_user_row(elt);
|
||||
};
|
||||
|
||||
exports.clear_highlight = function () {
|
||||
// Undo highlighting
|
||||
var item = $('#user_presences li.user_sidebar_entry.highlighted_user');
|
||||
item.removeClass('highlighted_user');
|
||||
};
|
||||
|
||||
exports.searching = function () {
|
||||
return exports.user_filter && exports.user_filter.searching();
|
||||
};
|
||||
|
@ -252,16 +246,15 @@ exports.build_user_sidebar = function () {
|
|||
|
||||
resize.resize_page_components();
|
||||
|
||||
// Highlight top user when searching
|
||||
$('#user_presences li.user_sidebar_entry.highlighted_user').removeClass('highlighted_user');
|
||||
if (exports.searching()) {
|
||||
var all_streams = $('#user_presences li.user_sidebar_entry.narrow-filter');
|
||||
stream_list.highlight_first(all_streams, 'highlighted_user');
|
||||
}
|
||||
return user_info; // for testing
|
||||
};
|
||||
|
||||
var update_users_for_search = _.throttle(exports.build_user_sidebar, 50);
|
||||
function do_update_users_for_search() {
|
||||
exports.build_user_sidebar();
|
||||
exports.user_cursor.reset();
|
||||
}
|
||||
|
||||
var update_users_for_search = _.throttle(do_update_users_for_search, 50);
|
||||
|
||||
function show_huddles() {
|
||||
$('#group-pm-list').addClass("show");
|
||||
|
@ -342,8 +335,7 @@ function focus_ping(want_redraw) {
|
|||
|
||||
if (want_redraw) {
|
||||
presence.set_info(data.presences, data.server_timestamp);
|
||||
exports.build_user_sidebar();
|
||||
exports.update_huddles();
|
||||
exports.redraw();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
@ -371,7 +363,7 @@ exports.initialize = function () {
|
|||
page_params.initial_servertime);
|
||||
delete page_params.presences;
|
||||
|
||||
exports.set_user_list_filter();
|
||||
exports.set_cursor_and_filter();
|
||||
|
||||
exports.build_user_sidebar();
|
||||
exports.update_huddles();
|
||||
|
@ -408,59 +400,72 @@ exports.set_user_status = function (email, info, server_time) {
|
|||
|
||||
exports.redraw = function () {
|
||||
exports.build_user_sidebar();
|
||||
exports.user_cursor.redraw();
|
||||
exports.update_huddles();
|
||||
};
|
||||
|
||||
exports.highlight_first_user = function () {
|
||||
if ($('#user_presences li.user_sidebar_entry.narrow-filter.highlighted_user').length === 0) {
|
||||
// Highlight
|
||||
var all_streams = $('#user_presences li.user_sidebar_entry.narrow-filter');
|
||||
stream_list.highlight_first(all_streams, 'highlighted_user');
|
||||
}
|
||||
exports.reset_users = function () {
|
||||
// Call this when we're leaving the search widget.
|
||||
exports.build_user_sidebar();
|
||||
exports.user_cursor.clear();
|
||||
};
|
||||
|
||||
exports.narrow_for_user = function (opts) {
|
||||
var user_id = buddy_list.get_key_from_li({li: opts.li});
|
||||
var email = people.get_person_from_user_id(user_id).email;
|
||||
return exports.narrow_for_user_id({user_id: user_id});
|
||||
};
|
||||
|
||||
exports.narrow_for_user_id = function (opts) {
|
||||
var person = people.get_person_from_user_id(opts.user_id);
|
||||
var email = person.email;
|
||||
|
||||
narrow.by('pm-with', email, {trigger: 'sidebar'});
|
||||
exports.user_filter.clear_and_hide_search();
|
||||
};
|
||||
|
||||
function keydown_enter_key() {
|
||||
// Is there at least one user?
|
||||
if ($('#user_presences li.user_sidebar_entry.narrow-filter').length > 0) {
|
||||
// There must be a 'highlighted_user' user
|
||||
var li = $('#user_presences li.user_sidebar_entry.narrow-filter.highlighted_user');
|
||||
exports.narrow_for_user({li: li});
|
||||
popovers.hide_all();
|
||||
var user_id = exports.user_cursor.get_key();
|
||||
if (user_id === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
exports.narrow_for_user_id({user_id: user_id});
|
||||
popovers.hide_all();
|
||||
}
|
||||
|
||||
function keydown_user_filter(e) {
|
||||
stream_list.keydown_filter(e, '#user_presences li.user_sidebar_entry.narrow-filter',
|
||||
$('#user_presences'), 'highlighted_user', keydown_enter_key);
|
||||
}
|
||||
|
||||
function focusout_user_filter() {
|
||||
// Undo highlighting
|
||||
$('#user_presences li.user_sidebar_entry.highlighted_user').removeClass('highlighted_user');
|
||||
}
|
||||
|
||||
exports.set_user_list_filter = function () {
|
||||
exports.user_filter = user_search({
|
||||
update_list: update_users_for_search,
|
||||
reset_items: exports.clear_highlight,
|
||||
initialize_list_for_search: exports.highlight_first_user,
|
||||
exports.set_cursor_and_filter = function () {
|
||||
exports.user_cursor = list_cursor({
|
||||
list: buddy_list,
|
||||
highlight_class: 'highlighted_user',
|
||||
});
|
||||
|
||||
exports.set_user_list_filter_handlers();
|
||||
};
|
||||
exports.user_filter = user_search({
|
||||
update_list: update_users_for_search,
|
||||
reset_items: exports.reset_users,
|
||||
on_focus: exports.user_cursor.reset,
|
||||
});
|
||||
|
||||
exports.set_user_list_filter_handlers = function () {
|
||||
exports.user_filter.input_field()
|
||||
.on('keydown', keydown_user_filter)
|
||||
.on('blur', focusout_user_filter);
|
||||
var $input = exports.user_filter.input_field();
|
||||
|
||||
$input.on('blur', exports.user_cursor.clear);
|
||||
|
||||
keydown_util.handle({
|
||||
elem: $input,
|
||||
handlers: {
|
||||
enter_key: function () {
|
||||
keydown_enter_key();
|
||||
return true;
|
||||
},
|
||||
up_arrow: function () {
|
||||
exports.user_cursor.prev();
|
||||
return true;
|
||||
},
|
||||
down_arrow: function () {
|
||||
exports.user_cursor.next();
|
||||
return true;
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
exports.initiate_search = function () {
|
||||
|
|
|
@ -35,6 +35,36 @@ var buddy_list = (function () {
|
|||
self.container.html(html);
|
||||
};
|
||||
|
||||
self.first_key = function () {
|
||||
var list_items = self.container.find(self.item_sel);
|
||||
var li = list_items.first();
|
||||
if (li.length === 0) {
|
||||
return;
|
||||
}
|
||||
var key = self.get_key_from_li({li: li});
|
||||
return key;
|
||||
};
|
||||
|
||||
self.prev_key = function (key) {
|
||||
var li = self.find_li({key: key});
|
||||
var prev_li = li.prev();
|
||||
if (prev_li.length === 0) {
|
||||
return;
|
||||
}
|
||||
var prev_key = self.get_key_from_li({li: prev_li});
|
||||
return prev_key;
|
||||
};
|
||||
|
||||
self.next_key = function (key) {
|
||||
var li = self.find_li({key: key});
|
||||
var next_li = li.next();
|
||||
if (next_li.length === 0) {
|
||||
return;
|
||||
}
|
||||
var next_key = self.get_key_from_li({li: next_li});
|
||||
return next_key;
|
||||
};
|
||||
|
||||
self.maybe_remove_key = function (opts) {
|
||||
var li = self.find_li({key: opts.key});
|
||||
li.remove();
|
||||
|
|
|
@ -7,6 +7,7 @@ var exports = {};
|
|||
*/
|
||||
|
||||
var keys = {
|
||||
13: 'enter_key',
|
||||
37: 'left_arrow',
|
||||
38: 'up_arrow',
|
||||
39: 'right_arrow',
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
var list_cursor = function (opts) {
|
||||
var self = {};
|
||||
|
||||
var config_ok = (
|
||||
opts.highlight_class &&
|
||||
opts.list &&
|
||||
opts.list.container &&
|
||||
opts.list.find_li &&
|
||||
opts.list.first_key &&
|
||||
opts.list.prev_key &&
|
||||
opts.list.next_key
|
||||
);
|
||||
|
||||
if (!config_ok) {
|
||||
blueslip.error('Programming error');
|
||||
return;
|
||||
}
|
||||
|
||||
self.clear = function () {
|
||||
if (self.curr_key === undefined) {
|
||||
return;
|
||||
}
|
||||
var row = self.get_row(self.curr_key);
|
||||
if (row) {
|
||||
row.clear();
|
||||
}
|
||||
self.curr_key = undefined;
|
||||
};
|
||||
|
||||
self.get_key = function () {
|
||||
return self.curr_key;
|
||||
};
|
||||
|
||||
self.get_row = function (key) {
|
||||
// TODO: The list class should probably do more of the work
|
||||
// here, so we're not so coupled to jQuery, and
|
||||
// so we instead just get back a widget we can say
|
||||
// something like widget.select() on. This will
|
||||
// be especially important if we do lazy rendering.
|
||||
// It would also give the caller more flexibility on
|
||||
// the actual styling.
|
||||
if (key === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
var li = opts.list.find_li({key: key});
|
||||
|
||||
if (li.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
highlight: function () {
|
||||
li.addClass(opts.highlight_class);
|
||||
self.adjust_scroll(li);
|
||||
},
|
||||
clear: function () {
|
||||
li.removeClass(opts.highlight_class);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
self.adjust_scroll = function (li) {
|
||||
// TODO: move scroll_element_into_container out of
|
||||
// stream_list.js
|
||||
stream_list.scroll_element_into_container(li, opts.list.container);
|
||||
};
|
||||
|
||||
self.redraw = function () {
|
||||
// We should only call this for situations like the buddy
|
||||
// list where we redraw the whole list without necessarily
|
||||
// changing it, so we just want to re-highlight the current
|
||||
// row in the new DOM. If you are filtering, for now you
|
||||
// should call the 'reset()' method.
|
||||
var row = self.get_row(self.curr_key);
|
||||
|
||||
if (row === undefined) {
|
||||
return;
|
||||
}
|
||||
row.highlight();
|
||||
};
|
||||
|
||||
self.go_to = function (key) {
|
||||
if (key === self.curr_key) {
|
||||
return;
|
||||
}
|
||||
if (key === undefined) {
|
||||
blueslip.error('Caller is not checking keys for list_cursor.go_to');
|
||||
return;
|
||||
}
|
||||
self.clear();
|
||||
self.curr_key = key;
|
||||
var row = self.get_row(key);
|
||||
if (row === undefined) {
|
||||
blueslip.error('Cannot highlight key for list_cursor: ' + key);
|
||||
return;
|
||||
}
|
||||
row.highlight();
|
||||
};
|
||||
|
||||
self.reset = function () {
|
||||
self.clear();
|
||||
var key = opts.list.first_key();
|
||||
if (key === undefined) {
|
||||
self.curr_key = undefined;
|
||||
return;
|
||||
}
|
||||
self.go_to(key);
|
||||
};
|
||||
|
||||
self.prev = function () {
|
||||
if (self.curr_key === undefined) {
|
||||
return;
|
||||
}
|
||||
var key = opts.list.prev_key(self.curr_key);
|
||||
if (key === undefined) {
|
||||
// leave the current key
|
||||
return;
|
||||
}
|
||||
self.go_to(key);
|
||||
};
|
||||
|
||||
self.next = function () {
|
||||
if (self.curr_key === undefined) {
|
||||
// This is sort of a special case where we went from
|
||||
// an empty filter to having data.
|
||||
self.reset();
|
||||
return;
|
||||
}
|
||||
var key = opts.list.next_key(self.curr_key);
|
||||
if (key === undefined) {
|
||||
// leave the current key
|
||||
return;
|
||||
}
|
||||
self.go_to(key);
|
||||
};
|
||||
|
||||
return self;
|
||||
};
|
||||
if (typeof module !== 'undefined') {
|
||||
module.exports = list_cursor;
|
||||
}
|
|
@ -306,7 +306,7 @@ exports.update_streams_sidebar = function () {
|
|||
$('#stream_filters li.highlighted_stream').removeClass('highlighted_stream');
|
||||
if (exports.searching()) {
|
||||
var all_streams = $('#stream_filters li.narrow-filter');
|
||||
exports.highlight_first(all_streams, 'highlighted_stream');
|
||||
exports.highlight_first(all_streams);
|
||||
}
|
||||
|
||||
if (! narrow_state.active()) {
|
||||
|
@ -452,7 +452,7 @@ function focus_stream_filter(e) {
|
|||
if ($('#stream_filters li.narrow-filter.highlighted_stream').length === 0) {
|
||||
// Highlight
|
||||
var all_streams = $('#stream_filters li.narrow-filter');
|
||||
exports.highlight_first(all_streams, 'highlighted_stream');
|
||||
exports.highlight_first(all_streams);
|
||||
}
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
@ -480,7 +480,7 @@ function keydown_enter_key() {
|
|||
|
||||
function keydown_stream_filter(e) {
|
||||
exports.keydown_filter(e, '#stream_filters li.narrow-filter',
|
||||
$('#stream-filters-container'), 'highlighted_stream', keydown_enter_key);
|
||||
$('#stream-filters-container'), keydown_enter_key);
|
||||
}
|
||||
|
||||
function actually_update_streams_for_search() {
|
||||
|
@ -575,7 +575,7 @@ exports.initiate_search = function () {
|
|||
|
||||
// Highlight first result
|
||||
var all_streams = $('#stream_filters li.narrow-filter');
|
||||
exports.highlight_first(all_streams, 'highlighted_stream');
|
||||
exports.highlight_first(all_streams);
|
||||
};
|
||||
|
||||
exports.clear_and_hide_search = function () {
|
||||
|
@ -595,16 +595,13 @@ function next_sibing_in_dir(elm, dir_up) {
|
|||
return elm.next();
|
||||
}
|
||||
|
||||
function keydown_arrow_key(dir_up, all_streams_selector,
|
||||
scroll_container, highlighting_class) {
|
||||
function keydown_arrow_key(dir_up, all_streams_selector, scroll_container) {
|
||||
// Are there streams to cyle through?
|
||||
if ($(all_streams_selector).length > 0) {
|
||||
var current_sel = $(all_streams_selector + '.' + highlighting_class).expectOne();
|
||||
var current_sel = $(all_streams_selector + '.highlighted_stream').expectOne();
|
||||
var next_sibling = next_sibing_in_dir(current_sel, dir_up);
|
||||
|
||||
if (highlighting_class === 'highlighted_stream'
|
||||
&& next_sibling.is('hr.stream-split')) {
|
||||
// Only for the left sidebar
|
||||
if (next_sibling.is('hr.stream-split')) {
|
||||
// Skip separator
|
||||
next_sibling = next_sibing_in_dir(next_sibling, dir_up);
|
||||
}
|
||||
|
@ -622,20 +619,15 @@ function keydown_arrow_key(dir_up, all_streams_selector,
|
|||
}
|
||||
|
||||
// Classes must be explicitly named
|
||||
if (highlighting_class === 'highlighted_stream') {
|
||||
current_sel.removeClass('highlighted_stream');
|
||||
next_sibling.addClass('highlighted_stream');
|
||||
} else if (highlighting_class === 'highlighted_user') {
|
||||
current_sel.removeClass('highlighted_user');
|
||||
next_sibling.addClass('highlighted_user');
|
||||
}
|
||||
current_sel.removeClass('highlighted_stream');
|
||||
next_sibling.addClass('highlighted_stream');
|
||||
|
||||
exports.scroll_element_into_container(next_sibling, scroll_container);
|
||||
}
|
||||
}
|
||||
|
||||
exports.keydown_filter = function (e, all_streams_selector, scroll_container,
|
||||
highlighting_class, enter_press_function) {
|
||||
enter_press_function) {
|
||||
// Function for left and right sidebar
|
||||
// Could be placed somewhere else but ui.js is already very full
|
||||
|
||||
|
@ -651,15 +643,13 @@ exports.keydown_filter = function (e, all_streams_selector, scroll_container,
|
|||
}
|
||||
case 38: {
|
||||
// Up-arrow key was pressed
|
||||
keydown_arrow_key(true, all_streams_selector,
|
||||
scroll_container, highlighting_class);
|
||||
keydown_arrow_key(true, all_streams_selector, scroll_container);
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
case 40: {
|
||||
// Down-arrow key was pressed
|
||||
keydown_arrow_key(false, all_streams_selector,
|
||||
scroll_container, highlighting_class);
|
||||
keydown_arrow_key(false, all_streams_selector, scroll_container);
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
|
@ -675,14 +665,10 @@ exports.keydown_filter = function (e, all_streams_selector, scroll_container,
|
|||
}
|
||||
};
|
||||
|
||||
exports.highlight_first = function (all_streams, highlighting_class) {
|
||||
exports.highlight_first = function (all_streams) {
|
||||
if (all_streams.length > 0) {
|
||||
// Classes must be explicitly named
|
||||
if (highlighting_class === 'highlighted_stream') {
|
||||
all_streams.first().addClass('highlighted_stream');
|
||||
} else if (highlighting_class === 'highlighted_user') {
|
||||
all_streams.first().addClass('highlighted_user');
|
||||
}
|
||||
all_streams.first().addClass('highlighted_stream');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ var user_search = function (opts) {
|
|||
|
||||
$input.val('');
|
||||
$input.blur();
|
||||
opts.update_list();
|
||||
opts.reset_items();
|
||||
};
|
||||
|
||||
self.escape_search = function () {
|
||||
|
@ -87,7 +87,6 @@ var user_search = function (opts) {
|
|||
self.expand_column();
|
||||
self.show_widget();
|
||||
$input.focus();
|
||||
opts.initialize_list_for_search();
|
||||
};
|
||||
|
||||
self.toggle_filter_displayed = function () {
|
||||
|
@ -99,7 +98,7 @@ var user_search = function (opts) {
|
|||
};
|
||||
|
||||
function on_focus(e) {
|
||||
opts.initialize_list_for_search();
|
||||
opts.on_focus();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
|
@ -107,7 +106,7 @@ var user_search = function (opts) {
|
|||
$('#userlist-header').on('click', self.toggle_filter_displayed);
|
||||
|
||||
$input.on('input', opts.update_list);
|
||||
$input.on('click', on_focus);
|
||||
$input.on('focus', on_focus);
|
||||
|
||||
return self;
|
||||
};
|
||||
|
|
|
@ -1138,6 +1138,7 @@ JS_SPECS = {
|
|||
'js/user_search.js',
|
||||
'js/buddy_data.js',
|
||||
'js/buddy_list.js',
|
||||
'js/list_cursor.js',
|
||||
'js/activity.js',
|
||||
'js/user_events.js',
|
||||
'js/colorspace.js',
|
||||
|
|
Loading…
Reference in New Issue