diff --git a/templates/zerver/app/index.html b/templates/zerver/app/index.html
index 6a07838e29..353673403f 100644
--- a/templates/zerver/app/index.html
+++ b/templates/zerver/app/index.html
@@ -259,6 +259,8 @@
+
+
diff --git a/tools/test-js-with-node b/tools/test-js-with-node
index da2c6136c9..15393b7b97 100755
--- a/tools/test-js-with-node
+++ b/tools/test-js-with-node
@@ -178,6 +178,7 @@ EXEMPT_FILES = make_set(
"web/src/resize_handler.js",
"web/src/rows.js",
"web/src/scheduled_messages.js",
+ "web/src/scheduled_messages_feed_ui.js",
"web/src/scheduled_messages_overlay_ui.js",
"web/src/scheduled_messages_popover.js",
"web/src/scheduled_messages_ui.js",
diff --git a/web/src/narrow.js b/web/src/narrow.js
index 581870b8c0..ef3515ae49 100644
--- a/web/src/narrow.js
+++ b/web/src/narrow.js
@@ -39,6 +39,7 @@ import * as pm_list from "./pm_list";
import * as recent_view_ui from "./recent_view_ui";
import * as recent_view_util from "./recent_view_util";
import * as resize from "./resize";
+import * as scheduled_messages_feed_ui from "./scheduled_messages_feed_ui";
import * as search from "./search";
import {web_mark_read_on_scroll_policy_values} from "./settings_config";
import * as spectators from "./spectators";
@@ -922,6 +923,7 @@ export function to_compose_target() {
function handle_post_view_change(msg_list) {
const filter = msg_list.data.filter;
+ scheduled_messages_feed_ui.update_schedule_message_indicator();
typing_events.render_notifications_for_narrow();
if (filter.contains_only_private_messages()) {
diff --git a/web/src/scheduled_messages_feed_ui.js b/web/src/scheduled_messages_feed_ui.js
new file mode 100644
index 0000000000..2581340f67
--- /dev/null
+++ b/web/src/scheduled_messages_feed_ui.js
@@ -0,0 +1,64 @@
+import $ from "jquery";
+
+import render_scheduled_messages_indicator from "../templates/scheduled_messages_indicator.hbs";
+
+import * as narrow_state from "./narrow_state";
+import * as scheduled_messages from "./scheduled_messages";
+import * as util from "./util";
+
+function get_scheduled_messages_matching_narrow() {
+ const scheduled_messages_list = Object.values(scheduled_messages.scheduled_messages_data);
+ const filter = narrow_state.filter();
+ const is_conversation_view = filter === undefined ? false : filter.is_conversation_view();
+ const current_view_type = narrow_state.narrowed_to_pms() ? "private" : "stream";
+
+ if (!is_conversation_view) {
+ return false;
+ }
+
+ const matching_scheduled_messages = scheduled_messages_list.filter((scheduled_message) => {
+ // One could imagine excluding scheduled messages that failed
+ // to send, but structurally, we want to raise awareness of
+ // them -- we expect users to cancel/clear/reschedule those if
+ // aware of them.
+
+ if (current_view_type !== scheduled_message.type) {
+ return false;
+ }
+
+ if (scheduled_message.type === "private") {
+ // Both of these will be the user IDs for all participants including the
+ // current user sorted in ascending order.
+ if (scheduled_message.to.toString() === narrow_state.pm_ids_string()) {
+ return true;
+ }
+ } else if (scheduled_message.type === "stream") {
+ const narrow_dict = {
+ stream_id: narrow_state.stream_sub().stream_id,
+ topic: narrow_state.topic(),
+ };
+ const scheduled_message_dict = {
+ stream_id: scheduled_message.to,
+ topic: scheduled_message.topic,
+ };
+ if (util.same_stream_and_topic(narrow_dict, scheduled_message_dict)) {
+ return true;
+ }
+ }
+ return false;
+ });
+ return matching_scheduled_messages;
+}
+
+export function update_schedule_message_indicator() {
+ $("#scheduled_message_indicator").empty();
+ const matching_scheduled_messages = get_scheduled_messages_matching_narrow();
+ const scheduled_message_count = matching_scheduled_messages.length;
+ if (scheduled_message_count > 0) {
+ $("#scheduled_message_indicator").html(
+ render_scheduled_messages_indicator({
+ scheduled_message_count,
+ }),
+ );
+ }
+}
diff --git a/web/src/server_events_dispatch.js b/web/src/server_events_dispatch.js
index e5b0b7fdb6..4cd921a931 100644
--- a/web/src/server_events_dispatch.js
+++ b/web/src/server_events_dispatch.js
@@ -43,6 +43,7 @@ import * as realm_playground from "./realm_playground";
import {realm_user_settings_defaults} from "./realm_user_settings_defaults";
import * as reload from "./reload";
import * as scheduled_messages from "./scheduled_messages";
+import * as scheduled_messages_feed_ui from "./scheduled_messages_feed_ui";
import * as scheduled_messages_overlay_ui from "./scheduled_messages_overlay_ui";
import * as scheduled_messages_ui from "./scheduled_messages_ui";
import * as scroll_bar from "./scroll_bar";
@@ -493,12 +494,14 @@ export function dispatch_normal_event(event) {
switch (event.op) {
case "add": {
scheduled_messages.add_scheduled_messages(event.scheduled_messages);
+ scheduled_messages_feed_ui.update_schedule_message_indicator();
scheduled_messages_overlay_ui.rerender();
left_sidebar_navigation_area.update_scheduled_messages_row();
break;
}
case "remove": {
scheduled_messages.remove_scheduled_message(event.scheduled_message_id);
+ scheduled_messages_feed_ui.update_schedule_message_indicator();
scheduled_messages_ui.hide_scheduled_message_success_compose_banner(
event.scheduled_message_id,
);
diff --git a/web/styles/scheduled_messages.css b/web/styles/scheduled_messages.css
index e2502e242b..6212c88702 100644
--- a/web/styles/scheduled_messages.css
+++ b/web/styles/scheduled_messages.css
@@ -18,3 +18,23 @@
}
}
}
+
+#scheduled_message_indicator {
+ display: block;
+ margin-left: 10px;
+ font-style: italic;
+ color: hsl(0deg 0% 53%);
+}
+
+@media (width < $xl_min) {
+ #scheduled_message_indicator {
+ margin-right: 7px;
+ }
+}
+
+@media (width < $md_min) {
+ #scheduled_message_indicator {
+ margin-right: 7px;
+ margin-left: 7px;
+ }
+}
diff --git a/web/templates/scheduled_messages_indicator.hbs b/web/templates/scheduled_messages_indicator.hbs
new file mode 100644
index 0000000000..381982c1c1
--- /dev/null
+++ b/web/templates/scheduled_messages_indicator.hbs
@@ -0,0 +1,7 @@
+
+
+ {{#tr}}
+ You have
{scheduled_message_count, plural, =1 {1 scheduled message} other {# scheduled messages}} for this conversation.
+ {{#*inline "z-link"}}
{{> @partial-block}}{{/inline}}
+ {{/tr}}
+
diff --git a/web/tests/dispatch.test.js b/web/tests/dispatch.test.js
index 8b1621d30e..b561987ed6 100644
--- a/web/tests/dispatch.test.js
+++ b/web/tests/dispatch.test.js
@@ -43,7 +43,9 @@ const realm_logo = mock_esm("../src/realm_logo");
const realm_playground = mock_esm("../src/realm_playground");
const reload = mock_esm("../src/reload");
const scheduled_messages = mock_esm("../src/scheduled_messages");
+const scheduled_messages_feed_ui = mock_esm("../src/scheduled_messages_feed_ui");
const scheduled_messages_overlay_ui = mock_esm("../src/scheduled_messages_overlay_ui");
+const scheduled_messages_ui = mock_esm("../src/scheduled_messages_ui");
const scroll_bar = mock_esm("../src/scroll_bar");
const settings_account = mock_esm("../src/settings_account");
const settings_bots = mock_esm("../src/settings_bots");
@@ -383,6 +385,9 @@ run_test("reaction", ({override}) => {
run_test("scheduled_messages", ({override}) => {
override(scheduled_messages_overlay_ui, "rerender", noop);
override(scheduled_messages_overlay_ui, "remove_scheduled_message_id", noop);
+ override(scheduled_messages_feed_ui, "update_schedule_message_indicator", noop);
+ override(scheduled_messages_ui, "hide_scheduled_message_success_compose_banner", noop);
+
let event = event_fixtures.scheduled_messages__add;
{
const stub = make_stub();