Skip to content

Commit

Permalink
notifications: Add initial implementation for reaction notifications.
Browse files Browse the repository at this point in the history
  • Loading branch information
akarsh-jain-790 committed Feb 28, 2024
1 parent be59ef9 commit b07ba96
Show file tree
Hide file tree
Showing 22 changed files with 666 additions and 3 deletions.
1 change: 1 addition & 0 deletions tools/lib/capitalization.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
r"Topics I participate in",
r"Topics I send a message to",
r"Topics I start",
r"In topics I follow",
# Specific short words
r"beta",
r"and",
Expand Down
3 changes: 2 additions & 1 deletion web/src/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,8 @@ export function build_page() {
settings_config.automatically_follow_or_unmute_topics_policy_values,
automatically_unmute_topics_in_muted_streams_policy_values:
settings_config.automatically_follow_or_unmute_topics_policy_values,
realm_enable_guest_user_indicator: realm.realm_enable_guest_user_indicator,
streams_reaction_notification_values: settings_config.streams_reaction_notification_values,
realm_enable_guest_user_indicator: page_params.realm_enable_guest_user_indicator,
};

if (options.realm_logo_source !== "D" && options.realm_night_logo_source === "D") {
Expand Down
17 changes: 16 additions & 1 deletion web/src/message_notifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ import * as user_topics from "./user_topics";

function get_notification_content(message) {
let content;

if (message.reaction_label) {
return message.reaction_label;
}

// Convert the content to plain text, replacing emoji with their alt text
const $content = $("<div>").html(message.content);
ui_util.replace_emoji_with_text($content);
Expand Down Expand Up @@ -69,6 +74,11 @@ function debug_notification_source_value(message) {
function get_notification_key(message) {
let key;

if (message.current_reaction_key) {
key = message.current_reaction_key;
return key;
}

if (message.type === "private" || message.type === "test-notification") {
key = message.display_reply_to;
} else {
Expand All @@ -89,6 +99,10 @@ function get_notification_title(message, msg_count) {
let title = message.sender_full_name;
let other_recipients;

if (message.reacted_by) {
title = message.reacted_by;
}

if (msg_count > 1) {
title = msg_count + " messages from " + title;
}
Expand Down Expand Up @@ -128,6 +142,7 @@ export function process_notification(notification) {
const message = notification.message;
const content = get_notification_content(message);
const key = get_notification_key(message);
const notification_tag = message.current_reaction_key ? message.id + key : message.id;
let notification_object;
let msg_count = 1;

Expand All @@ -146,7 +161,7 @@ export function process_notification(notification) {
notification_object = new desktop_notifications.NotificationAPI(title, {
icon: icon_url,
body: content,
tag: message.id,
tag: notification_tag,
});
desktop_notifications.notice_memory.set(key, {
obj: notification_object,
Expand Down
141 changes: 141 additions & 0 deletions web/src/reaction_notifications.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import $ from "jquery";

import * as desktop_notifications from "./desktop_notifications";
import {$t} from "./i18n";
import * as message_notifications from "./message_notifications";
import * as message_store from "./message_store";
import * as people from "./people";
import {get_local_reaction_id} from "./reactions";
import * as settings_config from "./settings_config";
import {current_user} from "./state_data";
import * as ui_util from "./ui_util";
import {user_settings} from "./user_settings";
import * as user_topics from "./user_topics";

function generate_notification_title(emoji_name, user_ids) {
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 + ":",
};

if (user_ids.length === 1) {
context.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];
return $t(
{
defaultMessage: "You and {other_username} reacted with {emoji_name}.",
},
context,
);
}

context.total_reactions = usernames.length;
context.last_username = usernames.at(-1);

return $t(
{
defaultMessage:
"{last_username} and {total_reactions} others reacted with {emoji_name}.",
},
context,
);
}

export function reaction_is_notifiable(message) {
if (message.current_user_reacted) {
return false;
}

if (!message.sent_by_me) {
return false;
}

if (message.type === "private" && user_settings.enable_dm_reactions_notifications) {
return true;
}

if (
message.type === "stream" &&
user_settings.streams_reaction_notification ===
settings_config.streams_reaction_notification_values.always.code
) {
return true;
}

if (
message.type === "stream" &&
user_settings.streams_reaction_notification ===
settings_config.streams_reaction_notification_values.never.code
) {
return false;
}

if (
message.type === "stream" &&
user_topics.is_topic_followed(message.stream_id, message.topic) &&
user_settings.streams_reaction_notification ===
settings_config.streams_reaction_notification_values.followed_topics.code
) {
return true;
}

if (
message.type === "stream" &&
!user_topics.is_topic_unmuted(message.stream_id, message.topic) &&
user_settings.streams_reaction_notification ===
settings_config.streams_reaction_notification_values.unmuted_topics.code
) {
return true;
}

// Everything else is on the table; next filter based on notification
// settings.
return false;
}

export function received_reaction(event) {
const message_id = event.message_id;
const message = message_store.get(message_id);

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;
}

const user_id = event.user_id;
const local_id = get_local_reaction_id(event);
const clean_reaction_object = message.clean_reactions.get(local_id);

message.current_user_reacted = user_id === current_user.user_id;
message.current_reaction_key = clean_reaction_object.emoji_name;
message.reaction_label = generate_notification_title(
clean_reaction_object.emoji_name,
clean_reaction_object.user_ids,
);
message.reacted_by = people.get_full_name(user_id);

if (!reaction_is_notifiable(message)) {
return;
}

if (message_notifications.should_send_desktop_notification(message)) {
message_notifications.process_notification({
message,
desktop_notify: desktop_notifications.granted_desktop_notifications_permission(),
});
}
if (message_notifications.should_send_audible_notification(message)) {
ui_util.play_audio($("#user-notification-sound-audio").get(0));
}
}
2 changes: 2 additions & 0 deletions web/src/realm_user_settings_defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export type RealmDefaultSettings = {
enable_followed_topic_push_notifications: boolean;
enable_followed_topic_email_notifications: boolean;
enable_followed_topic_wildcard_mentions_notify: boolean;
enable_dm_reactions_notifications: boolean;
enter_sends: boolean;
web_escape_navigates_to_home_view: boolean;
fluid_layout_width: boolean;
Expand All @@ -46,6 +47,7 @@ export type RealmDefaultSettings = {
wildcard_mentions_notify: boolean;
automatically_follow_topics_policy: number;
automatically_unmute_topics_in_muted_streams_policy: number;
streams_reaction_notification: number;
automatically_follow_topics_where_mentioned: boolean;
};

Expand Down
2 changes: 2 additions & 0 deletions web/src/server_events_dispatch.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import * as overlays from "./overlays";
import * as peer_data from "./peer_data";
import * as people from "./people";
import * as pm_list from "./pm_list";
import {received_reaction} from "./reaction_notifications";
import * as reactions from "./reactions";
import * as realm_icon from "./realm_icon";
import * as realm_logo from "./realm_logo";
Expand Down Expand Up @@ -189,6 +190,7 @@ export function dispatch_normal_event(event) {
switch (event.op) {
case "add":
reactions.add_reaction(event);
received_reaction(event);
break;
case "remove":
reactions.remove_reaction(event);
Expand Down
1 change: 1 addition & 0 deletions web/src/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ export function build_page() {
settings_config.automatically_follow_or_unmute_topics_policy_values,
automatically_unmute_topics_in_muted_streams_policy_values:
settings_config.automatically_follow_or_unmute_topics_policy_values,
streams_reaction_notification_values: settings_config.streams_reaction_notification_values,
});

settings_bots.update_bot_settings_tip($("#personal-bot-settings-tip"), false);
Expand Down
27 changes: 27 additions & 0 deletions web/src/settings_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,12 @@ export const notification_settings_labels = {
automatically_follow_topics_where_mentioned: $t({
defaultMessage: "Automatically follow topics where I'm mentioned",
}),
enable_dm_reactions_notifications: $t({
defaultMessage: "Notify me about reactions to my DMs",
}),
streams_reaction_notification: $t({
defaultMessage: "Notify me about reactions to my stream messages",
}),
};

export const realm_user_settings_defaults_labels = {
Expand Down Expand Up @@ -761,6 +767,8 @@ const other_notification_settings = [
"automatically_follow_topics_policy",
"automatically_unmute_topics_in_muted_streams_policy",
"automatically_follow_topics_where_mentioned",
"enable_dm_reactions_notifications",
"streams_reaction_notification",
];

export const all_notification_settings = [
Expand Down Expand Up @@ -970,6 +978,25 @@ export const automatically_follow_or_unmute_topics_policy_values = {
},
};

export const streams_reaction_notification_values = {
never: {
code: 1,
description: $t({defaultMessage: "Never"}),
},
followed_topics: {
code: 2,
description: $t({defaultMessage: "In topics I follow"}),
},
unmuted_topics: {
code: 3,
description: $t({defaultMessage: "In unmuted topics"}),
},
always: {
code: 4,
description: $t({defaultMessage: "Always"}),
},
};

export const stream_privacy_policy_values = {
web_public: {
code: "web-public",
Expand Down
8 changes: 7 additions & 1 deletion web/src/settings_notifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,11 @@ export function set_up(settings_panel) {
settings_object.automatically_unmute_topics_in_muted_streams_policy,
);

const $streams_reaction_notification_dropdown = $container.find(
".setting_streams_reaction_notification",
);
$streams_reaction_notification_dropdown.val(settings_object.streams_reaction_notification);

set_enable_digest_emails_visibility($container, for_realm_settings);

if (for_realm_settings) {
Expand Down Expand Up @@ -322,7 +327,8 @@ export function update_page(settings_panel) {
case "notification_sound":
case "realm_name_in_email_notifications_policy":
case "automatically_follow_topics_policy":
case "automatically_unmute_topics_in_muted_streams_policy": {
case "automatically_unmute_topics_in_muted_streams_policy":
case "streams_reaction_notification": {
$container.find(`.setting_${CSS.escape(setting)}`).val(settings_object[setting]);
break;
}
Expand Down
2 changes: 2 additions & 0 deletions web/src/user_settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ export type UserSettings = (StreamNotificationSettings &
automatically_follow_topics_policy: number;
automatically_unmute_topics_in_muted_streams_policy: number;
automatically_follow_topics_where_mentioned: boolean;
enable_dm_reactions_notifications: boolean;
streams_reaction_notification: number;
timezone: string;
};

Expand Down
25 changes: 25 additions & 0 deletions web/templates/settings/notification_settings.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,31 @@
prefix=prefix}}
</div>

<div
class="reaction_notifications m-10 {{#if for_realm_settings}}settings-subsection-parent{{else}}subsection-parent{{/if}}">
<div class="subsection-header inline-block">
<h3>{{t "Reaction notifications" }}</h3>
{{> settings_save_discard_widget section_name="reaction-notifications-settings" show_only_indicator=(not
for_realm_settings) }}
</div>


{{> settings_checkbox
setting_name="enable_dm_reactions_notifications"
is_checked=(lookup settings_object "enable_dm_reactions_notifications")
label=(lookup settings_label "enable_dm_reactions_notifications")
prefix=prefix}}

<div class="input-group">
<label for="streams_reaction_notification" class="dropdown-title">{{ settings_label.streams_reaction_notification }}</label>
<select name="streams_reaction_notification"
class="setting_streams_reaction_notification prop-element settings_select bootstrap-focus-style"
id="{{prefix}}streams_reaction_notification" data-setting-widget-type="number">
{{> dropdown_options_widget option_values=streams_reaction_notification_values}}
</select>
</div>
</div>

<div class="desktop_notifications m-10 {{#if for_realm_settings}}settings-subsection-parent{{else}}subsection-parent{{/if}}">

<div class="subsection-header inline-block">
Expand Down
1 change: 1 addition & 0 deletions web/tests/i18n.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ run_test("tr_tag", ({mock_template}) => {
"Automatically unmute topics in muted streams",
automatically_follow_topics_where_mentioned:
"Automatically follow topics where I'm mentioned",
streams_reaction_notification: "Notify me about reactions to my stream messages",
},
show_push_notifications_tooltip: false,
user_role_text: "Member",
Expand Down
Loading

0 comments on commit b07ba96

Please sign in to comment.