mirror of
https://github.com/zulip/zulip.git
synced 2025-11-05 22:43:42 +00:00
Earlier, 'THIS CONVERSATION' section was missing in the right sidebar when narrowed to empty string topic. This commit fixes the bug.
315 lines
9.4 KiB
TypeScript
315 lines
9.4 KiB
TypeScript
import $ from "jquery";
|
|
import _ from "lodash";
|
|
import assert from "minimalistic-assert";
|
|
|
|
import render_empty_list_widget_for_list from "../templates/empty_list_widget_for_list.hbs";
|
|
|
|
import * as activity from "./activity.ts";
|
|
import * as blueslip from "./blueslip.ts";
|
|
import * as buddy_data from "./buddy_data.ts";
|
|
import {buddy_list} from "./buddy_list.ts";
|
|
import * as keydown_util from "./keydown_util.ts";
|
|
import {ListCursor} from "./list_cursor.ts";
|
|
import * as narrow_state from "./narrow_state.ts";
|
|
import * as people from "./people.ts";
|
|
import * as pm_list from "./pm_list.ts";
|
|
import * as popovers from "./popovers.ts";
|
|
import * as presence from "./presence.ts";
|
|
import type {PresenceInfoFromEvent} from "./presence.ts";
|
|
import * as sidebar_ui from "./sidebar_ui.ts";
|
|
import {realm} from "./state_data.ts";
|
|
import * as ui_util from "./ui_util.ts";
|
|
import type {FullUnreadCountsData} from "./unread.ts";
|
|
import {UserSearch} from "./user_search.ts";
|
|
import * as util from "./util.ts";
|
|
|
|
export let user_cursor: ListCursor<number> | undefined;
|
|
export let user_filter: UserSearch | undefined;
|
|
|
|
// Function initialized from `ui_init` to avoid importing narrow.js and causing circular imports.
|
|
let narrow_by_email: (email: string) => void;
|
|
|
|
function get_pm_list_item(user_id: string): JQuery | undefined {
|
|
return buddy_list.find_li({
|
|
key: Number.parseInt(user_id, 10),
|
|
});
|
|
}
|
|
|
|
function set_pm_count(user_ids_string: string, count: number): void {
|
|
const $pm_li = get_pm_list_item(user_ids_string);
|
|
if ($pm_li !== undefined) {
|
|
ui_util.update_unread_count_in_dom($pm_li, count);
|
|
}
|
|
}
|
|
|
|
export function update_dom_with_unread_counts(counts: FullUnreadCountsData): void {
|
|
// counts is just a data object that gets calculated elsewhere
|
|
// Our job is to update some DOM elements.
|
|
|
|
for (const [user_ids_string, count] of counts.pm_count) {
|
|
// TODO: just use user_ids_string in our markup
|
|
const is_pm = !user_ids_string.includes(",");
|
|
if (is_pm) {
|
|
set_pm_count(user_ids_string, count);
|
|
}
|
|
}
|
|
}
|
|
|
|
export function clear_for_testing(): void {
|
|
user_cursor = undefined;
|
|
user_filter = undefined;
|
|
}
|
|
|
|
export let update_presence_indicators = (): void => {
|
|
$("[data-presence-indicator-user-id]").each(function () {
|
|
const user_id = Number.parseInt($(this).attr("data-presence-indicator-user-id") ?? "", 10);
|
|
const is_deactivated = !people.is_active_user_for_popover(user_id || 0);
|
|
assert(!Number.isNaN(user_id));
|
|
const user_circle_class = buddy_data.get_user_circle_class(user_id, is_deactivated);
|
|
const user_circle_class_with_icon = `${user_circle_class} zulip-icon-${user_circle_class}`;
|
|
$(this)
|
|
.removeClass(
|
|
`
|
|
user-circle-active zulip-icon-user-circle-active
|
|
user-circle-idle zulip-icon-user-circle-idle
|
|
user-circle-offline zulip-icon-user-circle-offline
|
|
`,
|
|
)
|
|
.addClass(user_circle_class_with_icon);
|
|
});
|
|
};
|
|
|
|
export function rewire_update_presence_indicators(value: typeof update_presence_indicators): void {
|
|
update_presence_indicators = value;
|
|
}
|
|
|
|
export function redraw_user(user_id: number): void {
|
|
if (realm.realm_presence_disabled) {
|
|
return;
|
|
}
|
|
|
|
const filter_text = get_filter_text();
|
|
|
|
if (!buddy_data.matches_filter(filter_text, user_id)) {
|
|
return;
|
|
}
|
|
|
|
buddy_list.insert_or_move([user_id]);
|
|
update_presence_indicators();
|
|
}
|
|
|
|
export function rerender_user_sidebar_participants(): void {
|
|
if (!narrow_state.stream_id() || narrow_state.topic() === undefined) {
|
|
return;
|
|
}
|
|
|
|
buddy_list.rerender_participants();
|
|
}
|
|
|
|
export function check_should_redraw_new_user(user_id: number): boolean {
|
|
if (realm.realm_presence_disabled) {
|
|
return false;
|
|
}
|
|
|
|
const user_is_in_presence_info = presence.presence_info.has(user_id);
|
|
const user_not_yet_known = people.maybe_get_user_by_id(user_id, true) === undefined;
|
|
return user_is_in_presence_info && user_not_yet_known;
|
|
}
|
|
|
|
export function searching(): boolean {
|
|
return user_filter?.searching() ?? false;
|
|
}
|
|
|
|
export function render_empty_user_list_message_if_needed($container: JQuery): void {
|
|
const empty_list_message = $container.attr("data-search-results-empty");
|
|
|
|
if (!empty_list_message || $container.children().length > 0) {
|
|
return;
|
|
}
|
|
|
|
const empty_list_widget_html = render_empty_list_widget_for_list({empty_list_message});
|
|
$container.append($(empty_list_widget_html));
|
|
}
|
|
|
|
export let build_user_sidebar = (): number[] | undefined => {
|
|
if (realm.realm_presence_disabled) {
|
|
return undefined;
|
|
}
|
|
|
|
assert(user_filter !== undefined);
|
|
const filter_text = user_filter.text();
|
|
|
|
const all_user_ids = buddy_data.get_filtered_and_sorted_user_ids(filter_text);
|
|
|
|
buddy_list.populate({all_user_ids});
|
|
|
|
render_empty_user_list_message_if_needed(buddy_list.$users_matching_view_list);
|
|
render_empty_user_list_message_if_needed(buddy_list.$other_users_list);
|
|
|
|
return all_user_ids; // for testing
|
|
};
|
|
|
|
export function rewire_build_user_sidebar(value: typeof build_user_sidebar): void {
|
|
build_user_sidebar = value;
|
|
}
|
|
|
|
function do_update_users_for_search(): void {
|
|
// Hide all the popovers but not userlist sidebar
|
|
// when the user is searching.
|
|
popovers.hide_all();
|
|
build_user_sidebar();
|
|
assert(user_cursor !== undefined);
|
|
user_cursor.reset();
|
|
}
|
|
|
|
const update_users_for_search = _.throttle(do_update_users_for_search, 50);
|
|
|
|
export function initialize(opts: {narrow_by_email: (email: string) => void}): void {
|
|
narrow_by_email = opts.narrow_by_email;
|
|
|
|
set_cursor_and_filter();
|
|
|
|
build_user_sidebar();
|
|
|
|
buddy_list.start_scroll_handler();
|
|
|
|
function get_full_presence_list_update(): void {
|
|
activity.send_presence_to_server(redraw);
|
|
}
|
|
|
|
/* Time between keep-alive pings */
|
|
const active_ping_interval_ms = realm.server_presence_ping_interval_seconds * 1000;
|
|
util.call_function_periodically(get_full_presence_list_update, active_ping_interval_ms);
|
|
|
|
// Let the server know we're here, but do not pass
|
|
// redraw, since we just got all this info in page_params.
|
|
activity.send_presence_to_server();
|
|
}
|
|
|
|
export function update_presence_info(
|
|
user_id: number,
|
|
info: PresenceInfoFromEvent,
|
|
server_time: number,
|
|
): void {
|
|
// There can be some case where the presence event
|
|
// was set for an inaccessible user if
|
|
// CAN_ACCESS_ALL_USERS_GROUP_LIMITS_PRESENCE is
|
|
// disabled. We just ignore that event and return.
|
|
const person = people.maybe_get_user_by_id(user_id, true);
|
|
if (person === undefined || person.is_inaccessible_user) {
|
|
return;
|
|
}
|
|
|
|
presence.update_info_from_event(user_id, info, server_time);
|
|
redraw_user(user_id);
|
|
pm_list.update_private_messages();
|
|
}
|
|
|
|
export function redraw(): void {
|
|
build_user_sidebar();
|
|
assert(user_cursor !== undefined);
|
|
user_cursor.redraw();
|
|
pm_list.update_private_messages();
|
|
update_presence_indicators();
|
|
}
|
|
|
|
export function reset_users(): void {
|
|
// Call this when we're leaving the search widget.
|
|
build_user_sidebar();
|
|
assert(user_cursor !== undefined);
|
|
user_cursor.clear();
|
|
}
|
|
|
|
export function narrow_for_user(opts: {$li: JQuery}): void {
|
|
const user_id = buddy_list.get_user_id_from_li({$li: opts.$li});
|
|
narrow_for_user_id({user_id});
|
|
}
|
|
|
|
export function narrow_for_user_id(opts: {user_id: number}): void {
|
|
const person = people.get_by_user_id(opts.user_id);
|
|
const email = person.email;
|
|
|
|
assert(narrow_by_email);
|
|
narrow_by_email(email);
|
|
assert(user_filter !== undefined);
|
|
user_filter.clear_search();
|
|
}
|
|
|
|
function keydown_enter_key(): void {
|
|
assert(user_cursor !== undefined);
|
|
const user_id = user_cursor.get_key();
|
|
if (user_id === undefined) {
|
|
return;
|
|
}
|
|
|
|
narrow_for_user_id({user_id});
|
|
sidebar_ui.hide_all();
|
|
popovers.hide_all();
|
|
}
|
|
|
|
export function set_cursor_and_filter(): void {
|
|
user_cursor = new ListCursor({
|
|
list: buddy_list,
|
|
highlight_class: "highlighted_user",
|
|
});
|
|
|
|
user_filter = new UserSearch({
|
|
update_list: update_users_for_search,
|
|
reset_items: reset_users,
|
|
on_focus() {
|
|
user_cursor!.reset();
|
|
},
|
|
});
|
|
|
|
const $input = user_filter.input_field();
|
|
|
|
$input.on("blur", () => {
|
|
user_cursor!.clear();
|
|
});
|
|
|
|
keydown_util.handle({
|
|
$elem: $input,
|
|
handlers: {
|
|
Enter() {
|
|
keydown_enter_key();
|
|
return true;
|
|
},
|
|
ArrowUp() {
|
|
user_cursor!.prev();
|
|
return true;
|
|
},
|
|
ArrowDown() {
|
|
user_cursor!.next();
|
|
return true;
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
export function initiate_search(): void {
|
|
if (user_filter) {
|
|
$("body").removeClass("hide-right-sidebar");
|
|
popovers.hide_all();
|
|
user_filter.initiate_search();
|
|
}
|
|
}
|
|
|
|
export function clear_search(): void {
|
|
if (user_filter) {
|
|
user_filter.clear_search();
|
|
}
|
|
}
|
|
|
|
export function get_filter_text(): string {
|
|
if (!user_filter) {
|
|
// This may be overly defensive, but there may be
|
|
// situations where get called before everything is
|
|
// fully initialized. The empty string is a fine
|
|
// default here.
|
|
blueslip.warn("get_filter_text() is called before initialization");
|
|
return "";
|
|
}
|
|
|
|
return user_filter.text();
|
|
}
|