mirror of
https://github.com/zulip/zulip.git
synced 2025-11-01 20:44:04 +00:00
recent_conversations: Improve sorting behavior.
Refine sorting algorithm of recent conversation list so that it works like spreadsheet sorting, with previous sorting orders remembered. Fixes #33289.
This commit is contained in:
@@ -11,6 +11,7 @@ type SortingFunction<T> = (a: T, b: T) => number;
|
||||
|
||||
type ListWidgetMeta<Key, Item = Key> = {
|
||||
sorting_function: SortingFunction<Item> | null;
|
||||
applied_sorting_functions: [SortingFunction<Item>, boolean][]; // This is used to keep track of the sorting functions applied.
|
||||
sorting_functions: Map<string, SortingFunction<Item>>;
|
||||
filter_value: string;
|
||||
offset: number;
|
||||
@@ -280,6 +281,7 @@ export function create<Key, Item = Key>(
|
||||
|
||||
const meta: ListWidgetMeta<Key, Item> = {
|
||||
sorting_function: null,
|
||||
applied_sorting_functions: [],
|
||||
sorting_functions: new Map(),
|
||||
offset: 0,
|
||||
list,
|
||||
@@ -303,11 +305,26 @@ export function create<Key, Item = Key>(
|
||||
meta.filtered_list = get_filtered_items(meta.filter_value, meta.list, opts);
|
||||
|
||||
if (meta.sorting_function) {
|
||||
meta.filtered_list.sort(meta.sorting_function);
|
||||
// If the sorting function is already applied, remove it to avoid duplicate sorting.
|
||||
const existing_sorting_function_index = meta.applied_sorting_functions.findIndex(
|
||||
([sorting_function, _]) => sorting_function === meta.sorting_function,
|
||||
);
|
||||
if (existing_sorting_function_index !== -1) {
|
||||
meta.applied_sorting_functions.splice(existing_sorting_function_index, 1);
|
||||
}
|
||||
|
||||
if (meta.reverse_mode) {
|
||||
meta.filtered_list.reverse();
|
||||
meta.applied_sorting_functions.push([meta.sorting_function, meta.reverse_mode]);
|
||||
meta.filtered_list.sort((a, b) => {
|
||||
for (let i = meta.applied_sorting_functions.length - 1; i >= 0; i -= 1) {
|
||||
const sorting_function = meta.applied_sorting_functions[i]![0];
|
||||
const is_reverse = meta.applied_sorting_functions[i]![1];
|
||||
const result = sorting_function(a, b);
|
||||
if (result !== 0) {
|
||||
return is_reverse ? -result : result;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -527,6 +527,105 @@ run_test("sorting", () => {
|
||||
assert.ok(cleared);
|
||||
expected_html = html_for([ellen, alice, bob, cal, dave]);
|
||||
assert.deepEqual($container.$appended_data.html(), expected_html);
|
||||
});
|
||||
|
||||
run_test("Apply consecutive sorts", () => {
|
||||
const $container = make_container();
|
||||
const $scroll_container = make_scroll_container();
|
||||
const $sort_container = make_sort_container();
|
||||
|
||||
let cleared;
|
||||
$container.empty = () => {
|
||||
cleared = true;
|
||||
};
|
||||
|
||||
const alice = {name: "alice", salary: 50};
|
||||
const bob = {name: "bob", salary: 40};
|
||||
const cal = {name: "cal", salary: 30};
|
||||
const dave = {name: "dave", salary: 20};
|
||||
const ellen = {name: "ellen", salary: 95};
|
||||
const bob_2 = {name: "bob", salary: 20};
|
||||
const cal_2 = {name: "cal", salary: 60};
|
||||
|
||||
const list = [alice, bob, cal, dave, ellen, bob_2, cal_2];
|
||||
|
||||
const opts = {
|
||||
name: "sorting-list",
|
||||
$parent_container: $sort_container,
|
||||
modifier_html: (item) => div(item.name) + div(item.salary),
|
||||
get_item: (item) => item,
|
||||
filter: {
|
||||
predicate: () => true,
|
||||
},
|
||||
sort_fields: {
|
||||
...ListWidget.generic_sort_functions("alphabetic", ["name"]),
|
||||
...ListWidget.generic_sort_functions("numeric", ["salary"]),
|
||||
},
|
||||
$simplebar_container: $scroll_container,
|
||||
};
|
||||
|
||||
function html_for(people) {
|
||||
return people.map((item) => opts.modifier_html(item)).join("");
|
||||
}
|
||||
|
||||
ListWidget.create($container, list, opts);
|
||||
|
||||
let button_opts;
|
||||
let $button;
|
||||
let expected_html;
|
||||
|
||||
// Apply sorting by salary first, then by name
|
||||
button_opts = {
|
||||
sort_type: "numeric",
|
||||
prop_name: "salary",
|
||||
list_name: "my-list",
|
||||
active: false,
|
||||
};
|
||||
|
||||
$button = sort_button(button_opts);
|
||||
|
||||
$sort_container.f.apply($button);
|
||||
|
||||
button_opts = {
|
||||
sort_type: "alphabetic",
|
||||
prop_name: "name",
|
||||
list_name: "my-list",
|
||||
active: false,
|
||||
};
|
||||
|
||||
$button = sort_button(button_opts);
|
||||
|
||||
$sort_container.f.apply($button);
|
||||
|
||||
assert.ok(cleared);
|
||||
assert.ok($button.siblings_deactivated);
|
||||
|
||||
expected_html = html_for([alice, bob_2, bob, cal, cal_2, dave, ellen]);
|
||||
assert.deepEqual($container.$appended_data.html(), expected_html);
|
||||
|
||||
// Apply sorting by salary again, the previous sort by salary should be removed
|
||||
button_opts = {
|
||||
sort_type: "numeric",
|
||||
prop_name: "salary",
|
||||
list_name: "my-list",
|
||||
active: false,
|
||||
};
|
||||
|
||||
$button = sort_button(button_opts);
|
||||
|
||||
cleared = false;
|
||||
$sort_container.f.apply($button);
|
||||
assert.ok(cleared);
|
||||
expected_html = html_for([bob_2, dave, cal, bob, alice, cal_2, ellen]);
|
||||
assert.deepEqual($container.$appended_data.html(), expected_html);
|
||||
assert.ok(!$button.hasClass("descend"));
|
||||
|
||||
// Hit the salary field again to reverse the salary sorting
|
||||
cleared = false;
|
||||
$sort_container.f.apply($button);
|
||||
assert.ok(cleared);
|
||||
expected_html = html_for([ellen, cal_2, alice, bob, cal, bob_2, dave]);
|
||||
assert.deepEqual($container.$appended_data.html(), expected_html);
|
||||
assert.ok($button.hasClass("descend"));
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user