2021-03-11 05:43:45 +01:00
|
|
|
import $ from "jquery";
|
2021-02-28 01:25:00 +01:00
|
|
|
import _ from "lodash";
|
2020-08-01 03:43:15 +02:00
|
|
|
|
2021-02-28 01:25:00 +01:00
|
|
|
import * as emoji from "../shared/js/emoji";
|
|
|
|
import render_message_reaction from "../templates/message_reaction.hbs";
|
2020-07-25 02:02:35 +02:00
|
|
|
|
2021-03-16 23:38:59 +01:00
|
|
|
import * as blueslip from "./blueslip";
|
2021-02-28 01:25:00 +01:00
|
|
|
import * as channel from "./channel";
|
2021-02-28 01:26:16 +01:00
|
|
|
import * as emoji_picker from "./emoji_picker";
|
2021-04-13 06:51:54 +02:00
|
|
|
import {$t} from "./i18n";
|
2021-03-30 02:21:21 +02:00
|
|
|
import * as message_lists from "./message_lists";
|
2021-02-28 01:25:00 +01:00
|
|
|
import * as message_store from "./message_store";
|
2021-03-25 22:35:45 +01:00
|
|
|
import {page_params} from "./page_params";
|
2021-02-28 01:25:00 +01:00
|
|
|
import * as people from "./people";
|
2021-09-07 04:09:12 +02:00
|
|
|
import * as spectators from "./spectators";
|
2021-07-28 16:00:58 +02:00
|
|
|
import {user_settings} from "./user_settings";
|
2019-02-08 11:56:33 +01:00
|
|
|
|
2021-02-28 01:25:00 +01:00
|
|
|
export const view = {}; // function namespace
|
2020-08-20 21:24:06 +02:00
|
|
|
|
2021-02-28 01:25:00 +01:00
|
|
|
export function get_local_reaction_id(reaction_info) {
|
2020-07-15 00:34:28 +02:00
|
|
|
return [reaction_info.reaction_type, reaction_info.emoji_code].join(",");
|
2021-02-28 01:25:00 +01:00
|
|
|
}
|
2017-10-31 22:33:28 +01:00
|
|
|
|
2021-02-28 01:25:00 +01:00
|
|
|
export function open_reactions_popover() {
|
2021-03-30 02:21:21 +02:00
|
|
|
const message = message_lists.current.selected_message();
|
2021-02-24 16:50:30 +01:00
|
|
|
let target;
|
|
|
|
|
|
|
|
// Use verbose style to ensure we test both sides of the condition.
|
|
|
|
if (message.sent_by_me) {
|
2021-03-30 02:21:21 +02:00
|
|
|
target = $(message_lists.current.selected_row()).find(".actions_hover")[0];
|
2021-02-24 16:50:30 +01:00
|
|
|
} else {
|
2021-03-30 02:21:21 +02:00
|
|
|
target = $(message_lists.current.selected_row()).find(".reaction_button")[0];
|
2017-09-18 22:06:39 +02:00
|
|
|
}
|
2021-02-24 16:50:30 +01:00
|
|
|
|
2021-03-30 02:21:21 +02:00
|
|
|
emoji_picker.toggle_emoji_popover(target, message_lists.current.selected_id());
|
2017-09-18 22:06:39 +02:00
|
|
|
return true;
|
2021-02-28 01:25:00 +01:00
|
|
|
}
|
2017-09-18 22:06:39 +02:00
|
|
|
|
2021-02-28 01:25:00 +01:00
|
|
|
export function current_user_has_reacted_to_emoji(message, local_id) {
|
|
|
|
set_clean_reactions(message);
|
reactions: Rewrite code to use clean reactions.
Before this commit, the reactions code would
take the `message.reactions` structure from
the server and try to "collapse" all the reactions
for the same users into the same reactions,
but with each reaction having a list of user_ids.
It was a strangely denormalized structure that
was awkward to work with, and it made it really
hard to reason about whether the data was in
the original structure that the server sent or
the modified structure.
Now we use a cleaner, normalized Map to keep
each reaction (i.e. one per emoji), and we
write that to `message.clean_reactions`.
The `clean_reactions` structure is now the
authoritatize source for all reaction-related
operations. As soon as you try to do anything
with reactions, we build the `clean_reactions`
data on the fly from the server data.
In particular, when we process events, we just
directly manipulate the `clean_reactions` data,
which is much easier to work with, since it's
a Map and doesn't duplicate any data.
This rewrite should avoid some obscure bugs.
I use `r` as shorthand for the clean reaction
structures, so as not to confuse it with
data from the server's message.reactions.
It also avoids some confusion where we use
`reaction` as a var name for the reaction
elements.
2020-03-08 13:13:47 +01:00
|
|
|
|
|
|
|
const r = message.clean_reactions.get(local_id);
|
|
|
|
return r && r.user_ids.includes(page_params.user_id);
|
2021-02-28 01:25:00 +01:00
|
|
|
}
|
2017-10-08 17:02:14 +02:00
|
|
|
|
2017-10-31 22:33:28 +01:00
|
|
|
function get_message(message_id) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const message = message_store.get(message_id);
|
2017-10-31 22:33:28 +01:00
|
|
|
if (!message) {
|
2020-07-15 01:29:15 +02:00
|
|
|
blueslip.error("reactions: Bad message id: " + message_id);
|
2020-09-24 07:50:36 +02:00
|
|
|
return undefined;
|
2016-12-02 13:23:23 +01:00
|
|
|
}
|
2017-10-31 22:33:28 +01:00
|
|
|
|
2021-02-28 01:25:00 +01:00
|
|
|
set_clean_reactions(message);
|
2017-10-31 22:33:28 +01:00
|
|
|
return message;
|
|
|
|
}
|
|
|
|
|
2017-12-19 00:55:40 +01:00
|
|
|
function create_reaction(message_id, reaction_info) {
|
|
|
|
return {
|
2020-07-20 22:18:43 +02:00
|
|
|
message_id,
|
2020-04-22 23:24:28 +02:00
|
|
|
user_id: page_params.user_id,
|
2021-02-28 01:25:00 +01:00
|
|
|
local_id: get_local_reaction_id(reaction_info),
|
2017-12-19 00:55:40 +01:00
|
|
|
reaction_type: reaction_info.reaction_type,
|
|
|
|
emoji_name: reaction_info.emoji_name,
|
|
|
|
emoji_code: reaction_info.emoji_code,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function update_ui_and_send_reaction_ajax(message_id, reaction_info) {
|
2021-11-21 17:31:12 +01:00
|
|
|
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.
|
|
|
|
spectators.login_to_access();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const message = get_message(message_id);
|
2021-02-28 01:25:00 +01:00
|
|
|
const local_id = get_local_reaction_id(reaction_info);
|
|
|
|
const has_reacted = current_user_has_reacted_to_emoji(message, local_id);
|
2020-07-15 01:29:15 +02:00
|
|
|
const operation = has_reacted ? "remove" : "add";
|
2019-11-02 00:06:25 +01:00
|
|
|
const reaction = create_reaction(message_id, reaction_info);
|
2017-12-19 00:55:40 +01:00
|
|
|
|
|
|
|
if (operation === "add") {
|
2021-02-28 01:25:00 +01:00
|
|
|
add_reaction(reaction);
|
2017-12-19 00:55:40 +01:00
|
|
|
} else {
|
2021-02-28 01:25:00 +01:00
|
|
|
remove_reaction(reaction);
|
2017-12-19 00:55:40 +01:00
|
|
|
}
|
2017-10-31 22:33:28 +01:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const args = {
|
2020-07-15 01:29:15 +02:00
|
|
|
url: "/json/messages/" + message_id + "/reactions",
|
2017-10-31 22:33:28 +01:00
|
|
|
data: reaction_info,
|
2020-07-20 22:18:43 +02:00
|
|
|
success() {},
|
|
|
|
error(xhr) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const response = channel.xhr_error_message("Error sending reaction", xhr);
|
2020-03-28 01:25:56 +01:00
|
|
|
// Errors are somewhat common here, due to race conditions
|
2017-03-24 23:46:20 +01:00
|
|
|
// where the user tries to add/remove the reaction when there is already
|
|
|
|
// an in-flight request. We eventually want to make this a blueslip
|
|
|
|
// error, rather than a warning, but we need to implement either
|
|
|
|
// #4291 or #4295 first.
|
|
|
|
blueslip.warn(response);
|
2017-01-12 00:17:43 +01:00
|
|
|
},
|
2016-12-02 13:23:23 +01:00
|
|
|
};
|
2020-07-15 01:29:15 +02:00
|
|
|
if (operation === "add") {
|
2017-10-31 22:33:28 +01:00
|
|
|
channel.post(args);
|
2020-07-15 01:29:15 +02:00
|
|
|
} else if (operation === "remove") {
|
2016-12-02 13:23:23 +01:00
|
|
|
channel.del(args);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-28 01:25:00 +01:00
|
|
|
export function toggle_emoji_reaction(message_id, emoji_name) {
|
2018-03-11 18:55:20 +01:00
|
|
|
// This codepath doesn't support toggling a deactivated realm emoji.
|
|
|
|
// Since an user can interact with a deactivated realm emoji only by
|
|
|
|
// clicking on a reaction and that is handled by `process_reaction_click()`
|
|
|
|
// method. This codepath is to be used only where there is no chance of an
|
|
|
|
// user interacting with a deactivated realm emoji like emoji picker.
|
2017-05-11 17:00:24 +02:00
|
|
|
|
2021-07-26 12:43:55 +02:00
|
|
|
const reaction_info = emoji.get_emoji_details_by_name(emoji_name);
|
2017-12-19 00:55:40 +01:00
|
|
|
update_ui_and_send_reaction_ajax(message_id, reaction_info);
|
2021-02-28 01:25:00 +01:00
|
|
|
}
|
2016-12-02 13:23:23 +01:00
|
|
|
|
2021-02-28 01:25:00 +01:00
|
|
|
export function process_reaction_click(message_id, local_id) {
|
2020-03-09 14:26:17 +01:00
|
|
|
const message = get_message(message_id);
|
|
|
|
|
|
|
|
if (!message) {
|
2020-07-15 01:29:15 +02:00
|
|
|
blueslip.error("message_id for reaction click is unknown: " + message_id);
|
2020-03-09 14:26:17 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const r = message.clean_reactions.get(local_id);
|
|
|
|
|
|
|
|
if (!r) {
|
|
|
|
blueslip.error(
|
2020-07-15 00:34:28 +02:00
|
|
|
"Data integrity problem for reaction " + local_id + " (message " + message_id + ")",
|
|
|
|
);
|
2020-03-09 14:26:17 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const reaction_info = {
|
|
|
|
reaction_type: r.reaction_type,
|
|
|
|
emoji_name: r.emoji_name,
|
|
|
|
emoji_code: r.emoji_code,
|
|
|
|
};
|
2017-10-31 22:33:28 +01:00
|
|
|
|
2017-12-19 00:55:40 +01:00
|
|
|
update_ui_and_send_reaction_ajax(message_id, reaction_info);
|
2021-02-28 01:25:00 +01:00
|
|
|
}
|
2017-10-31 22:33:28 +01:00
|
|
|
|
2020-10-21 15:09:10 +02:00
|
|
|
function generate_title(emoji_name, user_ids) {
|
2021-05-10 15:35:02 +02:00
|
|
|
const usernames = people.get_display_full_names(
|
|
|
|
user_ids.filter((user_id) => user_id !== page_params.user_id),
|
|
|
|
);
|
2020-10-21 15:09:10 +02:00
|
|
|
const current_user_reacted = user_ids.length !== usernames.length;
|
|
|
|
|
|
|
|
const context = {
|
|
|
|
emoji_name: ":" + emoji_name + ":",
|
|
|
|
};
|
|
|
|
|
|
|
|
if (user_ids.length === 1) {
|
|
|
|
if (current_user_reacted) {
|
2021-04-13 06:51:54 +02:00
|
|
|
return $t({defaultMessage: "You (click to remove) reacted with {emoji_name}"}, context);
|
2020-10-21 15:09:10 +02:00
|
|
|
}
|
|
|
|
context.username = usernames[0];
|
2021-04-13 06:51:54 +02:00
|
|
|
return $t({defaultMessage: "{username} reacted with {emoji_name}"}, context);
|
2016-12-02 13:23:23 +01:00
|
|
|
}
|
|
|
|
|
2020-10-21 15:09:10 +02:00
|
|
|
if (user_ids.length === 2 && current_user_reacted) {
|
|
|
|
context.other_username = usernames[0];
|
2021-04-13 06:51:54 +02:00
|
|
|
return $t(
|
|
|
|
{
|
|
|
|
defaultMessage:
|
|
|
|
"You (click to remove) and {other_username} reacted with {emoji_name}",
|
|
|
|
},
|
2020-10-21 15:09:10 +02:00
|
|
|
context,
|
|
|
|
);
|
2016-12-02 13:23:23 +01:00
|
|
|
}
|
2020-10-21 15:09:10 +02:00
|
|
|
|
|
|
|
context.comma_separated_usernames = _.initial(usernames).join(", ");
|
|
|
|
context.last_username = _.last(usernames);
|
|
|
|
if (current_user_reacted) {
|
2021-04-13 06:51:54 +02:00
|
|
|
return $t(
|
|
|
|
{
|
|
|
|
defaultMessage:
|
|
|
|
"You (click to remove), {comma_separated_usernames} and {last_username} reacted with {emoji_name}",
|
|
|
|
},
|
2020-10-21 15:09:10 +02:00
|
|
|
context,
|
|
|
|
);
|
2016-12-02 13:23:23 +01:00
|
|
|
}
|
2021-04-13 06:51:54 +02:00
|
|
|
return $t(
|
|
|
|
{
|
|
|
|
defaultMessage:
|
|
|
|
"{comma_separated_usernames} and {last_username} reacted with {emoji_name}",
|
|
|
|
},
|
2020-10-21 15:09:10 +02:00
|
|
|
context,
|
|
|
|
);
|
2016-12-02 13:23:23 +01:00
|
|
|
}
|
|
|
|
|
2019-04-04 14:56:54 +02:00
|
|
|
// Add a tooltip showing who reacted to a message.
|
2021-02-28 01:25:00 +01:00
|
|
|
export function get_reaction_title_data(message_id, local_id) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const message = get_message(message_id);
|
reactions: Rewrite code to use clean reactions.
Before this commit, the reactions code would
take the `message.reactions` structure from
the server and try to "collapse" all the reactions
for the same users into the same reactions,
but with each reaction having a list of user_ids.
It was a strangely denormalized structure that
was awkward to work with, and it made it really
hard to reason about whether the data was in
the original structure that the server sent or
the modified structure.
Now we use a cleaner, normalized Map to keep
each reaction (i.e. one per emoji), and we
write that to `message.clean_reactions`.
The `clean_reactions` structure is now the
authoritatize source for all reaction-related
operations. As soon as you try to do anything
with reactions, we build the `clean_reactions`
data on the fly from the server data.
In particular, when we process events, we just
directly manipulate the `clean_reactions` data,
which is much easier to work with, since it's
a Map and doesn't duplicate any data.
This rewrite should avoid some obscure bugs.
I use `r` as shorthand for the clean reaction
structures, so as not to confuse it with
data from the server's message.reactions.
It also avoids some confusion where we use
`reaction` as a var name for the reaction
elements.
2020-03-08 13:13:47 +01:00
|
|
|
|
2020-03-09 14:26:17 +01:00
|
|
|
const r = message.clean_reactions.get(local_id);
|
|
|
|
const user_list = r.user_ids;
|
|
|
|
const emoji_name = r.emoji_name;
|
2019-11-02 00:06:25 +01:00
|
|
|
const title = generate_title(emoji_name, user_list);
|
2019-04-04 14:56:54 +02:00
|
|
|
|
|
|
|
return title;
|
2021-02-28 01:25:00 +01:00
|
|
|
}
|
2019-04-04 14:56:54 +02:00
|
|
|
|
2021-02-28 01:25:00 +01:00
|
|
|
export function get_reaction_section(message_id) {
|
2021-02-03 23:23:32 +01:00
|
|
|
const message_element = $(".message_table").find(`[zid='${CSS.escape(message_id)}']`);
|
2020-07-15 01:29:15 +02:00
|
|
|
const section = message_element.find(".message_reactions");
|
2017-05-29 16:31:51 +02:00
|
|
|
return section;
|
2021-02-28 01:25:00 +01:00
|
|
|
}
|
2017-05-29 16:31:51 +02:00
|
|
|
|
2021-02-28 01:25:00 +01:00
|
|
|
export function find_reaction(message_id, local_id) {
|
|
|
|
const reaction_section = get_reaction_section(message_id);
|
2021-02-03 23:23:32 +01:00
|
|
|
const reaction = reaction_section.find(`[data-reaction-id='${CSS.escape(local_id)}']`);
|
2017-05-29 16:31:51 +02:00
|
|
|
return reaction;
|
2021-02-28 01:25:00 +01:00
|
|
|
}
|
2017-05-29 16:31:51 +02:00
|
|
|
|
2021-02-28 01:25:00 +01:00
|
|
|
export function get_add_reaction_button(message_id) {
|
|
|
|
const reaction_section = get_reaction_section(message_id);
|
2020-07-15 01:29:15 +02:00
|
|
|
const add_button = reaction_section.find(".reaction_button");
|
2017-05-29 16:31:51 +02:00
|
|
|
return add_button;
|
2021-02-28 01:25:00 +01:00
|
|
|
}
|
2017-05-29 16:31:51 +02:00
|
|
|
|
2021-02-28 01:25:00 +01:00
|
|
|
export function set_reaction_count(reaction, count) {
|
2020-07-15 01:29:15 +02:00
|
|
|
const count_element = reaction.find(".message_reaction_count");
|
2018-03-22 22:04:24 +01:00
|
|
|
count_element.text(count);
|
2021-02-28 01:25:00 +01:00
|
|
|
}
|
2017-05-29 19:24:31 +02:00
|
|
|
|
2021-02-28 01:25:00 +01:00
|
|
|
export function add_reaction(event) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const message_id = event.message_id;
|
|
|
|
const message = message_store.get(message_id);
|
2017-10-31 22:33:28 +01:00
|
|
|
|
2017-01-17 09:10:41 +01:00
|
|
|
if (message === undefined) {
|
|
|
|
// If we don't have the message in cache, do nothing; if we
|
|
|
|
// ever fetch it from the server, it'll come with the
|
|
|
|
// latest reactions attached
|
|
|
|
return;
|
|
|
|
}
|
2017-05-29 16:31:51 +02:00
|
|
|
|
2021-02-28 01:25:00 +01:00
|
|
|
set_clean_reactions(message);
|
reactions: Rewrite code to use clean reactions.
Before this commit, the reactions code would
take the `message.reactions` structure from
the server and try to "collapse" all the reactions
for the same users into the same reactions,
but with each reaction having a list of user_ids.
It was a strangely denormalized structure that
was awkward to work with, and it made it really
hard to reason about whether the data was in
the original structure that the server sent or
the modified structure.
Now we use a cleaner, normalized Map to keep
each reaction (i.e. one per emoji), and we
write that to `message.clean_reactions`.
The `clean_reactions` structure is now the
authoritatize source for all reaction-related
operations. As soon as you try to do anything
with reactions, we build the `clean_reactions`
data on the fly from the server data.
In particular, when we process events, we just
directly manipulate the `clean_reactions` data,
which is much easier to work with, since it's
a Map and doesn't duplicate any data.
This rewrite should avoid some obscure bugs.
I use `r` as shorthand for the clean reaction
structures, so as not to confuse it with
data from the server's message.reactions.
It also avoids some confusion where we use
`reaction` as a var name for the reaction
elements.
2020-03-08 13:13:47 +01:00
|
|
|
|
2021-02-28 01:25:00 +01:00
|
|
|
const local_id = get_local_reaction_id(event);
|
2020-04-22 23:24:28 +02:00
|
|
|
const user_id = event.user_id;
|
reactions: Rewrite code to use clean reactions.
Before this commit, the reactions code would
take the `message.reactions` structure from
the server and try to "collapse" all the reactions
for the same users into the same reactions,
but with each reaction having a list of user_ids.
It was a strangely denormalized structure that
was awkward to work with, and it made it really
hard to reason about whether the data was in
the original structure that the server sent or
the modified structure.
Now we use a cleaner, normalized Map to keep
each reaction (i.e. one per emoji), and we
write that to `message.clean_reactions`.
The `clean_reactions` structure is now the
authoritatize source for all reaction-related
operations. As soon as you try to do anything
with reactions, we build the `clean_reactions`
data on the fly from the server data.
In particular, when we process events, we just
directly manipulate the `clean_reactions` data,
which is much easier to work with, since it's
a Map and doesn't duplicate any data.
This rewrite should avoid some obscure bugs.
I use `r` as shorthand for the clean reaction
structures, so as not to confuse it with
data from the server's message.reactions.
It also avoids some confusion where we use
`reaction` as a var name for the reaction
elements.
2020-03-08 13:13:47 +01:00
|
|
|
|
|
|
|
const r = message.clean_reactions.get(local_id);
|
|
|
|
|
|
|
|
if (r && r.user_ids.includes(user_id)) {
|
2017-12-19 00:55:40 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
reactions: Rewrite code to use clean reactions.
Before this commit, the reactions code would
take the `message.reactions` structure from
the server and try to "collapse" all the reactions
for the same users into the same reactions,
but with each reaction having a list of user_ids.
It was a strangely denormalized structure that
was awkward to work with, and it made it really
hard to reason about whether the data was in
the original structure that the server sent or
the modified structure.
Now we use a cleaner, normalized Map to keep
each reaction (i.e. one per emoji), and we
write that to `message.clean_reactions`.
The `clean_reactions` structure is now the
authoritatize source for all reaction-related
operations. As soon as you try to do anything
with reactions, we build the `clean_reactions`
data on the fly from the server data.
In particular, when we process events, we just
directly manipulate the `clean_reactions` data,
which is much easier to work with, since it's
a Map and doesn't duplicate any data.
This rewrite should avoid some obscure bugs.
I use `r` as shorthand for the clean reaction
structures, so as not to confuse it with
data from the server's message.reactions.
It also avoids some confusion where we use
`reaction` as a var name for the reaction
elements.
2020-03-08 13:13:47 +01:00
|
|
|
if (r) {
|
|
|
|
r.user_ids.push(user_id);
|
2021-02-28 01:25:00 +01:00
|
|
|
update_user_fields(r);
|
reactions: Rewrite code to use clean reactions.
Before this commit, the reactions code would
take the `message.reactions` structure from
the server and try to "collapse" all the reactions
for the same users into the same reactions,
but with each reaction having a list of user_ids.
It was a strangely denormalized structure that
was awkward to work with, and it made it really
hard to reason about whether the data was in
the original structure that the server sent or
the modified structure.
Now we use a cleaner, normalized Map to keep
each reaction (i.e. one per emoji), and we
write that to `message.clean_reactions`.
The `clean_reactions` structure is now the
authoritatize source for all reaction-related
operations. As soon as you try to do anything
with reactions, we build the `clean_reactions`
data on the fly from the server data.
In particular, when we process events, we just
directly manipulate the `clean_reactions` data,
which is much easier to work with, since it's
a Map and doesn't duplicate any data.
This rewrite should avoid some obscure bugs.
I use `r` as shorthand for the clean reaction
structures, so as not to confuse it with
data from the server's message.reactions.
It also avoids some confusion where we use
`reaction` as a var name for the reaction
elements.
2020-03-08 13:13:47 +01:00
|
|
|
} else {
|
2021-02-28 01:25:00 +01:00
|
|
|
add_clean_reaction({
|
2020-07-20 22:18:43 +02:00
|
|
|
message,
|
|
|
|
local_id,
|
reactions: Rewrite code to use clean reactions.
Before this commit, the reactions code would
take the `message.reactions` structure from
the server and try to "collapse" all the reactions
for the same users into the same reactions,
but with each reaction having a list of user_ids.
It was a strangely denormalized structure that
was awkward to work with, and it made it really
hard to reason about whether the data was in
the original structure that the server sent or
the modified structure.
Now we use a cleaner, normalized Map to keep
each reaction (i.e. one per emoji), and we
write that to `message.clean_reactions`.
The `clean_reactions` structure is now the
authoritatize source for all reaction-related
operations. As soon as you try to do anything
with reactions, we build the `clean_reactions`
data on the fly from the server data.
In particular, when we process events, we just
directly manipulate the `clean_reactions` data,
which is much easier to work with, since it's
a Map and doesn't duplicate any data.
This rewrite should avoid some obscure bugs.
I use `r` as shorthand for the clean reaction
structures, so as not to confuse it with
data from the server's message.reactions.
It also avoids some confusion where we use
`reaction` as a var name for the reaction
elements.
2020-03-08 13:13:47 +01:00
|
|
|
user_ids: [user_id],
|
|
|
|
reaction_type: event.reaction_type,
|
|
|
|
emoji_name: event.emoji_name,
|
|
|
|
emoji_code: event.emoji_code,
|
|
|
|
});
|
|
|
|
}
|
2017-05-29 16:31:51 +02:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const opts = {
|
2020-07-20 22:18:43 +02:00
|
|
|
message_id,
|
2017-10-31 22:33:28 +01:00
|
|
|
reaction_type: event.reaction_type,
|
|
|
|
emoji_name: event.emoji_name,
|
|
|
|
emoji_code: event.emoji_code,
|
2020-07-20 22:18:43 +02:00
|
|
|
user_id,
|
2017-10-31 22:33:28 +01:00
|
|
|
};
|
2017-05-29 16:31:51 +02:00
|
|
|
|
reactions: Rewrite code to use clean reactions.
Before this commit, the reactions code would
take the `message.reactions` structure from
the server and try to "collapse" all the reactions
for the same users into the same reactions,
but with each reaction having a list of user_ids.
It was a strangely denormalized structure that
was awkward to work with, and it made it really
hard to reason about whether the data was in
the original structure that the server sent or
the modified structure.
Now we use a cleaner, normalized Map to keep
each reaction (i.e. one per emoji), and we
write that to `message.clean_reactions`.
The `clean_reactions` structure is now the
authoritatize source for all reaction-related
operations. As soon as you try to do anything
with reactions, we build the `clean_reactions`
data on the fly from the server data.
In particular, when we process events, we just
directly manipulate the `clean_reactions` data,
which is much easier to work with, since it's
a Map and doesn't duplicate any data.
This rewrite should avoid some obscure bugs.
I use `r` as shorthand for the clean reaction
structures, so as not to confuse it with
data from the server's message.reactions.
It also avoids some confusion where we use
`reaction` as a var name for the reaction
elements.
2020-03-08 13:13:47 +01:00
|
|
|
if (r) {
|
|
|
|
opts.user_list = r.user_ids;
|
2021-02-28 01:25:00 +01:00
|
|
|
view.update_existing_reaction(opts);
|
2016-12-02 13:23:23 +01:00
|
|
|
} else {
|
2021-02-28 01:25:00 +01:00
|
|
|
view.insert_new_reaction(opts);
|
2016-12-02 13:23:23 +01:00
|
|
|
}
|
2021-02-28 01:25:00 +01:00
|
|
|
}
|
2016-12-02 13:23:23 +01:00
|
|
|
|
2021-02-28 01:25:00 +01:00
|
|
|
view.update_existing_reaction = function (opts) {
|
2017-05-29 16:31:51 +02:00
|
|
|
// 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.
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const message_id = opts.message_id;
|
|
|
|
const emoji_name = opts.emoji_name;
|
|
|
|
const user_list = opts.user_list;
|
|
|
|
const user_id = opts.user_id;
|
2021-02-28 01:25:00 +01:00
|
|
|
const local_id = get_local_reaction_id(opts);
|
|
|
|
const reaction = find_reaction(message_id, local_id);
|
2017-05-29 16:31:51 +02:00
|
|
|
|
2021-02-28 01:25:00 +01:00
|
|
|
set_reaction_count(reaction, user_list.length);
|
2017-05-29 16:31:51 +02:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const new_label = generate_title(emoji_name, user_list);
|
2020-07-15 01:29:15 +02:00
|
|
|
reaction.attr("aria-label", new_label);
|
2017-05-29 16:31:51 +02:00
|
|
|
|
2017-06-28 20:10:05 +02:00
|
|
|
if (user_id === page_params.user_id) {
|
2017-05-29 16:31:51 +02:00
|
|
|
reaction.addClass("reacted");
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-02-28 01:25:00 +01:00
|
|
|
view.insert_new_reaction = function (opts) {
|
2017-05-29 16:31:51 +02:00
|
|
|
// Our caller ensures we are the first user to react to this
|
|
|
|
// message with this emoji, and it populates user_list for
|
|
|
|
// us. We then render the emoji/title/count and insert it
|
|
|
|
// before the add button.
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const message_id = opts.message_id;
|
|
|
|
const user_id = opts.user_id;
|
|
|
|
const user_list = [user_id];
|
2017-05-29 16:31:51 +02:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const context = {
|
2020-07-20 22:18:43 +02:00
|
|
|
message_id,
|
2021-11-20 20:13:59 +01:00
|
|
|
...emoji.get_emoji_details_for_rendering(opts),
|
2017-06-28 21:07:26 +02:00
|
|
|
};
|
|
|
|
|
2021-11-20 20:13:59 +01:00
|
|
|
const new_label = generate_title(opts.emoji_name, user_list);
|
2017-05-29 16:31:51 +02:00
|
|
|
|
2017-06-28 21:07:26 +02:00
|
|
|
context.count = 1;
|
2019-04-04 14:56:54 +02:00
|
|
|
context.label = new_label;
|
2021-02-28 01:25:00 +01:00
|
|
|
context.local_id = get_local_reaction_id(opts);
|
2021-07-28 16:00:58 +02:00
|
|
|
context.emoji_alt_code = user_settings.emojiset === "text";
|
2021-11-20 20:13:59 +01:00
|
|
|
context.is_realm_emoji =
|
|
|
|
context.reaction_type === "realm_emoji" || context.reaction_type === "zulip_extra_emoji";
|
2017-05-29 16:31:51 +02:00
|
|
|
|
2017-06-28 21:29:45 +02:00
|
|
|
if (opts.user_id === page_params.user_id) {
|
2017-06-28 21:07:26 +02:00
|
|
|
context.class = "message_reaction reacted";
|
2017-05-29 16:31:51 +02:00
|
|
|
} else {
|
2017-06-28 21:07:26 +02:00
|
|
|
context.class = "message_reaction";
|
2017-05-29 16:31:51 +02:00
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const new_reaction = $(render_message_reaction(context));
|
2017-05-29 16:31:51 +02:00
|
|
|
|
|
|
|
// Now insert it before the add button.
|
2021-02-28 01:25:00 +01:00
|
|
|
const reaction_button_element = get_add_reaction_button(message_id);
|
2017-05-29 16:31:51 +02:00
|
|
|
new_reaction.insertBefore(reaction_button_element);
|
|
|
|
};
|
|
|
|
|
2021-02-28 01:25:00 +01:00
|
|
|
export function remove_reaction(event) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const reaction_type = event.reaction_type;
|
|
|
|
const emoji_name = event.emoji_name;
|
|
|
|
const emoji_code = event.emoji_code;
|
|
|
|
const message_id = event.message_id;
|
2020-04-22 23:24:28 +02:00
|
|
|
const user_id = event.user_id;
|
2019-11-02 00:06:25 +01:00
|
|
|
const message = message_store.get(message_id);
|
2021-02-28 01:25:00 +01:00
|
|
|
const local_id = get_local_reaction_id(event);
|
2017-05-29 16:56:13 +02:00
|
|
|
|
2017-01-17 09:10:41 +01:00
|
|
|
if (message === undefined) {
|
|
|
|
// If we don't have the message in cache, do nothing; if we
|
|
|
|
// ever fetch it from the server, it'll come with the
|
|
|
|
// latest reactions attached
|
|
|
|
return;
|
|
|
|
}
|
2017-05-29 16:56:13 +02:00
|
|
|
|
2021-02-28 01:25:00 +01:00
|
|
|
set_clean_reactions(message);
|
reactions: Rewrite code to use clean reactions.
Before this commit, the reactions code would
take the `message.reactions` structure from
the server and try to "collapse" all the reactions
for the same users into the same reactions,
but with each reaction having a list of user_ids.
It was a strangely denormalized structure that
was awkward to work with, and it made it really
hard to reason about whether the data was in
the original structure that the server sent or
the modified structure.
Now we use a cleaner, normalized Map to keep
each reaction (i.e. one per emoji), and we
write that to `message.clean_reactions`.
The `clean_reactions` structure is now the
authoritatize source for all reaction-related
operations. As soon as you try to do anything
with reactions, we build the `clean_reactions`
data on the fly from the server data.
In particular, when we process events, we just
directly manipulate the `clean_reactions` data,
which is much easier to work with, since it's
a Map and doesn't duplicate any data.
This rewrite should avoid some obscure bugs.
I use `r` as shorthand for the clean reaction
structures, so as not to confuse it with
data from the server's message.reactions.
It also avoids some confusion where we use
`reaction` as a var name for the reaction
elements.
2020-03-08 13:13:47 +01:00
|
|
|
|
|
|
|
const r = message.clean_reactions.get(local_id);
|
|
|
|
|
|
|
|
if (!r) {
|
2017-12-19 00:55:40 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
reactions: Rewrite code to use clean reactions.
Before this commit, the reactions code would
take the `message.reactions` structure from
the server and try to "collapse" all the reactions
for the same users into the same reactions,
but with each reaction having a list of user_ids.
It was a strangely denormalized structure that
was awkward to work with, and it made it really
hard to reason about whether the data was in
the original structure that the server sent or
the modified structure.
Now we use a cleaner, normalized Map to keep
each reaction (i.e. one per emoji), and we
write that to `message.clean_reactions`.
The `clean_reactions` structure is now the
authoritatize source for all reaction-related
operations. As soon as you try to do anything
with reactions, we build the `clean_reactions`
data on the fly from the server data.
In particular, when we process events, we just
directly manipulate the `clean_reactions` data,
which is much easier to work with, since it's
a Map and doesn't duplicate any data.
This rewrite should avoid some obscure bugs.
I use `r` as shorthand for the clean reaction
structures, so as not to confuse it with
data from the server's message.reactions.
It also avoids some confusion where we use
`reaction` as a var name for the reaction
elements.
2020-03-08 13:13:47 +01:00
|
|
|
if (!r.user_ids.includes(user_id)) {
|
|
|
|
return;
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
}
|
2017-05-29 16:56:13 +02:00
|
|
|
|
2020-07-02 01:39:34 +02:00
|
|
|
r.user_ids = r.user_ids.filter((id) => id !== user_id);
|
reactions: Rewrite code to use clean reactions.
Before this commit, the reactions code would
take the `message.reactions` structure from
the server and try to "collapse" all the reactions
for the same users into the same reactions,
but with each reaction having a list of user_ids.
It was a strangely denormalized structure that
was awkward to work with, and it made it really
hard to reason about whether the data was in
the original structure that the server sent or
the modified structure.
Now we use a cleaner, normalized Map to keep
each reaction (i.e. one per emoji), and we
write that to `message.clean_reactions`.
The `clean_reactions` structure is now the
authoritatize source for all reaction-related
operations. As soon as you try to do anything
with reactions, we build the `clean_reactions`
data on the fly from the server data.
In particular, when we process events, we just
directly manipulate the `clean_reactions` data,
which is much easier to work with, since it's
a Map and doesn't duplicate any data.
This rewrite should avoid some obscure bugs.
I use `r` as shorthand for the clean reaction
structures, so as not to confuse it with
data from the server's message.reactions.
It also avoids some confusion where we use
`reaction` as a var name for the reaction
elements.
2020-03-08 13:13:47 +01:00
|
|
|
if (r.user_ids.length > 0) {
|
2021-02-28 01:25:00 +01:00
|
|
|
update_user_fields(r);
|
reactions: Rewrite code to use clean reactions.
Before this commit, the reactions code would
take the `message.reactions` structure from
the server and try to "collapse" all the reactions
for the same users into the same reactions,
but with each reaction having a list of user_ids.
It was a strangely denormalized structure that
was awkward to work with, and it made it really
hard to reason about whether the data was in
the original structure that the server sent or
the modified structure.
Now we use a cleaner, normalized Map to keep
each reaction (i.e. one per emoji), and we
write that to `message.clean_reactions`.
The `clean_reactions` structure is now the
authoritatize source for all reaction-related
operations. As soon as you try to do anything
with reactions, we build the `clean_reactions`
data on the fly from the server data.
In particular, when we process events, we just
directly manipulate the `clean_reactions` data,
which is much easier to work with, since it's
a Map and doesn't duplicate any data.
This rewrite should avoid some obscure bugs.
I use `r` as shorthand for the clean reaction
structures, so as not to confuse it with
data from the server's message.reactions.
It also avoids some confusion where we use
`reaction` as a var name for the reaction
elements.
2020-03-08 13:13:47 +01:00
|
|
|
} else {
|
|
|
|
message.clean_reactions.delete(local_id);
|
2016-12-02 13:23:23 +01:00
|
|
|
}
|
2017-05-29 16:56:13 +02:00
|
|
|
|
2021-02-28 01:25:00 +01:00
|
|
|
view.remove_reaction({
|
2020-07-20 22:18:43 +02:00
|
|
|
message_id,
|
|
|
|
reaction_type,
|
|
|
|
emoji_name,
|
|
|
|
emoji_code,
|
reactions: Rewrite code to use clean reactions.
Before this commit, the reactions code would
take the `message.reactions` structure from
the server and try to "collapse" all the reactions
for the same users into the same reactions,
but with each reaction having a list of user_ids.
It was a strangely denormalized structure that
was awkward to work with, and it made it really
hard to reason about whether the data was in
the original structure that the server sent or
the modified structure.
Now we use a cleaner, normalized Map to keep
each reaction (i.e. one per emoji), and we
write that to `message.clean_reactions`.
The `clean_reactions` structure is now the
authoritatize source for all reaction-related
operations. As soon as you try to do anything
with reactions, we build the `clean_reactions`
data on the fly from the server data.
In particular, when we process events, we just
directly manipulate the `clean_reactions` data,
which is much easier to work with, since it's
a Map and doesn't duplicate any data.
This rewrite should avoid some obscure bugs.
I use `r` as shorthand for the clean reaction
structures, so as not to confuse it with
data from the server's message.reactions.
It also avoids some confusion where we use
`reaction` as a var name for the reaction
elements.
2020-03-08 13:13:47 +01:00
|
|
|
user_list: r.user_ids,
|
2020-07-20 22:18:43 +02:00
|
|
|
user_id,
|
2017-06-28 21:41:35 +02:00
|
|
|
});
|
2021-02-28 01:25:00 +01:00
|
|
|
}
|
2017-06-28 21:41:35 +02:00
|
|
|
|
2021-02-28 01:25:00 +01:00
|
|
|
view.remove_reaction = function (opts) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const message_id = opts.message_id;
|
|
|
|
const emoji_name = opts.emoji_name;
|
|
|
|
const user_list = opts.user_list;
|
|
|
|
const user_id = opts.user_id;
|
2021-02-28 01:25:00 +01:00
|
|
|
const local_id = get_local_reaction_id(opts);
|
|
|
|
const reaction = find_reaction(message_id, local_id);
|
2017-05-29 16:56:13 +02:00
|
|
|
|
|
|
|
if (user_list.length === 0) {
|
|
|
|
// If this user was the only one reacting for this emoji, we simply
|
|
|
|
// remove the reaction and exit.
|
|
|
|
reaction.remove();
|
|
|
|
return;
|
2017-02-13 07:51:40 +01:00
|
|
|
}
|
2017-05-29 16:56:13 +02:00
|
|
|
|
|
|
|
// The emoji still has reactions from other users, so we need to update
|
|
|
|
// the title/count and, if the user is the current user, turn off the
|
|
|
|
// "reacted" class.
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const new_label = generate_title(emoji_name, user_list);
|
2020-07-15 01:29:15 +02:00
|
|
|
reaction.attr("aria-label", new_label);
|
2019-04-04 14:56:54 +02:00
|
|
|
|
|
|
|
// If the user is the current user, turn off the "reacted" class.
|
2017-05-29 16:56:13 +02:00
|
|
|
|
2021-02-28 01:25:00 +01:00
|
|
|
set_reaction_count(reaction, user_list.length);
|
2017-05-29 16:56:13 +02:00
|
|
|
|
|
|
|
if (user_id === page_params.user_id) {
|
|
|
|
reaction.removeClass("reacted");
|
2016-12-02 13:23:23 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-02-28 01:25:00 +01:00
|
|
|
export function get_emojis_used_by_user_for_message_id(message_id) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const user_id = page_params.user_id;
|
|
|
|
const message = message_store.get(message_id);
|
2021-02-28 01:25:00 +01:00
|
|
|
set_clean_reactions(message);
|
reactions: Rewrite code to use clean reactions.
Before this commit, the reactions code would
take the `message.reactions` structure from
the server and try to "collapse" all the reactions
for the same users into the same reactions,
but with each reaction having a list of user_ids.
It was a strangely denormalized structure that
was awkward to work with, and it made it really
hard to reason about whether the data was in
the original structure that the server sent or
the modified structure.
Now we use a cleaner, normalized Map to keep
each reaction (i.e. one per emoji), and we
write that to `message.clean_reactions`.
The `clean_reactions` structure is now the
authoritatize source for all reaction-related
operations. As soon as you try to do anything
with reactions, we build the `clean_reactions`
data on the fly from the server data.
In particular, when we process events, we just
directly manipulate the `clean_reactions` data,
which is much easier to work with, since it's
a Map and doesn't duplicate any data.
This rewrite should avoid some obscure bugs.
I use `r` as shorthand for the clean reaction
structures, so as not to confuse it with
data from the server's message.reactions.
It also avoids some confusion where we use
`reaction` as a var name for the reaction
elements.
2020-03-08 13:13:47 +01:00
|
|
|
|
|
|
|
const names = [];
|
|
|
|
for (const r of message.clean_reactions.values()) {
|
|
|
|
if (r.user_ids.includes(user_id)) {
|
|
|
|
names.push(r.emoji_name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return names;
|
2021-02-28 01:25:00 +01:00
|
|
|
}
|
reactions: Rewrite code to use clean reactions.
Before this commit, the reactions code would
take the `message.reactions` structure from
the server and try to "collapse" all the reactions
for the same users into the same reactions,
but with each reaction having a list of user_ids.
It was a strangely denormalized structure that
was awkward to work with, and it made it really
hard to reason about whether the data was in
the original structure that the server sent or
the modified structure.
Now we use a cleaner, normalized Map to keep
each reaction (i.e. one per emoji), and we
write that to `message.clean_reactions`.
The `clean_reactions` structure is now the
authoritatize source for all reaction-related
operations. As soon as you try to do anything
with reactions, we build the `clean_reactions`
data on the fly from the server data.
In particular, when we process events, we just
directly manipulate the `clean_reactions` data,
which is much easier to work with, since it's
a Map and doesn't duplicate any data.
This rewrite should avoid some obscure bugs.
I use `r` as shorthand for the clean reaction
structures, so as not to confuse it with
data from the server's message.reactions.
It also avoids some confusion where we use
`reaction` as a var name for the reaction
elements.
2020-03-08 13:13:47 +01:00
|
|
|
|
2021-02-28 01:25:00 +01:00
|
|
|
export function get_message_reactions(message) {
|
|
|
|
set_clean_reactions(message);
|
reactions: Rewrite code to use clean reactions.
Before this commit, the reactions code would
take the `message.reactions` structure from
the server and try to "collapse" all the reactions
for the same users into the same reactions,
but with each reaction having a list of user_ids.
It was a strangely denormalized structure that
was awkward to work with, and it made it really
hard to reason about whether the data was in
the original structure that the server sent or
the modified structure.
Now we use a cleaner, normalized Map to keep
each reaction (i.e. one per emoji), and we
write that to `message.clean_reactions`.
The `clean_reactions` structure is now the
authoritatize source for all reaction-related
operations. As soon as you try to do anything
with reactions, we build the `clean_reactions`
data on the fly from the server data.
In particular, when we process events, we just
directly manipulate the `clean_reactions` data,
which is much easier to work with, since it's
a Map and doesn't duplicate any data.
This rewrite should avoid some obscure bugs.
I use `r` as shorthand for the clean reaction
structures, so as not to confuse it with
data from the server's message.reactions.
It also avoids some confusion where we use
`reaction` as a var name for the reaction
elements.
2020-03-08 13:13:47 +01:00
|
|
|
return Array.from(message.clean_reactions.values());
|
2021-02-28 01:25:00 +01:00
|
|
|
}
|
reactions: Rewrite code to use clean reactions.
Before this commit, the reactions code would
take the `message.reactions` structure from
the server and try to "collapse" all the reactions
for the same users into the same reactions,
but with each reaction having a list of user_ids.
It was a strangely denormalized structure that
was awkward to work with, and it made it really
hard to reason about whether the data was in
the original structure that the server sent or
the modified structure.
Now we use a cleaner, normalized Map to keep
each reaction (i.e. one per emoji), and we
write that to `message.clean_reactions`.
The `clean_reactions` structure is now the
authoritatize source for all reaction-related
operations. As soon as you try to do anything
with reactions, we build the `clean_reactions`
data on the fly from the server data.
In particular, when we process events, we just
directly manipulate the `clean_reactions` data,
which is much easier to work with, since it's
a Map and doesn't duplicate any data.
This rewrite should avoid some obscure bugs.
I use `r` as shorthand for the clean reaction
structures, so as not to confuse it with
data from the server's message.reactions.
It also avoids some confusion where we use
`reaction` as a var name for the reaction
elements.
2020-03-08 13:13:47 +01:00
|
|
|
|
2021-02-28 01:25:00 +01:00
|
|
|
export function set_clean_reactions(message) {
|
reactions: Rewrite code to use clean reactions.
Before this commit, the reactions code would
take the `message.reactions` structure from
the server and try to "collapse" all the reactions
for the same users into the same reactions,
but with each reaction having a list of user_ids.
It was a strangely denormalized structure that
was awkward to work with, and it made it really
hard to reason about whether the data was in
the original structure that the server sent or
the modified structure.
Now we use a cleaner, normalized Map to keep
each reaction (i.e. one per emoji), and we
write that to `message.clean_reactions`.
The `clean_reactions` structure is now the
authoritatize source for all reaction-related
operations. As soon as you try to do anything
with reactions, we build the `clean_reactions`
data on the fly from the server data.
In particular, when we process events, we just
directly manipulate the `clean_reactions` data,
which is much easier to work with, since it's
a Map and doesn't duplicate any data.
This rewrite should avoid some obscure bugs.
I use `r` as shorthand for the clean reaction
structures, so as not to confuse it with
data from the server's message.reactions.
It also avoids some confusion where we use
`reaction` as a var name for the reaction
elements.
2020-03-08 13:13:47 +01:00
|
|
|
/*
|
|
|
|
The server sends us a single structure for
|
|
|
|
each reaction, even if two users are reacting
|
|
|
|
with the same emoji. Our first loop creates
|
|
|
|
a map of distinct reactions and a map of
|
|
|
|
local_id -> user_ids. The `local_id` is
|
|
|
|
basically a key for the emoji name.
|
|
|
|
|
|
|
|
Then in our second loop we build a more compact
|
|
|
|
data structure that's easier for our message
|
|
|
|
list view templates to work with.
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (message.clean_reactions) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const distinct_reactions = new Map();
|
|
|
|
const user_map = new Map();
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
|
|
|
|
for (const reaction of message.reactions) {
|
2021-02-28 01:25:00 +01:00
|
|
|
const local_id = get_local_reaction_id(reaction);
|
2020-04-22 23:24:28 +02:00
|
|
|
const user_id = reaction.user_id;
|
reactions: Rewrite code to use clean reactions.
Before this commit, the reactions code would
take the `message.reactions` structure from
the server and try to "collapse" all the reactions
for the same users into the same reactions,
but with each reaction having a list of user_ids.
It was a strangely denormalized structure that
was awkward to work with, and it made it really
hard to reason about whether the data was in
the original structure that the server sent or
the modified structure.
Now we use a cleaner, normalized Map to keep
each reaction (i.e. one per emoji), and we
write that to `message.clean_reactions`.
The `clean_reactions` structure is now the
authoritatize source for all reaction-related
operations. As soon as you try to do anything
with reactions, we build the `clean_reactions`
data on the fly from the server data.
In particular, when we process events, we just
directly manipulate the `clean_reactions` data,
which is much easier to work with, since it's
a Map and doesn't duplicate any data.
This rewrite should avoid some obscure bugs.
I use `r` as shorthand for the clean reaction
structures, so as not to confuse it with
data from the server's message.reactions.
It also avoids some confusion where we use
`reaction` as a var name for the reaction
elements.
2020-03-08 13:13:47 +01:00
|
|
|
|
2017-03-26 20:38:47 +02:00
|
|
|
if (!people.is_known_user_id(user_id)) {
|
2020-07-15 00:34:28 +02:00
|
|
|
blueslip.warn("Unknown user_id " + user_id + " in reaction for message " + message.id);
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
continue;
|
2017-03-26 20:38:47 +02:00
|
|
|
}
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
|
reactions: Rewrite code to use clean reactions.
Before this commit, the reactions code would
take the `message.reactions` structure from
the server and try to "collapse" all the reactions
for the same users into the same reactions,
but with each reaction having a list of user_ids.
It was a strangely denormalized structure that
was awkward to work with, and it made it really
hard to reason about whether the data was in
the original structure that the server sent or
the modified structure.
Now we use a cleaner, normalized Map to keep
each reaction (i.e. one per emoji), and we
write that to `message.clean_reactions`.
The `clean_reactions` structure is now the
authoritatize source for all reaction-related
operations. As soon as you try to do anything
with reactions, we build the `clean_reactions`
data on the fly from the server data.
In particular, when we process events, we just
directly manipulate the `clean_reactions` data,
which is much easier to work with, since it's
a Map and doesn't duplicate any data.
This rewrite should avoid some obscure bugs.
I use `r` as shorthand for the clean reaction
structures, so as not to confuse it with
data from the server's message.reactions.
It also avoids some confusion where we use
`reaction` as a var name for the reaction
elements.
2020-03-08 13:13:47 +01:00
|
|
|
if (!distinct_reactions.has(local_id)) {
|
|
|
|
distinct_reactions.set(local_id, reaction);
|
|
|
|
user_map.set(local_id, []);
|
2016-12-02 13:23:23 +01:00
|
|
|
}
|
reactions: Rewrite code to use clean reactions.
Before this commit, the reactions code would
take the `message.reactions` structure from
the server and try to "collapse" all the reactions
for the same users into the same reactions,
but with each reaction having a list of user_ids.
It was a strangely denormalized structure that
was awkward to work with, and it made it really
hard to reason about whether the data was in
the original structure that the server sent or
the modified structure.
Now we use a cleaner, normalized Map to keep
each reaction (i.e. one per emoji), and we
write that to `message.clean_reactions`.
The `clean_reactions` structure is now the
authoritatize source for all reaction-related
operations. As soon as you try to do anything
with reactions, we build the `clean_reactions`
data on the fly from the server data.
In particular, when we process events, we just
directly manipulate the `clean_reactions` data,
which is much easier to work with, since it's
a Map and doesn't duplicate any data.
This rewrite should avoid some obscure bugs.
I use `r` as shorthand for the clean reaction
structures, so as not to confuse it with
data from the server's message.reactions.
It also avoids some confusion where we use
`reaction` as a var name for the reaction
elements.
2020-03-08 13:13:47 +01:00
|
|
|
|
|
|
|
const user_ids = user_map.get(local_id);
|
|
|
|
|
|
|
|
if (user_ids.includes(user_id)) {
|
2020-07-15 00:34:28 +02:00
|
|
|
blueslip.error(
|
|
|
|
"server sent duplicate reactions for user " + user_id + " (key=" + local_id + ")",
|
|
|
|
);
|
reactions: Rewrite code to use clean reactions.
Before this commit, the reactions code would
take the `message.reactions` structure from
the server and try to "collapse" all the reactions
for the same users into the same reactions,
but with each reaction having a list of user_ids.
It was a strangely denormalized structure that
was awkward to work with, and it made it really
hard to reason about whether the data was in
the original structure that the server sent or
the modified structure.
Now we use a cleaner, normalized Map to keep
each reaction (i.e. one per emoji), and we
write that to `message.clean_reactions`.
The `clean_reactions` structure is now the
authoritatize source for all reaction-related
operations. As soon as you try to do anything
with reactions, we build the `clean_reactions`
data on the fly from the server data.
In particular, when we process events, we just
directly manipulate the `clean_reactions` data,
which is much easier to work with, since it's
a Map and doesn't duplicate any data.
This rewrite should avoid some obscure bugs.
I use `r` as shorthand for the clean reaction
structures, so as not to confuse it with
data from the server's message.reactions.
It also avoids some confusion where we use
`reaction` as a var name for the reaction
elements.
2020-03-08 13:13:47 +01:00
|
|
|
continue;
|
2017-02-13 07:51:40 +01:00
|
|
|
}
|
reactions: Rewrite code to use clean reactions.
Before this commit, the reactions code would
take the `message.reactions` structure from
the server and try to "collapse" all the reactions
for the same users into the same reactions,
but with each reaction having a list of user_ids.
It was a strangely denormalized structure that
was awkward to work with, and it made it really
hard to reason about whether the data was in
the original structure that the server sent or
the modified structure.
Now we use a cleaner, normalized Map to keep
each reaction (i.e. one per emoji), and we
write that to `message.clean_reactions`.
The `clean_reactions` structure is now the
authoritatize source for all reaction-related
operations. As soon as you try to do anything
with reactions, we build the `clean_reactions`
data on the fly from the server data.
In particular, when we process events, we just
directly manipulate the `clean_reactions` data,
which is much easier to work with, since it's
a Map and doesn't duplicate any data.
This rewrite should avoid some obscure bugs.
I use `r` as shorthand for the clean reaction
structures, so as not to confuse it with
data from the server's message.reactions.
It also avoids some confusion where we use
`reaction` as a var name for the reaction
elements.
2020-03-08 13:13:47 +01:00
|
|
|
|
|
|
|
user_ids.push(user_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
It might feel a little janky to attach clean_reactions
|
|
|
|
directly to the message object, but this allows the
|
|
|
|
server to send us a new copy of the message, and then
|
|
|
|
the next time we try to get reactions from it, we
|
|
|
|
won't have `clean_reactions`, and we will re-process
|
|
|
|
the server's latest copy of the reactions.
|
|
|
|
*/
|
|
|
|
message.clean_reactions = new Map();
|
|
|
|
|
|
|
|
for (const local_id of distinct_reactions.keys()) {
|
|
|
|
const reaction = distinct_reactions.get(local_id);
|
|
|
|
const user_ids = user_map.get(local_id);
|
|
|
|
|
2021-02-28 01:25:00 +01:00
|
|
|
add_clean_reaction({
|
2020-07-20 22:18:43 +02:00
|
|
|
message,
|
|
|
|
local_id,
|
|
|
|
user_ids,
|
reactions: Rewrite code to use clean reactions.
Before this commit, the reactions code would
take the `message.reactions` structure from
the server and try to "collapse" all the reactions
for the same users into the same reactions,
but with each reaction having a list of user_ids.
It was a strangely denormalized structure that
was awkward to work with, and it made it really
hard to reason about whether the data was in
the original structure that the server sent or
the modified structure.
Now we use a cleaner, normalized Map to keep
each reaction (i.e. one per emoji), and we
write that to `message.clean_reactions`.
The `clean_reactions` structure is now the
authoritatize source for all reaction-related
operations. As soon as you try to do anything
with reactions, we build the `clean_reactions`
data on the fly from the server data.
In particular, when we process events, we just
directly manipulate the `clean_reactions` data,
which is much easier to work with, since it's
a Map and doesn't duplicate any data.
This rewrite should avoid some obscure bugs.
I use `r` as shorthand for the clean reaction
structures, so as not to confuse it with
data from the server's message.reactions.
It also avoids some confusion where we use
`reaction` as a var name for the reaction
elements.
2020-03-08 13:13:47 +01:00
|
|
|
reaction_type: reaction.reaction_type,
|
|
|
|
emoji_name: reaction.emoji_name,
|
|
|
|
emoji_code: reaction.emoji_code,
|
|
|
|
});
|
|
|
|
}
|
2021-02-28 01:25:00 +01:00
|
|
|
}
|
reactions: Rewrite code to use clean reactions.
Before this commit, the reactions code would
take the `message.reactions` structure from
the server and try to "collapse" all the reactions
for the same users into the same reactions,
but with each reaction having a list of user_ids.
It was a strangely denormalized structure that
was awkward to work with, and it made it really
hard to reason about whether the data was in
the original structure that the server sent or
the modified structure.
Now we use a cleaner, normalized Map to keep
each reaction (i.e. one per emoji), and we
write that to `message.clean_reactions`.
The `clean_reactions` structure is now the
authoritatize source for all reaction-related
operations. As soon as you try to do anything
with reactions, we build the `clean_reactions`
data on the fly from the server data.
In particular, when we process events, we just
directly manipulate the `clean_reactions` data,
which is much easier to work with, since it's
a Map and doesn't duplicate any data.
This rewrite should avoid some obscure bugs.
I use `r` as shorthand for the clean reaction
structures, so as not to confuse it with
data from the server's message.reactions.
It also avoids some confusion where we use
`reaction` as a var name for the reaction
elements.
2020-03-08 13:13:47 +01:00
|
|
|
|
2021-02-28 01:25:00 +01:00
|
|
|
export function add_clean_reaction(opts) {
|
2021-11-20 20:38:00 +01:00
|
|
|
const r = emoji.get_emoji_details_for_rendering(opts);
|
reactions: Rewrite code to use clean reactions.
Before this commit, the reactions code would
take the `message.reactions` structure from
the server and try to "collapse" all the reactions
for the same users into the same reactions,
but with each reaction having a list of user_ids.
It was a strangely denormalized structure that
was awkward to work with, and it made it really
hard to reason about whether the data was in
the original structure that the server sent or
the modified structure.
Now we use a cleaner, normalized Map to keep
each reaction (i.e. one per emoji), and we
write that to `message.clean_reactions`.
The `clean_reactions` structure is now the
authoritatize source for all reaction-related
operations. As soon as you try to do anything
with reactions, we build the `clean_reactions`
data on the fly from the server data.
In particular, when we process events, we just
directly manipulate the `clean_reactions` data,
which is much easier to work with, since it's
a Map and doesn't duplicate any data.
This rewrite should avoid some obscure bugs.
I use `r` as shorthand for the clean reaction
structures, so as not to confuse it with
data from the server's message.reactions.
It also avoids some confusion where we use
`reaction` as a var name for the reaction
elements.
2020-03-08 13:13:47 +01:00
|
|
|
|
|
|
|
r.local_id = opts.local_id;
|
|
|
|
|
|
|
|
r.user_ids = opts.user_ids;
|
2021-02-28 01:25:00 +01:00
|
|
|
update_user_fields(r);
|
reactions: Rewrite code to use clean reactions.
Before this commit, the reactions code would
take the `message.reactions` structure from
the server and try to "collapse" all the reactions
for the same users into the same reactions,
but with each reaction having a list of user_ids.
It was a strangely denormalized structure that
was awkward to work with, and it made it really
hard to reason about whether the data was in
the original structure that the server sent or
the modified structure.
Now we use a cleaner, normalized Map to keep
each reaction (i.e. one per emoji), and we
write that to `message.clean_reactions`.
The `clean_reactions` structure is now the
authoritatize source for all reaction-related
operations. As soon as you try to do anything
with reactions, we build the `clean_reactions`
data on the fly from the server data.
In particular, when we process events, we just
directly manipulate the `clean_reactions` data,
which is much easier to work with, since it's
a Map and doesn't duplicate any data.
This rewrite should avoid some obscure bugs.
I use `r` as shorthand for the clean reaction
structures, so as not to confuse it with
data from the server's message.reactions.
It also avoids some confusion where we use
`reaction` as a var name for the reaction
elements.
2020-03-08 13:13:47 +01:00
|
|
|
|
2021-07-28 16:00:58 +02:00
|
|
|
r.emoji_alt_code = user_settings.emojiset === "text";
|
2021-11-20 20:38:00 +01:00
|
|
|
r.is_realm_emoji = r.reaction_type === "realm_emoji" || r.reaction_type === "zulip_extra_emoji";
|
reactions: Rewrite code to use clean reactions.
Before this commit, the reactions code would
take the `message.reactions` structure from
the server and try to "collapse" all the reactions
for the same users into the same reactions,
but with each reaction having a list of user_ids.
It was a strangely denormalized structure that
was awkward to work with, and it made it really
hard to reason about whether the data was in
the original structure that the server sent or
the modified structure.
Now we use a cleaner, normalized Map to keep
each reaction (i.e. one per emoji), and we
write that to `message.clean_reactions`.
The `clean_reactions` structure is now the
authoritatize source for all reaction-related
operations. As soon as you try to do anything
with reactions, we build the `clean_reactions`
data on the fly from the server data.
In particular, when we process events, we just
directly manipulate the `clean_reactions` data,
which is much easier to work with, since it's
a Map and doesn't duplicate any data.
This rewrite should avoid some obscure bugs.
I use `r` as shorthand for the clean reaction
structures, so as not to confuse it with
data from the server's message.reactions.
It also avoids some confusion where we use
`reaction` as a var name for the reaction
elements.
2020-03-08 13:13:47 +01:00
|
|
|
|
|
|
|
opts.message.clean_reactions.set(opts.local_id, r);
|
2021-02-28 01:25:00 +01:00
|
|
|
}
|
reactions: Rewrite code to use clean reactions.
Before this commit, the reactions code would
take the `message.reactions` structure from
the server and try to "collapse" all the reactions
for the same users into the same reactions,
but with each reaction having a list of user_ids.
It was a strangely denormalized structure that
was awkward to work with, and it made it really
hard to reason about whether the data was in
the original structure that the server sent or
the modified structure.
Now we use a cleaner, normalized Map to keep
each reaction (i.e. one per emoji), and we
write that to `message.clean_reactions`.
The `clean_reactions` structure is now the
authoritatize source for all reaction-related
operations. As soon as you try to do anything
with reactions, we build the `clean_reactions`
data on the fly from the server data.
In particular, when we process events, we just
directly manipulate the `clean_reactions` data,
which is much easier to work with, since it's
a Map and doesn't duplicate any data.
This rewrite should avoid some obscure bugs.
I use `r` as shorthand for the clean reaction
structures, so as not to confuse it with
data from the server's message.reactions.
It also avoids some confusion where we use
`reaction` as a var name for the reaction
elements.
2020-03-08 13:13:47 +01:00
|
|
|
|
2021-02-28 01:25:00 +01:00
|
|
|
export function update_user_fields(r) {
|
reactions: Rewrite code to use clean reactions.
Before this commit, the reactions code would
take the `message.reactions` structure from
the server and try to "collapse" all the reactions
for the same users into the same reactions,
but with each reaction having a list of user_ids.
It was a strangely denormalized structure that
was awkward to work with, and it made it really
hard to reason about whether the data was in
the original structure that the server sent or
the modified structure.
Now we use a cleaner, normalized Map to keep
each reaction (i.e. one per emoji), and we
write that to `message.clean_reactions`.
The `clean_reactions` structure is now the
authoritatize source for all reaction-related
operations. As soon as you try to do anything
with reactions, we build the `clean_reactions`
data on the fly from the server data.
In particular, when we process events, we just
directly manipulate the `clean_reactions` data,
which is much easier to work with, since it's
a Map and doesn't duplicate any data.
This rewrite should avoid some obscure bugs.
I use `r` as shorthand for the clean reaction
structures, so as not to confuse it with
data from the server's message.reactions.
It also avoids some confusion where we use
`reaction` as a var name for the reaction
elements.
2020-03-08 13:13:47 +01:00
|
|
|
r.count = r.user_ids.length;
|
|
|
|
r.label = generate_title(r.emoji_name, r.user_ids);
|
|
|
|
if (r.user_ids.includes(page_params.user_id)) {
|
|
|
|
r.class = "message_reaction reacted";
|
|
|
|
} else {
|
|
|
|
r.class = "message_reaction";
|
|
|
|
}
|
2021-02-28 01:25:00 +01:00
|
|
|
}
|