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:
Quan Nguyen
2025-03-11 13:48:31 -04:00
committed by GitHub
parent 48e8250e28
commit b4bb708c14
2 changed files with 120 additions and 4 deletions

View File

@@ -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;
});
}
},

View File

@@ -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"));
});