mirror of https://github.com/zulip/zulip.git
reactions: Convert module to typescript.
This commit is contained in:
parent
07671997ca
commit
fb7d77545f
|
@ -1,5 +1,6 @@
|
|||
import * as blueslip from "./blueslip";
|
||||
import * as people from "./people";
|
||||
import type {RawReaction} from "./reactions";
|
||||
import type {Submessage, TopicLink} from "./types";
|
||||
|
||||
const stored_messages = new Map();
|
||||
|
@ -106,6 +107,8 @@ export type Message = (
|
|||
| Omit<MessageWithBooleans & {type: "private"}, "reactions">
|
||||
| Omit<MessageWithBooleans & {type: "stream"}, "reactions">
|
||||
) & {
|
||||
// Replaced by `clean_reactions` in `reactions.set_clean_reactions`.
|
||||
reactions?: RawReaction[];
|
||||
// Added in `reactions.set_clean_reactions`.
|
||||
clean_reactions: Map<string, MessageCleanReaction>;
|
||||
|
||||
|
|
|
@ -1,33 +1,48 @@
|
|||
import $ from "jquery";
|
||||
import assert from "minimalistic-assert";
|
||||
|
||||
import render_message_reaction from "../templates/message_reaction.hbs";
|
||||
|
||||
import * as blueslip from "./blueslip";
|
||||
import * as channel from "./channel";
|
||||
import * as emoji from "./emoji";
|
||||
import type {EmojiRenderingDetails} from "./emoji";
|
||||
import {$t} from "./i18n";
|
||||
import * as message_lists from "./message_lists";
|
||||
import * as message_store from "./message_store";
|
||||
import type {Message, MessageCleanReaction} from "./message_store";
|
||||
import {page_params} from "./page_params";
|
||||
import * as people from "./people";
|
||||
import * as spectators from "./spectators";
|
||||
import {current_user} from "./state_data";
|
||||
import {user_settings} from "./user_settings";
|
||||
|
||||
const waiting_for_server_request_ids = new Set();
|
||||
const waiting_for_server_request_ids = new Set<string>();
|
||||
|
||||
export function get_local_reaction_id(rendering_details) {
|
||||
type ReactionEvent = {
|
||||
message_id: number;
|
||||
user_id: number;
|
||||
local_id: string;
|
||||
reaction_type: string;
|
||||
emoji_name: string;
|
||||
emoji_code: string;
|
||||
};
|
||||
|
||||
export function get_local_reaction_id(rendering_details: EmojiRenderingDetails): string {
|
||||
return [rendering_details.reaction_type, rendering_details.emoji_code].join(",");
|
||||
}
|
||||
|
||||
export function current_user_has_reacted_to_emoji(message, local_id) {
|
||||
export function current_user_has_reacted_to_emoji(message: Message, local_id: string): boolean {
|
||||
set_clean_reactions(message);
|
||||
|
||||
const clean_reaction_object = message.clean_reactions.get(local_id);
|
||||
return clean_reaction_object && clean_reaction_object.user_ids.includes(current_user.user_id);
|
||||
return (
|
||||
clean_reaction_object !== undefined &&
|
||||
clean_reaction_object.user_ids.includes(current_user.user_id)
|
||||
);
|
||||
}
|
||||
|
||||
function get_message(message_id) {
|
||||
function get_message(message_id: number): Message | undefined {
|
||||
const message = message_store.get(message_id);
|
||||
if (!message) {
|
||||
blueslip.error("reactions: Bad message id", {message_id});
|
||||
|
@ -38,7 +53,17 @@ function get_message(message_id) {
|
|||
return message;
|
||||
}
|
||||
|
||||
function create_reaction(message_id, rendering_details) {
|
||||
export type RawReaction = {
|
||||
emoji_name: string;
|
||||
reaction_type: string;
|
||||
emoji_code: string;
|
||||
user_id: number;
|
||||
};
|
||||
|
||||
function create_reaction(
|
||||
message_id: number,
|
||||
rendering_details: EmojiRenderingDetails,
|
||||
): ReactionEvent {
|
||||
return {
|
||||
message_id,
|
||||
user_id: current_user.user_id,
|
||||
|
@ -49,7 +74,10 @@ function create_reaction(message_id, rendering_details) {
|
|||
};
|
||||
}
|
||||
|
||||
function update_ui_and_send_reaction_ajax(message_id, rendering_details) {
|
||||
function update_ui_and_send_reaction_ajax(
|
||||
message_id: number,
|
||||
rendering_details: EmojiRenderingDetails,
|
||||
): void {
|
||||
if (page_params.is_spectator) {
|
||||
// Spectators can't react, since they don't have accounts. We
|
||||
// stop here to avoid a confusing reaction local echo.
|
||||
|
@ -58,6 +86,9 @@ function update_ui_and_send_reaction_ajax(message_id, rendering_details) {
|
|||
}
|
||||
|
||||
const message = get_message(message_id);
|
||||
if (message === undefined) {
|
||||
return;
|
||||
}
|
||||
const local_id = get_local_reaction_id(rendering_details);
|
||||
const has_reacted = current_user_has_reacted_to_emoji(message, local_id);
|
||||
const operation = has_reacted ? "remove" : "add";
|
||||
|
@ -83,7 +114,7 @@ function update_ui_and_send_reaction_ajax(message_id, rendering_details) {
|
|||
success() {
|
||||
waiting_for_server_request_ids.delete(reaction_request_id);
|
||||
},
|
||||
error(xhr) {
|
||||
error(xhr: JQuery.jqXHR) {
|
||||
waiting_for_server_request_ids.delete(reaction_request_id);
|
||||
if (xhr.readyState !== 0) {
|
||||
if (
|
||||
|
@ -101,13 +132,13 @@ function update_ui_and_send_reaction_ajax(message_id, rendering_details) {
|
|||
|
||||
waiting_for_server_request_ids.add(reaction_request_id);
|
||||
if (operation === "add") {
|
||||
channel.post(args);
|
||||
void channel.post(args);
|
||||
} else if (operation === "remove") {
|
||||
channel.del(args);
|
||||
void channel.del(args);
|
||||
}
|
||||
}
|
||||
|
||||
export function toggle_emoji_reaction(message_id, emoji_name) {
|
||||
export function toggle_emoji_reaction(message_id: number, emoji_name: string): void {
|
||||
// This codepath doesn't support toggling a deactivated realm emoji.
|
||||
// Since a user can interact with a deactivated realm emoji only by
|
||||
// clicking on a reaction and that is handled by `process_reaction_click()`
|
||||
|
@ -118,7 +149,7 @@ export function toggle_emoji_reaction(message_id, emoji_name) {
|
|||
update_ui_and_send_reaction_ajax(message_id, rendering_details);
|
||||
}
|
||||
|
||||
export function process_reaction_click(message_id, local_id) {
|
||||
export function process_reaction_click(message_id: number, local_id: string): void {
|
||||
const message = get_message(message_id);
|
||||
|
||||
if (!message) {
|
||||
|
@ -142,26 +173,33 @@ export function process_reaction_click(message_id, local_id) {
|
|||
update_ui_and_send_reaction_ajax(message_id, rendering_details);
|
||||
}
|
||||
|
||||
function generate_title(emoji_name, user_ids) {
|
||||
function generate_title(emoji_name: string, user_ids: number[]): string {
|
||||
const usernames = people.get_display_full_names(
|
||||
user_ids.filter((user_id) => user_id !== current_user.user_id),
|
||||
);
|
||||
const current_user_reacted = user_ids.length !== usernames.length;
|
||||
|
||||
const context = {
|
||||
emoji_name: ":" + emoji_name + ":",
|
||||
};
|
||||
const colon_emoji_name = ":" + emoji_name + ":";
|
||||
|
||||
if (user_ids.length === 1) {
|
||||
if (current_user_reacted) {
|
||||
const context = {
|
||||
emoji_name: colon_emoji_name,
|
||||
};
|
||||
return $t({defaultMessage: "You (click to remove) reacted with {emoji_name}"}, context);
|
||||
}
|
||||
context.username = usernames[0];
|
||||
const context = {
|
||||
emoji_name: colon_emoji_name,
|
||||
username: usernames[0],
|
||||
};
|
||||
return $t({defaultMessage: "{username} reacted with {emoji_name}"}, context);
|
||||
}
|
||||
|
||||
if (user_ids.length === 2 && current_user_reacted) {
|
||||
context.other_username = usernames[0];
|
||||
const context = {
|
||||
emoji_name: colon_emoji_name,
|
||||
other_username: usernames[0],
|
||||
};
|
||||
return $t(
|
||||
{
|
||||
defaultMessage:
|
||||
|
@ -171,8 +209,11 @@ function generate_title(emoji_name, user_ids) {
|
|||
);
|
||||
}
|
||||
|
||||
context.comma_separated_usernames = usernames.slice(0, -1).join(", ");
|
||||
context.last_username = usernames.at(-1);
|
||||
const context = {
|
||||
emoji_name: colon_emoji_name,
|
||||
comma_separated_usernames: usernames.slice(0, -1).join(", "),
|
||||
last_username: usernames.at(-1),
|
||||
};
|
||||
if (current_user_reacted) {
|
||||
return $t(
|
||||
{
|
||||
|
@ -192,10 +233,13 @@ function generate_title(emoji_name, user_ids) {
|
|||
}
|
||||
|
||||
// Add a tooltip showing who reacted to a message.
|
||||
export function get_reaction_title_data(message_id, local_id) {
|
||||
export function get_reaction_title_data(message_id: number, local_id: string): string {
|
||||
const message = get_message(message_id);
|
||||
assert(message !== undefined);
|
||||
|
||||
const clean_reaction_object = message.clean_reactions.get(local_id);
|
||||
assert(clean_reaction_object !== undefined);
|
||||
|
||||
const user_list = clean_reaction_object.user_ids;
|
||||
const emoji_name = clean_reaction_object.emoji_name;
|
||||
const title = generate_title(emoji_name, user_list);
|
||||
|
@ -203,29 +247,29 @@ export function get_reaction_title_data(message_id, local_id) {
|
|||
return title;
|
||||
}
|
||||
|
||||
export function get_reaction_sections(message_id) {
|
||||
export function get_reaction_sections(message_id: number): JQuery {
|
||||
const $rows = message_lists.all_rendered_row_for_message_id(message_id);
|
||||
return $rows.find(".message_reactions");
|
||||
}
|
||||
|
||||
export function find_reaction(message_id, local_id) {
|
||||
export function find_reaction(message_id: number, local_id: string): JQuery {
|
||||
const $reaction_section = get_reaction_sections(message_id);
|
||||
const $reaction = $reaction_section.find(`[data-reaction-id='${CSS.escape(local_id)}']`);
|
||||
return $reaction;
|
||||
}
|
||||
|
||||
export function get_add_reaction_button(message_id) {
|
||||
export function get_add_reaction_button(message_id: number): JQuery {
|
||||
const $reaction_section = get_reaction_sections(message_id);
|
||||
const $add_button = $reaction_section.find(".reaction_button");
|
||||
return $add_button;
|
||||
}
|
||||
|
||||
export function set_reaction_vote_text($reaction, vote_text) {
|
||||
export function set_reaction_vote_text($reaction: JQuery, vote_text: string): void {
|
||||
const $count_element = $reaction.find(".message_reaction_count");
|
||||
$count_element.text(vote_text);
|
||||
}
|
||||
|
||||
export function add_reaction(event) {
|
||||
export function add_reaction(event: ReactionEvent): void {
|
||||
const message_id = event.message_id;
|
||||
const message = message_store.get(message_id);
|
||||
|
||||
|
@ -271,7 +315,11 @@ export function add_reaction(event) {
|
|||
}
|
||||
}
|
||||
|
||||
export function update_existing_reaction(clean_reaction_object, message, acting_user_id) {
|
||||
export function update_existing_reaction(
|
||||
clean_reaction_object: MessageCleanReaction,
|
||||
message: Message,
|
||||
acting_user_id: number,
|
||||
): void {
|
||||
// Our caller ensures that this message already has a reaction
|
||||
// for this emoji and sets up our user_list. This function
|
||||
// simply updates the DOM.
|
||||
|
@ -291,34 +339,38 @@ export function update_existing_reaction(clean_reaction_object, message, acting_
|
|||
update_vote_text_on_message(message);
|
||||
}
|
||||
|
||||
export function insert_new_reaction(clean_reaction_object, message, user_id) {
|
||||
export function insert_new_reaction(
|
||||
clean_reaction_object: MessageCleanReaction,
|
||||
message: Message,
|
||||
user_id: number,
|
||||
): void {
|
||||
// Our caller ensures we are the first user to react to this
|
||||
// message with this emoji. We then render the emoji/title/count
|
||||
// and insert it before the add button.
|
||||
|
||||
const context = {
|
||||
message_id: message.id,
|
||||
...emoji.get_emoji_details_for_rendering(clean_reaction_object),
|
||||
};
|
||||
|
||||
const emoji_details = emoji.get_emoji_details_for_rendering(clean_reaction_object);
|
||||
const new_label = generate_title(
|
||||
clean_reaction_object.emoji_name,
|
||||
clean_reaction_object.user_ids,
|
||||
);
|
||||
|
||||
context.count = 1;
|
||||
context.label = new_label;
|
||||
context.local_id = get_local_reaction_id(clean_reaction_object);
|
||||
context.emoji_alt_code = user_settings.emojiset === "text";
|
||||
context.is_realm_emoji =
|
||||
context.reaction_type === "realm_emoji" || context.reaction_type === "zulip_extra_emoji";
|
||||
context.vote_text = ""; // Updated below
|
||||
const is_realm_emoji =
|
||||
emoji_details.reaction_type === "realm_emoji" ||
|
||||
emoji_details.reaction_type === "zulip_extra_emoji";
|
||||
const reaction_class =
|
||||
user_id === current_user.user_id ? "message_reaction reacted" : "message_reaction";
|
||||
|
||||
if (user_id === current_user.user_id) {
|
||||
context.class = "message_reaction reacted";
|
||||
} else {
|
||||
context.class = "message_reaction";
|
||||
}
|
||||
const context = {
|
||||
message_id: message.id,
|
||||
...emoji_details,
|
||||
count: 1,
|
||||
label: new_label,
|
||||
local_id: get_local_reaction_id(clean_reaction_object),
|
||||
emoji_alt_code: user_settings.emojiset === "text",
|
||||
is_realm_emoji,
|
||||
vote_text: "", // Updated below
|
||||
class: reaction_class,
|
||||
};
|
||||
|
||||
const $new_reaction = $(render_message_reaction(context));
|
||||
|
||||
|
@ -329,7 +381,7 @@ export function insert_new_reaction(clean_reaction_object, message, user_id) {
|
|||
update_vote_text_on_message(message);
|
||||
}
|
||||
|
||||
export function remove_reaction(event) {
|
||||
export function remove_reaction(event: ReactionEvent): void {
|
||||
const message_id = event.message_id;
|
||||
const user_id = event.user_id;
|
||||
const message = message_store.get(message_id);
|
||||
|
@ -366,7 +418,11 @@ export function remove_reaction(event) {
|
|||
remove_reaction_from_view(clean_reaction_object, message, user_id);
|
||||
}
|
||||
|
||||
export function remove_reaction_from_view(clean_reaction_object, message, user_id) {
|
||||
export function remove_reaction_from_view(
|
||||
clean_reaction_object: MessageCleanReaction,
|
||||
message: Message,
|
||||
user_id: number,
|
||||
): void {
|
||||
const local_id = get_local_reaction_id(clean_reaction_object);
|
||||
const $reaction = find_reaction(message.id, local_id);
|
||||
const reaction_count = clean_reaction_object.user_ids.length;
|
||||
|
@ -394,9 +450,11 @@ export function remove_reaction_from_view(clean_reaction_object, message, user_i
|
|||
update_vote_text_on_message(message);
|
||||
}
|
||||
|
||||
export function get_emojis_used_by_user_for_message_id(message_id) {
|
||||
export function get_emojis_used_by_user_for_message_id(message_id: number): string[] {
|
||||
const user_id = current_user.user_id;
|
||||
assert(user_id !== undefined);
|
||||
const message = message_store.get(message_id);
|
||||
assert(message !== undefined);
|
||||
set_clean_reactions(message);
|
||||
|
||||
const names = [];
|
||||
|
@ -409,12 +467,12 @@ export function get_emojis_used_by_user_for_message_id(message_id) {
|
|||
return names;
|
||||
}
|
||||
|
||||
export function get_message_reactions(message) {
|
||||
export function get_message_reactions(message: Message): MessageCleanReaction[] {
|
||||
set_clean_reactions(message);
|
||||
return [...message.clean_reactions.values()];
|
||||
}
|
||||
|
||||
export function set_clean_reactions(message) {
|
||||
export function set_clean_reactions(message: Message): void {
|
||||
/*
|
||||
set_clean_reactions processes the raw message.reactions object,
|
||||
which will contain one object for each individual reaction, even
|
||||
|
@ -441,8 +499,9 @@ export function set_clean_reactions(message) {
|
|||
// This first loop creates a temporary distinct_reactions data
|
||||
// structure, which will accumulate the set of users who have
|
||||
// reacted with each distinct reaction.
|
||||
const distinct_reactions = new Map();
|
||||
const user_map = new Map();
|
||||
assert(message.reactions !== undefined);
|
||||
const distinct_reactions = new Map<string, RawReaction>();
|
||||
const user_map = new Map<string, number[]>();
|
||||
for (const reaction of message.reactions) {
|
||||
const local_id = get_local_reaction_id(reaction);
|
||||
const user_id = reaction.user_id;
|
||||
|
@ -452,7 +511,7 @@ export function set_clean_reactions(message) {
|
|||
user_map.set(local_id, []);
|
||||
}
|
||||
|
||||
const user_ids = user_map.get(local_id);
|
||||
const user_ids = user_map.get(local_id)!;
|
||||
|
||||
if (user_ids.includes(user_id)) {
|
||||
blueslip.error("server sent duplicate reactions", {user_id, local_id});
|
||||
|
@ -471,6 +530,7 @@ export function set_clean_reactions(message) {
|
|||
|
||||
const reaction_counts_and_user_ids = [...distinct_reactions.keys()].map((local_id) => {
|
||||
const user_ids = user_map.get(local_id);
|
||||
assert(user_ids !== undefined);
|
||||
return {
|
||||
count: user_ids.length,
|
||||
user_ids,
|
||||
|
@ -480,7 +540,9 @@ export function set_clean_reactions(message) {
|
|||
|
||||
for (const local_id of distinct_reactions.keys()) {
|
||||
const reaction = distinct_reactions.get(local_id);
|
||||
assert(reaction !== undefined);
|
||||
const user_ids = user_map.get(local_id);
|
||||
assert(user_ids !== undefined);
|
||||
|
||||
message.clean_reactions.set(
|
||||
local_id,
|
||||
|
@ -502,7 +564,14 @@ function make_clean_reaction({
|
|||
emoji_code,
|
||||
reaction_type,
|
||||
should_display_reactors,
|
||||
}) {
|
||||
}: {
|
||||
local_id: string;
|
||||
user_ids: number[];
|
||||
emoji_name: string;
|
||||
emoji_code: string;
|
||||
reaction_type: string;
|
||||
should_display_reactors: boolean;
|
||||
}): MessageCleanReaction {
|
||||
const emoji_details = emoji.get_emoji_details_for_rendering({
|
||||
emoji_name,
|
||||
emoji_code,
|
||||
|
@ -537,7 +606,10 @@ function make_clean_reaction({
|
|||
};
|
||||
}
|
||||
|
||||
export function update_user_fields(clean_reaction_object, should_display_reactors) {
|
||||
export function update_user_fields(
|
||||
clean_reaction_object: MessageCleanReaction,
|
||||
should_display_reactors: boolean,
|
||||
): void {
|
||||
// update_user_fields needs to be called whenever the set of users
|
||||
// who reacted on a message might have changed, including due to
|
||||
// upvote/downvotes on ANY reaction in the message, because those
|
||||
|
@ -547,6 +619,7 @@ export function update_user_fields(clean_reaction_object, should_display_reactor
|
|||
clean_reaction_object.emoji_name,
|
||||
clean_reaction_object.user_ids,
|
||||
);
|
||||
|
||||
if (clean_reaction_object.user_ids.includes(current_user.user_id)) {
|
||||
clean_reaction_object.class = "message_reaction reacted";
|
||||
} else {
|
||||
|
@ -567,33 +640,40 @@ export function update_user_fields(clean_reaction_object, should_display_reactor
|
|||
);
|
||||
}
|
||||
|
||||
function get_reaction_counts_and_user_ids(message) {
|
||||
type ReactionUserIdAndCount = {
|
||||
count?: number;
|
||||
user_ids: number[];
|
||||
};
|
||||
|
||||
function get_reaction_counts_and_user_ids(message: Message): ReactionUserIdAndCount[] {
|
||||
return [...message.clean_reactions.values()].map((reaction) => ({
|
||||
count: reaction.count,
|
||||
user_ids: reaction.user_ids,
|
||||
}));
|
||||
}
|
||||
|
||||
export function get_vote_text(user_ids, should_display_reactors) {
|
||||
export function get_vote_text(user_ids: number[], should_display_reactors: boolean): string {
|
||||
if (should_display_reactors) {
|
||||
return comma_separated_usernames(user_ids);
|
||||
}
|
||||
return `${user_ids.length}`;
|
||||
}
|
||||
|
||||
function check_should_display_reactors(reaction_counts_and_user_ids) {
|
||||
function check_should_display_reactors(
|
||||
reaction_counts_and_user_ids: ReactionUserIdAndCount[],
|
||||
): boolean {
|
||||
if (!user_settings.display_emoji_reaction_users) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let total_reactions = 0;
|
||||
for (const {count, user_ids} of reaction_counts_and_user_ids) {
|
||||
total_reactions += count || user_ids.length;
|
||||
total_reactions += count ?? user_ids.length;
|
||||
}
|
||||
return total_reactions <= 3;
|
||||
}
|
||||
|
||||
function comma_separated_usernames(user_list) {
|
||||
function comma_separated_usernames(user_list: number[]): string {
|
||||
const usernames = people.get_display_full_names(user_list);
|
||||
const current_user_has_reacted = user_list.includes(current_user.user_id);
|
||||
|
||||
|
@ -607,7 +687,7 @@ function comma_separated_usernames(user_list) {
|
|||
return comma_separated_usernames;
|
||||
}
|
||||
|
||||
export function update_vote_text_on_message(message) {
|
||||
export function update_vote_text_on_message(message: Message): void {
|
||||
// Because whether we display a count or the names of reacting
|
||||
// users depends on total reactions on the message, we need to
|
||||
// recalculate this whenever adjusting reaction rendering on a
|
||||
|
@ -618,7 +698,9 @@ export function update_vote_text_on_message(message) {
|
|||
for (const [reaction, clean_reaction] of message.clean_reactions.entries()) {
|
||||
const reaction_elem = find_reaction(message.id, clean_reaction.local_id);
|
||||
const vote_text = get_vote_text(clean_reaction.user_ids, should_display_reactors);
|
||||
message.clean_reactions.get(reaction).vote_text = vote_text;
|
||||
const message_clean_reaction = message.clean_reactions.get(reaction);
|
||||
assert(message_clean_reaction !== undefined);
|
||||
message_clean_reaction.vote_text = vote_text;
|
||||
set_reaction_vote_text(reaction_elem, vote_text);
|
||||
}
|
||||
}
|
|
@ -1181,7 +1181,7 @@ test("remove_reaction_from_view (last person)", () => {
|
|||
assert.ok(removed);
|
||||
});
|
||||
|
||||
test("error_handling", ({override, override_rewire}) => {
|
||||
test("error_handling", ({override}) => {
|
||||
override(message_store, "get", noop);
|
||||
|
||||
blueslip.expect("error", "reactions: Bad message id");
|
||||
|
@ -1193,7 +1193,6 @@ test("error_handling", ({override, override_rewire}) => {
|
|||
emoji_code: "991",
|
||||
user_id: 99,
|
||||
};
|
||||
override_rewire(reactions, "current_user_has_reacted_to_emoji", () => true);
|
||||
reactions.toggle_emoji_reaction(55, bogus_event.emoji_name);
|
||||
|
||||
reactions.add_reaction(bogus_event);
|
||||
|
|
Loading…
Reference in New Issue