scheduled_messages: Add overlay to display and edit them.

Fixes #20971
This commit is contained in:
Aman Agrawal 2023-04-14 19:35:56 +00:00 committed by Tim Abbott
parent ff52187289
commit 043d54d170
15 changed files with 336 additions and 0 deletions

View File

@ -109,6 +109,7 @@
<div id="manage_streams_container"></div> <div id="manage_streams_container"></div>
<div id="manage_groups_container"></div> <div id="manage_groups_container"></div>
<div id="drafts_table"></div> <div id="drafts_table"></div>
<div id="scheduled_messages_overlay_container"></div>
<div id="settings_overlay_container" class="overlay" data-overlay="settings" aria-hidden="true"> <div id="settings_overlay_container" class="overlay" data-overlay="settings" aria-hidden="true">
</div> </div>

View File

@ -144,6 +144,8 @@ EXEMPT_FILES = make_set(
"web/src/reminder.js", "web/src/reminder.js",
"web/src/resize.js", "web/src/resize.js",
"web/src/rows.js", "web/src/rows.js",
"web/src/scheduled_messages.js",
"web/src/scheduled_messages_overlay_ui.js",
"web/src/scroll_bar.ts", "web/src/scroll_bar.ts",
"web/src/search_pill_widget.js", "web/src/search_pill_widget.js",
"web/src/sent_messages.js", "web/src/sent_messages.js",

View File

@ -38,6 +38,7 @@ import "../../styles/modal.css";
import "../../styles/settings.css"; import "../../styles/settings.css";
import "../../styles/image_upload_widget.css"; import "../../styles/image_upload_widget.css";
import "../../styles/subscriptions.css"; import "../../styles/subscriptions.css";
import "../../styles/scheduled_messages.css";
import "../../styles/drafts.css"; import "../../styles/drafts.css";
import "../../styles/input_pill.css"; import "../../styles/input_pill.css";
import "../../styles/informational_overlays.css"; import "../../styles/informational_overlays.css";

View File

@ -15,6 +15,7 @@ export const ERROR = "error";
const MESSAGE_SENT_CLASSNAMES = { const MESSAGE_SENT_CLASSNAMES = {
sent_scroll_to_view: "sent_scroll_to_view", sent_scroll_to_view: "sent_scroll_to_view",
narrow_to_recipient: "narrow_to_recipient", narrow_to_recipient: "narrow_to_recipient",
scheduled_message_banner: "scheduled_message_banner",
}; };
export const CLASSNAMES = { export const CLASSNAMES = {

View File

@ -208,6 +208,7 @@ export function is_overlay_hash(hash) {
"message-formatting", "message-formatting",
"search-operators", "search-operators",
"about-zulip", "about-zulip",
"scheduled",
]; ];
const main_hash = get_hash_category(hash); const main_hash = get_hash_category(hash);

View File

@ -16,6 +16,7 @@ import * as overlays from "./overlays";
import {page_params} from "./page_params"; import {page_params} from "./page_params";
import * as recent_topics_ui from "./recent_topics_ui"; import * as recent_topics_ui from "./recent_topics_ui";
import * as recent_topics_util from "./recent_topics_util"; import * as recent_topics_util from "./recent_topics_util";
import * as scheduled_messages_overlay_ui from "./scheduled_messages_overlay_ui";
import * as search from "./search"; import * as search from "./search";
import * as settings from "./settings"; import * as settings from "./settings";
import * as settings_panel_menu from "./settings_panel_menu"; import * as settings_panel_menu from "./settings_panel_menu";
@ -212,6 +213,7 @@ function do_hashchange_normal(from_reload) {
case "#organization": case "#organization":
case "#settings": case "#settings":
case "#about-zulip": case "#about-zulip":
case "#scheduled":
blueslip.error("overlay logic skipped for: " + hash); blueslip.error("overlay logic skipped for: " + hash);
break; break;
default: default:
@ -368,6 +370,10 @@ function do_hashchange_overlay(old_hash) {
if (base === "about-zulip") { if (base === "about-zulip") {
about_zulip.launch(); about_zulip.launch();
} }
if (base === "scheduled") {
scheduled_messages_overlay_ui.launch();
}
} }
function hashchanged(from_reload, e) { function hashchanged(from_reload, e) {

View File

@ -85,6 +85,10 @@ export function drafts_open(): boolean {
return open_overlay_name === "drafts"; return open_overlay_name === "drafts";
} }
export function scheduled_messages_open(): boolean {
return open_overlay_name === "scheduled";
}
export function active_modal(): string | undefined { export function active_modal(): string | undefined {
if (!is_modal_open()) { if (!is_modal_open()) {
blueslip.error("Programming error — Called active_modal when there is no modal open"); blueslip.error("Programming error — Called active_modal when there is no modal open");

View File

@ -0,0 +1,102 @@
import $ from "jquery";
import * as channel from "./channel";
import * as compose from "./compose";
import * as compose_actions from "./compose_actions";
import * as compose_fade from "./compose_fade";
import * as compose_ui from "./compose_ui";
import * as narrow from "./narrow";
import * as overlays from "./overlays";
import * as people from "./people";
import * as popover_menus from "./popover_menus";
// This is only updated when user opens the scheduled messages overlay.
export let scheduled_messages_data = [];
export function override_scheduled_messages_data(data) {
scheduled_messages_data = data;
}
export function edit_scheduled_message(scheduled_msg_id) {
const scheduled_msg = scheduled_messages_data.find(
(msg) => msg.message_id === scheduled_msg_id,
);
let compose_args;
if (scheduled_msg.type === "stream") {
compose_args = {
type: "stream",
stream: scheduled_msg.stream_name,
topic: scheduled_msg.topic,
content: scheduled_msg.content,
};
} else {
const recipient_emails = [];
if (scheduled_msg.to) {
for (const recipient_id of scheduled_msg.to) {
recipient_emails.push(people.get_by_user_id(recipient_id).email);
}
}
compose_args = {
type: scheduled_msg.type,
private_message_recipient: recipient_emails.join(","),
content: scheduled_msg.content,
};
}
if (compose_args.type === "stream") {
narrow.activate(
[
{operator: "stream", operand: compose_args.stream},
{operator: "topic", operand: compose_args.topic},
],
{trigger: "edit scheduled message"},
);
} else {
narrow.activate([{operator: "pm-with", operand: compose_args.private_message_recipient}], {
trigger: "edit scheduled message",
});
}
overlays.close_overlay("scheduled");
compose_fade.clear_compose();
compose.clear_preview_area();
compose_actions.start(compose_args.type, compose_args);
compose_ui.autosize_textarea($("#compose-textarea"));
$("#compose-textarea").attr("data-scheduled-message-id", scheduled_msg_id);
popover_menus.show_schedule_confirm_button(scheduled_msg.formatted_send_at_time, true);
}
export function delete_scheduled_message(scheduled_msg_id) {
channel.del({
url: "/json/scheduled_messages/" + scheduled_msg_id,
success() {
// TODO: Do this via events received from the server in server_events_dispatch.
if (overlays.scheduled_messages_open()) {
$(
`#scheduled_messages_overlay .scheduled-message-row[data-message-id=${scheduled_msg_id}]`,
).remove();
}
if ($("#compose-textarea").attr("data-scheduled-message-id")) {
const compose_scheduled_msg_id = $("#compose-textarea").attr(
"data-scheduled-message-id",
);
// If user deleted the scheduled message which is being edited in compose, we clear
// the scheduled message id from there which converts this editing state into a normal
// schedule message state. So, clicking "Schedule" will now create a new scheduled message.
if (compose_scheduled_msg_id === scheduled_msg_id) {
$("#compose-textarea").removeAttr("data-scheduled-message-id");
}
}
},
});
}
export function delete_scheduled_message_if_sent_directly() {
// Delete old scheduled message if it was sent.
if ($("#compose-textarea").attr("data-scheduled-message-id")) {
delete_scheduled_message($("#compose-textarea").attr("data-scheduled-message-id"));
$("#compose-textarea").removeAttr("data-scheduled-message-id");
}
}

View File

@ -0,0 +1,109 @@
import * as date_fns from "date-fns";
import $ from "jquery";
import render_scheduled_message from "../templates/scheduled_message.hbs";
import render_scheduled_messages_overlay from "../templates/scheduled_messages_overlay.hbs";
import * as blueslip from "./blueslip";
import * as browser_history from "./browser_history";
import * as channel from "./channel";
import * as loading from "./loading";
import * as overlays from "./overlays";
import * as people from "./people";
import * as scheduled_messages from "./scheduled_messages";
import * as stream_color from "./stream_color";
import * as stream_data from "./stream_data";
import * as timerender from "./timerender";
function hide_loading_indicator() {
loading.destroy_indicator($("#scheduled_messages_overlay .loading-indicator"));
$(".scheduled-messages-loading").hide();
}
function format(scheduled_messages) {
const formatted_msgs = [];
for (const msg of scheduled_messages) {
const msg_render_context = {...msg};
if (msg.type === "stream") {
msg_render_context.is_stream = true;
msg_render_context.stream_id = msg.to[0];
msg_render_context.stream_name = stream_data.maybe_get_stream_name(
msg_render_context.stream_id,
);
const color = stream_data.get_color(msg_render_context.stream_name);
msg_render_context.recipient_bar_color = stream_color.get_recipient_bar_color(color);
msg_render_context.stream_privacy_icon_color =
stream_color.get_stream_privacy_icon_color(color);
} else {
msg_render_context.is_stream = false;
msg_render_context.recipients = people.get_recipients(msg.to.join(","));
}
const time = new Date(msg.deliver_at);
msg_render_context.full_date_time = timerender.get_full_datetime(time);
msg_render_context.formatted_send_at_time = date_fns.format(time, "MMM d yyyy h:mm a");
formatted_msgs.push(msg_render_context);
}
return formatted_msgs;
}
export function launch() {
$("#scheduled_messages_overlay_container").empty();
$("#scheduled_messages_overlay_container").append(render_scheduled_messages_overlay());
overlays.open_overlay({
name: "scheduled",
$overlay: $("#scheduled_messages_overlay"),
on_close() {
browser_history.exit_overlay();
},
});
loading.make_indicator($("#scheduled_messages_overlay .loading-indicator"), {
abs_positioned: true,
});
channel.get({
url: "/json/scheduled_messages",
success(data) {
hide_loading_indicator();
if (data.scheduled_messages.length === 0) {
$(".no-overlay-messages").show();
} else {
// Saving formatted data is helpful when user is trying to edit a scheduled message.
scheduled_messages.override_scheduled_messages_data(
format(data.scheduled_messages),
);
const rendered_list = render_scheduled_message({
scheduled_messages_data: scheduled_messages.scheduled_messages_data,
});
const $messages_list = $("#scheduled_messages_overlay .overlay-messages-list");
$messages_list.append(rendered_list);
}
},
error(xhr) {
hide_loading_indicator();
blueslip.error(xhr);
},
});
}
export function initialize() {
$("body").on("click", ".scheduled-message-row .restore-overlay-message", (e) => {
let scheduled_msg_id = $(e.currentTarget)
.closest(".scheduled-message-row")
.attr("data-message-id");
scheduled_msg_id = Number.parseInt(scheduled_msg_id, 10);
scheduled_messages.edit_scheduled_message(scheduled_msg_id);
e.stopPropagation();
e.preventDefault();
});
$("body").on("click", ".scheduled-message-row .delete-overlay-message", (e) => {
const scheduled_msg_id = $(e.currentTarget)
.closest(".scheduled-message-row")
.attr("data-message-id");
scheduled_messages.delete_scheduled_message(scheduled_msg_id);
e.stopPropagation();
e.preventDefault();
});
}

View File

@ -72,6 +72,7 @@ import * as reload from "./reload";
import * as rendered_markdown from "./rendered_markdown"; import * as rendered_markdown from "./rendered_markdown";
import * as resize from "./resize"; import * as resize from "./resize";
import * as rows from "./rows"; import * as rows from "./rows";
import * as scheduled_messages_overlay_ui from "./scheduled_messages_overlay_ui";
import * as scroll_bar from "./scroll_bar"; import * as scroll_bar from "./scroll_bar";
import * as search from "./search"; import * as search from "./search";
import * as search_pill_widget from "./search_pill_widget"; import * as search_pill_widget from "./search_pill_widget";
@ -647,6 +648,7 @@ export function initialize_everything() {
spoilers.initialize(); spoilers.initialize();
lightbox.initialize(); lightbox.initialize();
click_handlers.initialize(); click_handlers.initialize();
scheduled_messages_overlay_ui.initialize();
copy_and_paste.initialize(); copy_and_paste.initialize();
overlays.initialize(); overlays.initialize();
invite.initialize(); invite.initialize();

View File

@ -1264,6 +1264,7 @@
background-color: hsl(212deg 28% 18%); background-color: hsl(212deg 28% 18%);
} }
.scheduled-messages-loading-logo,
.alert-zulip-logo, .alert-zulip-logo,
.top-messages-logo, .top-messages-logo,
.bottom-messages-logo { .bottom-messages-logo {

View File

@ -0,0 +1,16 @@
#scheduled_messages_overlay_container {
.scheduled-messages-loading {
margin-top: 10px;
display: grid;
> * {
grid-row-start: 1;
grid-column-start: 1;
margin: auto;
}
}
.no-overlay-messages {
display: none;
}
}

View File

@ -201,6 +201,7 @@ p.n-margin {
padding-top: var(--header-padding-bottom); padding-top: var(--header-padding-bottom);
} }
.scheduled-messages-loading-logo,
.alert-zulip-logo, .alert-zulip-logo,
.top-messages-logo, .top-messages-logo,
.bottom-messages-logo { .bottom-messages-logo {

View File

@ -0,0 +1,57 @@
{{#each scheduled_messages_data}}
<div class="scheduled-message-row overlay-message-row" data-message-id="{{message_id}}">
<div class="overlay-message-info-box" tabindex="0">
{{#if is_stream}}
<div class="message_header message_header_stream">
<div class="message-header-contents" style="background: {{recipient_bar_color}};">
<div class="message_label_clickable stream_label">
<span class="stream-privacy-modified-color-{{stream_id}} stream-privacy filter-icon" style="color: {{stream_privacy_icon_color}}">
{{> stream_privacy}}
</span>
{{stream_name}}
</div>
<span class="stream_topic_separator"><i class="zulip-icon zulip-icon-chevron-right"></i></span>
<span class="stream_topic">
<div class="message_label_clickable narrows_by_topic">
{{topic}}
</div>
</span>
<span class="recipient_bar_controls"></span>
<div class="recipient_row_date">{{ formatted_send_at_time }}</div>
</div>
</div>
{{else}}
<div class="message_header message_header_private_message">
<div class="message-header-contents">
<div class="message_label_clickable stream_label">
<span class="private_message_header_icon"><i class="zulip-icon zulip-icon-user"></i></span>
{{t "You and {recipients}" }}
</div>
<div class="recipient_row_date">{{ formatted_send_at_time }}</div>
</div>
</div>
{{/if}}
<div class="message_row{{^is_stream}} private-message{{/is_stream}}" role="listitem">
<div class="messagebox">
<div class="messagebox-content">
<div class="message_top_line">
<div class="overlay_message_controls">
<i class="fa fa-pencil fa-lg restore-overlay-message tippy-zulip-tooltip" aria-hidden="true" data-tooltip-template-id="restore-scheduled-message-tooltip-template"></i>
<template id="restore-scheduled-message-tooltip-template">
{{t 'Edit or reschedule message' }}
{{tooltip_hotkey_hints "Enter"}}
</template>
<i class="fa fa-trash-o fa-lg delete-overlay-message tippy-zulip-tooltip" aria-hidden="true" data-tooltip-template-id="delete-scheduled-message-tooltip-template"></i>
<template id="delete-scheduled-message-tooltip-template">
{{t 'Delete scheduled message' }}
{{tooltip_hotkey_hints "Backspace"}}
</template>
</div>
</div>
<div class="message_content rendered_markdown restore-overlay-message" title="{{t 'Edit or reschedule message' }}">{{rendered_markdown rendered_content}}</div>
</div>
</div>
</div>
</div>
</div>
{{/each}}

View File

@ -0,0 +1,32 @@
<div id="scheduled_messages_overlay" class="overlay new-style" data-overlay="scheduled">
<div class="flex overlay-content">
<div class="overlay-messages-container modal-bg">
<div class="overlay-messages-header">
<h1>{{t 'Scheduled messages' }}</h1>
<div class="exit">
<span class="exit-sign">&times;</span>
</div>
<div class="removed-drafts">
{{#tr}}
Click on the pencil (<z-pencil-icon></z-pencil-icon>) icon to reschedule a message.
{{#*inline "z-pencil-icon"}}<i class="fa fa-pencil"></i>{{/inline}}
{{/tr}}
</div>
</div>
<div class="scheduled-messages-loading">
<div class="scheduled-messages-loading-logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 773.12 773.12">
<circle cx="386.56" cy="386.56" r="386.56"></circle>
<path d="M566.66 527.25c0 33.03-24.23 60.05-53.84 60.05H260.29c-29.61 0-53.84-27.02-53.84-60.05 0-20.22 9.09-38.2 22.93-49.09l134.37-120c2.5-2.14 5.74 1.31 3.94 4.19l-49.29 98.69c-1.38 2.76.41 6.16 3.25 6.16h191.18c29.61 0 53.83 27.03 53.83 60.05zm0-281.39c0 20.22-9.09 38.2-22.93 49.09l-134.37 120c-2.5 2.14-5.74-1.31-3.94-4.19l49.29-98.69c1.38-2.76-.41-6.16-3.25-6.16H260.29c-29.61 0-53.84-27.02-53.84-60.05s24.23-60.05 53.84-60.05h252.54c29.61 0 53.83 27.02 53.83 60.05z"></path>
</svg>
</div>
<div class="loading-indicator"></div>
</div>
<div class="overlay-messages-list">
<div class="no-overlay-messages">
{{t 'No scheduled messages.'}}
</div>
</div>
</div>
</div>
</div>