2024-07-08 19:00:08 +02:00
|
|
|
import assert from "minimalistic-assert";
|
|
|
|
|
2024-11-12 03:59:37 +01:00
|
|
|
import {all_messages_data} from "./all_messages_data.ts";
|
|
|
|
import type {MessageList, RenderInfo} from "./message_list.ts";
|
|
|
|
import type {MessageListData} from "./message_list_data.ts";
|
|
|
|
import * as message_lists from "./message_lists.ts";
|
|
|
|
import * as message_store from "./message_store.ts";
|
|
|
|
import type {Message} from "./message_store.ts";
|
|
|
|
import * as people from "./people.ts";
|
|
|
|
import * as pm_conversations from "./pm_conversations.ts";
|
|
|
|
import * as unread from "./unread.ts";
|
|
|
|
import * as unread_ui from "./unread_ui.ts";
|
2020-08-01 03:43:15 +02:00
|
|
|
|
2024-07-08 19:00:08 +02:00
|
|
|
type DirectMessagePermissionHints = {
|
|
|
|
is_known_empty_conversation: boolean;
|
|
|
|
is_local_echo_safe: boolean;
|
|
|
|
};
|
|
|
|
|
2023-12-27 00:17:28 +01:00
|
|
|
export function do_unread_count_updates(messages: Message[], expect_no_new_unreads = false): void {
|
2022-10-25 00:34:47 +02:00
|
|
|
const any_new_unreads = unread.process_loaded_messages(messages, expect_no_new_unreads);
|
|
|
|
|
|
|
|
if (any_new_unreads) {
|
|
|
|
// The following operations are expensive, and thus should
|
|
|
|
// only happen if we found any unread messages justifying it.
|
|
|
|
unread_ui.update_unread_counts();
|
|
|
|
}
|
2021-02-28 01:10:31 +01:00
|
|
|
}
|
2017-03-19 22:43:38 +01:00
|
|
|
|
2023-12-27 00:17:28 +01:00
|
|
|
export function add_messages(
|
|
|
|
messages: Message[],
|
|
|
|
msg_list: MessageList,
|
|
|
|
append_to_view_opts: {messages_are_new: boolean},
|
|
|
|
): RenderInfo | undefined {
|
2017-03-19 22:43:38 +01:00
|
|
|
if (!messages) {
|
2020-09-24 07:50:36 +02:00
|
|
|
return undefined;
|
2017-03-19 22:43:38 +01:00
|
|
|
}
|
|
|
|
|
2023-12-27 00:17:28 +01:00
|
|
|
const render_info = msg_list.add_messages(messages, append_to_view_opts);
|
message scrolling: Fix "Scroll down to view" warning.
We recently added a feature to warn users that they
may need to scroll down to view messages that they
just sent, but it was broken due to various complexities
in the rendering code path.
Now we compute it a bit more rigorously.
It requires us to pass some info about rendering up
and down the stack, which is why it's kind of a long
commit, but the bulk of the logic is in these JS files:
* message_list_view.js
* notifications.js
I choose to pass structs around instead of booleans,
because I anticipate we may eventually add more metadata
about rendering to it, plus bools are just kinda brittle.
(The exceptions are that `_maybe_autoscroll`, which
is at the bottom of the stack, just passes back a simple
boolean, and `notify_local_mixes`, also at the bottom
of the stack, just accepts a simple boolean.)
This errs on the side of warning the user, even if the
new message is partially visible.
Fixes #11138
2019-01-07 21:00:03 +01:00
|
|
|
|
|
|
|
return render_info;
|
2019-01-08 01:26:02 +01:00
|
|
|
}
|
|
|
|
|
2023-12-27 00:17:28 +01:00
|
|
|
export function add_old_messages(
|
|
|
|
messages: Message[],
|
|
|
|
msg_list: MessageList,
|
|
|
|
): RenderInfo | undefined {
|
message scrolling: Fix "Scroll down to view" warning.
We recently added a feature to warn users that they
may need to scroll down to view messages that they
just sent, but it was broken due to various complexities
in the rendering code path.
Now we compute it a bit more rigorously.
It requires us to pass some info about rendering up
and down the stack, which is why it's kind of a long
commit, but the bulk of the logic is in these JS files:
* message_list_view.js
* notifications.js
I choose to pass structs around instead of booleans,
because I anticipate we may eventually add more metadata
about rendering to it, plus bools are just kinda brittle.
(The exceptions are that `_maybe_autoscroll`, which
is at the bottom of the stack, just passes back a simple
boolean, and `notify_local_mixes`, also at the bottom
of the stack, just accepts a simple boolean.)
This errs on the side of warning the user, even if the
new message is partially visible.
Fixes #11138
2019-01-07 21:00:03 +01:00
|
|
|
return add_messages(messages, msg_list, {messages_are_new: false});
|
2021-02-28 01:10:31 +01:00
|
|
|
}
|
2020-05-30 17:34:07 +02:00
|
|
|
|
2023-12-27 00:17:28 +01:00
|
|
|
export function add_new_messages(
|
|
|
|
messages: Message[],
|
|
|
|
msg_list: MessageList,
|
|
|
|
): RenderInfo | undefined {
|
2020-05-30 17:34:07 +02:00
|
|
|
if (!msg_list.data.fetch_status.has_found_newest()) {
|
|
|
|
// We don't render newly received messages for the message list,
|
|
|
|
// if we haven't found the latest messages to be displayed in the
|
|
|
|
// narrow. Otherwise the new message would be rendered just after
|
|
|
|
// the previously fetched messages when that's inaccurate.
|
|
|
|
msg_list.data.fetch_status.update_expected_max_message_id(messages);
|
2020-09-24 07:50:36 +02:00
|
|
|
return undefined;
|
2020-05-30 17:34:07 +02:00
|
|
|
}
|
message scrolling: Fix "Scroll down to view" warning.
We recently added a feature to warn users that they
may need to scroll down to view messages that they
just sent, but it was broken due to various complexities
in the rendering code path.
Now we compute it a bit more rigorously.
It requires us to pass some info about rendering up
and down the stack, which is why it's kind of a long
commit, but the bulk of the logic is in these JS files:
* message_list_view.js
* notifications.js
I choose to pass structs around instead of booleans,
because I anticipate we may eventually add more metadata
about rendering to it, plus bools are just kinda brittle.
(The exceptions are that `_maybe_autoscroll`, which
is at the bottom of the stack, just passes back a simple
boolean, and `notify_local_mixes`, also at the bottom
of the stack, just accepts a simple boolean.)
This errs on the side of warning the user, even if the
new message is partially visible.
Fixes #11138
2019-01-07 21:00:03 +01:00
|
|
|
return add_messages(messages, msg_list, {messages_are_new: true});
|
2021-02-28 01:10:31 +01:00
|
|
|
}
|
2017-03-19 22:43:38 +01:00
|
|
|
|
2023-12-27 00:17:28 +01:00
|
|
|
export function add_new_messages_data(
|
|
|
|
messages: Message[],
|
|
|
|
msg_list_data: MessageListData,
|
|
|
|
):
|
|
|
|
| {
|
|
|
|
top_messages: Message[];
|
|
|
|
bottom_messages: Message[];
|
|
|
|
interior_messages: Message[];
|
|
|
|
}
|
|
|
|
| undefined {
|
2021-03-30 06:23:09 +02:00
|
|
|
if (!msg_list_data.fetch_status.has_found_newest()) {
|
2024-08-18 19:08:27 +02:00
|
|
|
const filtered_msgs = msg_list_data.valid_non_duplicated_messages(messages);
|
2021-03-30 06:23:09 +02:00
|
|
|
// The reasoning in add_new_messages applies here as well;
|
|
|
|
// we're trying to maintain a data structure that's a
|
|
|
|
// contiguous range of message history, so we can't append a
|
|
|
|
// new message that might not be adjacent to that range.
|
2024-08-18 19:08:27 +02:00
|
|
|
msg_list_data.fetch_status.update_expected_max_message_id(filtered_msgs);
|
2021-03-30 06:23:09 +02:00
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
return msg_list_data.add_messages(messages);
|
|
|
|
}
|
|
|
|
|
2023-12-27 00:17:28 +01:00
|
|
|
export function get_messages_in_topic(stream_id: number, topic: string): Message[] {
|
2021-03-30 06:23:09 +02:00
|
|
|
return all_messages_data
|
2020-07-15 00:34:28 +02:00
|
|
|
.all_messages()
|
|
|
|
.filter(
|
|
|
|
(x) =>
|
|
|
|
x.type === "stream" &&
|
|
|
|
x.stream_id === stream_id &&
|
|
|
|
x.topic.toLowerCase() === topic.toLowerCase(),
|
|
|
|
);
|
2021-02-28 01:10:31 +01:00
|
|
|
}
|
2017-03-19 22:43:38 +01:00
|
|
|
|
2024-07-24 05:52:43 +02:00
|
|
|
export function get_messages_in_dm_conversations(user_ids_strings: Set<string>): Message[] {
|
|
|
|
return all_messages_data
|
|
|
|
.all_messages()
|
|
|
|
.filter((x) => x.type === "private" && user_ids_strings.has(x.to_user_ids));
|
|
|
|
}
|
|
|
|
|
2023-12-27 00:17:28 +01:00
|
|
|
export function get_max_message_id_in_stream(stream_id: number): number {
|
2020-08-04 11:13:20 +02:00
|
|
|
let max_message_id = 0;
|
2021-03-30 06:23:09 +02:00
|
|
|
for (const msg of all_messages_data.all_messages()) {
|
2020-12-22 11:26:39 +01:00
|
|
|
if (msg.type === "stream" && msg.stream_id === stream_id && msg.id > max_message_id) {
|
|
|
|
max_message_id = msg.id;
|
2020-08-04 11:13:20 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return max_message_id;
|
2021-02-28 01:10:31 +01:00
|
|
|
}
|
2020-08-04 11:13:20 +02:00
|
|
|
|
2023-12-27 00:17:28 +01:00
|
|
|
export function get_topics_for_message_ids(message_ids: number[]): Map<string, [number, string]> {
|
2024-05-02 23:55:28 +02:00
|
|
|
const topics = new Map<string, [number, string]>(); // key = stream_id:topic
|
2020-08-07 09:15:47 +02:00
|
|
|
for (const msg_id of message_ids) {
|
|
|
|
// message_store still has data on deleted messages when this runs.
|
|
|
|
const message = message_store.get(msg_id);
|
|
|
|
if (message === undefined) {
|
|
|
|
// We may not have the deleted message cached locally in
|
|
|
|
// message_store; if so, we can just skip processing it.
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (message.type === "stream") {
|
|
|
|
// Create unique keys for stream_id and topic.
|
|
|
|
const topic_key = message.stream_id + ":" + message.topic;
|
|
|
|
topics.set(topic_key, [message.stream_id, message.topic]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return topics;
|
2021-02-28 01:10:31 +01:00
|
|
|
}
|
2024-07-08 19:00:08 +02:00
|
|
|
|
|
|
|
export function get_direct_message_permission_hints(
|
|
|
|
recipient_ids_string: string,
|
|
|
|
): DirectMessagePermissionHints {
|
|
|
|
// Check if there are any previous messages in the DM conversation.
|
|
|
|
const have_conversation_in_cache =
|
|
|
|
pm_conversations.recent.has_conversation(recipient_ids_string);
|
|
|
|
if (have_conversation_in_cache) {
|
|
|
|
return {is_known_empty_conversation: false, is_local_echo_safe: true};
|
|
|
|
}
|
|
|
|
|
|
|
|
// If not, we need to check if the current filter matches the DM view we
|
|
|
|
// are composing to.
|
|
|
|
const dm_conversation = message_lists.current?.data?.filter.operands("dm")[0];
|
|
|
|
if (dm_conversation) {
|
|
|
|
const current_user_ids_string = people.emails_strings_to_user_ids_string(dm_conversation);
|
|
|
|
assert(current_user_ids_string !== undefined);
|
|
|
|
// If it matches and the messages for the current filter are fetched,
|
|
|
|
// then there are certainly no messages in the conversation.
|
|
|
|
if (
|
|
|
|
people.pm_lookup_key(recipient_ids_string) ===
|
|
|
|
people.pm_lookup_key(current_user_ids_string) &&
|
|
|
|
message_lists.current?.data?.fetch_status.has_found_newest()
|
|
|
|
) {
|
|
|
|
return {is_known_empty_conversation: true, is_local_echo_safe: true};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If it does not match, then there can be messages in the DM conversation
|
|
|
|
// which are not fetched locally and hence we disable local echo for clean
|
|
|
|
// error handling in case there are no messages in the conversation and
|
|
|
|
// user is not allowed to initiate DM conversations.
|
|
|
|
return {is_known_empty_conversation: false, is_local_echo_safe: false};
|
|
|
|
}
|
|
|
|
|
|
|
|
export function user_can_send_direct_message(user_ids_string: string): boolean {
|
|
|
|
return (
|
|
|
|
(!get_direct_message_permission_hints(user_ids_string).is_known_empty_conversation ||
|
|
|
|
people.user_can_initiate_direct_message_thread(user_ids_string)) &&
|
|
|
|
people.user_can_direct_message(user_ids_string)
|
|
|
|
);
|
|
|
|
}
|