mirror of
https://github.com/zulip/zulip.git
synced 2025-11-16 20:02:15 +00:00
reminder: Show scheduled reminder info on render message.
This commit is contained in:
@@ -22,6 +22,7 @@ import * as message_edit from "./message_edit.ts";
|
|||||||
import type {MessageList} from "./message_list.ts";
|
import type {MessageList} from "./message_list.ts";
|
||||||
import * as message_list_tooltips from "./message_list_tooltips.ts";
|
import * as message_list_tooltips from "./message_list_tooltips.ts";
|
||||||
import * as message_lists from "./message_lists.ts";
|
import * as message_lists from "./message_lists.ts";
|
||||||
|
import * as message_reminder from "./message_reminder.ts";
|
||||||
import * as message_store from "./message_store.ts";
|
import * as message_store from "./message_store.ts";
|
||||||
import type {Message} from "./message_store.ts";
|
import type {Message} from "./message_store.ts";
|
||||||
import * as message_viewport from "./message_viewport.ts";
|
import * as message_viewport from "./message_viewport.ts";
|
||||||
@@ -777,6 +778,7 @@ export class MessageListView {
|
|||||||
for (const message of messages) {
|
for (const message of messages) {
|
||||||
const message_reactions = reactions.get_message_reactions(message);
|
const message_reactions = reactions.get_message_reactions(message);
|
||||||
message.message_reactions = message_reactions;
|
message.message_reactions = message_reactions;
|
||||||
|
message.reminders = message_reminder.get_reminders(message.id);
|
||||||
|
|
||||||
// These will be used to build the message container
|
// These will be used to build the message container
|
||||||
let include_recipient = false;
|
let include_recipient = false;
|
||||||
@@ -1077,6 +1079,7 @@ export class MessageListView {
|
|||||||
_get_message_template(message_container: MessageContainer): string {
|
_get_message_template(message_container: MessageContainer): string {
|
||||||
const msg_reactions = reactions.get_message_reactions(message_container.msg);
|
const msg_reactions = reactions.get_message_reactions(message_container.msg);
|
||||||
message_container.msg.message_reactions = msg_reactions;
|
message_container.msg.message_reactions = msg_reactions;
|
||||||
|
message_container.msg.reminders = message_reminder.get_reminders(message_container.msg.id);
|
||||||
const msg_to_render = {
|
const msg_to_render = {
|
||||||
...message_container,
|
...message_container,
|
||||||
message_list_id: this.list.id,
|
message_list_id: this.list.id,
|
||||||
|
|||||||
@@ -1,17 +1,33 @@
|
|||||||
import $ from "jquery";
|
import $ from "jquery";
|
||||||
import type * as z from "zod/mini";
|
import type * as z from "zod/mini";
|
||||||
|
|
||||||
|
import render_message_reminders from "../templates/message_reminders.hbs";
|
||||||
|
|
||||||
import * as channel from "./channel.ts";
|
import * as channel from "./channel.ts";
|
||||||
import * as feedback_widget from "./feedback_widget.ts";
|
import * as feedback_widget from "./feedback_widget.ts";
|
||||||
import {$t} from "./i18n.ts";
|
import {$t} from "./i18n.ts";
|
||||||
|
import * as message_lists from "./message_lists.ts";
|
||||||
import type {StateData, reminder_schema} from "./state_data.ts";
|
import type {StateData, reminder_schema} from "./state_data.ts";
|
||||||
import * as timerender from "./timerender.ts";
|
import * as timerender from "./timerender.ts";
|
||||||
import * as ui_report from "./ui_report.ts";
|
import * as ui_report from "./ui_report.ts";
|
||||||
|
|
||||||
export type Reminder = z.infer<typeof reminder_schema>;
|
export type Reminder = z.infer<typeof reminder_schema>;
|
||||||
|
|
||||||
|
// Used to render reminders in message list.
|
||||||
|
export type TimeFormattedReminder = {
|
||||||
|
reminder_id: number;
|
||||||
|
formatted_delivery_time: string;
|
||||||
|
scheduled_delivery_timestamp: number;
|
||||||
|
};
|
||||||
|
|
||||||
export const reminders_by_id = new Map<number, Reminder>();
|
export const reminders_by_id = new Map<number, Reminder>();
|
||||||
|
|
||||||
|
export const reminders_by_message_id = new Map<number, TimeFormattedReminder[]>();
|
||||||
|
|
||||||
|
export function get_reminders(message_id: number): TimeFormattedReminder[] | undefined {
|
||||||
|
return reminders_by_message_id.get(message_id);
|
||||||
|
}
|
||||||
|
|
||||||
export function set_message_reminder(send_at_time: number, message_id: number, note: string): void {
|
export function set_message_reminder(send_at_time: number, message_id: number, note: string): void {
|
||||||
channel.post({
|
channel.post({
|
||||||
url: "/json/reminders",
|
url: "/json/reminders",
|
||||||
@@ -48,8 +64,38 @@ export function set_message_reminder(send_at_time: number, message_id: number, n
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function add_reminders(reminders: Reminder[]): void {
|
export function add_reminders(reminders: Reminder[]): void {
|
||||||
|
const message_ids_to_rerender = new Set<number>();
|
||||||
for (const reminder of reminders) {
|
for (const reminder of reminders) {
|
||||||
|
message_ids_to_rerender.add(reminder.reminder_target_message_id);
|
||||||
reminders_by_id.set(reminder.reminder_id, reminder);
|
reminders_by_id.set(reminder.reminder_id, reminder);
|
||||||
|
|
||||||
|
// Do all the formatting and sorting needed to display
|
||||||
|
// reminders for a message to avoid doing at the time of render.
|
||||||
|
const formatted_delivery_time = timerender.get_full_datetime(
|
||||||
|
new Date(reminder.scheduled_delivery_timestamp * 1000),
|
||||||
|
"time",
|
||||||
|
);
|
||||||
|
const time_formatted_reminder: TimeFormattedReminder = {
|
||||||
|
reminder_id: reminder.reminder_id,
|
||||||
|
formatted_delivery_time,
|
||||||
|
scheduled_delivery_timestamp: reminder.scheduled_delivery_timestamp,
|
||||||
|
};
|
||||||
|
if (!reminders_by_message_id.has(reminder.reminder_target_message_id)) {
|
||||||
|
reminders_by_message_id.set(reminder.reminder_target_message_id, [
|
||||||
|
time_formatted_reminder,
|
||||||
|
]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const message_reminders = get_reminders(reminder.reminder_target_message_id)!;
|
||||||
|
message_reminders.push(time_formatted_reminder);
|
||||||
|
// Sort reminders to show the earliest one first.
|
||||||
|
message_reminders.sort(
|
||||||
|
(a, b) => a.scheduled_delivery_timestamp - b.scheduled_delivery_timestamp,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const message_id of message_ids_to_rerender) {
|
||||||
|
rerender_reminders_for_message(message_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,6 +106,18 @@ export function initialize(reminders_params: StateData["reminders"]): void {
|
|||||||
export function remove_reminder(reminder_id: number): void {
|
export function remove_reminder(reminder_id: number): void {
|
||||||
if (reminders_by_id.has(reminder_id)) {
|
if (reminders_by_id.has(reminder_id)) {
|
||||||
reminders_by_id.delete(reminder_id);
|
reminders_by_id.delete(reminder_id);
|
||||||
|
|
||||||
|
for (const [message_id, message_reminders] of reminders_by_message_id) {
|
||||||
|
const index = message_reminders.findIndex((r) => r.reminder_id === reminder_id);
|
||||||
|
if (index !== -1) {
|
||||||
|
message_reminders.splice(index, 1);
|
||||||
|
if (message_reminders.length === 0) {
|
||||||
|
reminders_by_message_id.delete(message_id);
|
||||||
|
}
|
||||||
|
rerender_reminders_for_message(message_id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,3 +131,34 @@ export function delete_reminder(reminder_id: number, success?: () => void): void
|
|||||||
export function get_count(): number {
|
export function get_count(): number {
|
||||||
return reminders_by_id.size;
|
return reminders_by_id.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function rerender_reminders_for_message(message_id: number): void {
|
||||||
|
const $rows = message_lists.all_rendered_row_for_message_id(message_id);
|
||||||
|
if ($rows.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const message_reminders = get_reminders(message_id) ?? [];
|
||||||
|
if (message_reminders.length === 0) {
|
||||||
|
$rows.find(".message-reminders").remove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rendered_message_reminders_html = render_message_reminders({
|
||||||
|
msg: {
|
||||||
|
reminders: message_reminders,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
$rows.each(function () {
|
||||||
|
const $row = $(this);
|
||||||
|
const $existing = $row.find(".message-reminders");
|
||||||
|
if ($existing.length > 0) {
|
||||||
|
$existing.replaceWith($(rendered_message_reminders_html));
|
||||||
|
} else {
|
||||||
|
// Insert after reactions if they exist, otherwise after "more" section.
|
||||||
|
const $content = $row.find(".messagebox-content");
|
||||||
|
$content.append($(rendered_message_reminders_html));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import * as z from "zod/mini";
|
|||||||
import * as blueslip from "./blueslip.ts";
|
import * as blueslip from "./blueslip.ts";
|
||||||
import type {RawLocalMessage} from "./echo.ts";
|
import type {RawLocalMessage} from "./echo.ts";
|
||||||
import type {NewMessage, ProcessedMessage} from "./message_helper.ts";
|
import type {NewMessage, ProcessedMessage} from "./message_helper.ts";
|
||||||
|
import type {TimeFormattedReminder} from "./message_reminder.ts";
|
||||||
import * as people from "./people.ts";
|
import * as people from "./people.ts";
|
||||||
import {topic_link_schema} from "./types.ts";
|
import {topic_link_schema} from "./types.ts";
|
||||||
import type {UserStatusEmojiInfo} from "./user_status.ts";
|
import type {UserStatusEmojiInfo} from "./user_status.ts";
|
||||||
@@ -197,6 +198,10 @@ export type Message = (
|
|||||||
// Used in message_notifications to track if a notification has already
|
// Used in message_notifications to track if a notification has already
|
||||||
// been sent for this message.
|
// been sent for this message.
|
||||||
notification_sent?: boolean;
|
notification_sent?: boolean;
|
||||||
|
|
||||||
|
// Added during message rendering in message_list_view.ts. Should
|
||||||
|
// never be accessed outside rendering, as the value may be stale.
|
||||||
|
reminders?: TimeFormattedReminder[] | undefined;
|
||||||
} & (
|
} & (
|
||||||
| {
|
| {
|
||||||
type: "private";
|
type: "private";
|
||||||
|
|||||||
@@ -100,7 +100,7 @@
|
|||||||
ensures the timestamp will always have enough space
|
ensures the timestamp will always have enough space
|
||||||
in the column. */
|
in the column. */
|
||||||
grid-template:
|
grid-template:
|
||||||
minmax(var(--message-box-sender-line-height), auto) repeat(3, auto) /
|
minmax(var(--message-box-sender-line-height), auto) repeat(4, auto) /
|
||||||
var(--message-box-avatar-column-width) minmax(0, 1fr) max-content
|
var(--message-box-avatar-column-width) minmax(0, 1fr) max-content
|
||||||
8px minmax(var(--message-box-timestamp-column-width), max-content);
|
8px minmax(var(--message-box-timestamp-column-width), max-content);
|
||||||
/* Named grid areas provide flexibility for positioning grid items
|
/* Named grid areas provide flexibility for positioning grid items
|
||||||
@@ -109,7 +109,16 @@
|
|||||||
"edited message controls . time"
|
"edited message controls . time"
|
||||||
". message . . . "
|
". message . . . "
|
||||||
". more . . . "
|
". more . . . "
|
||||||
". reactions . . . ";
|
". reactions . . . "
|
||||||
|
". reminders . . . ";
|
||||||
|
|
||||||
|
&:has(.message-reminders) {
|
||||||
|
.message_reactions {
|
||||||
|
margin-bottom: calc(
|
||||||
|
var(--message-box-markdown-aligned-vertical-space) / 2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.content_edit_mode {
|
&.content_edit_mode {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
@@ -203,6 +212,16 @@
|
|||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message-reminders {
|
||||||
|
grid-area: reminders;
|
||||||
|
font-style: italic;
|
||||||
|
color: hsl(0deg 0% 53%);
|
||||||
|
|
||||||
|
.message-reminder {
|
||||||
|
margin-bottom: 0.1875em; /* 3px at 16px/1em */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.message_edit {
|
.message_edit {
|
||||||
grid-area: message;
|
grid-area: message;
|
||||||
/* Align self to start, rather than baseline, so the baseline
|
/* Align self to start, rather than baseline, so the baseline
|
||||||
@@ -274,7 +293,8 @@
|
|||||||
"avatar sender controls . time"
|
"avatar sender controls . time"
|
||||||
"avatar message . . . "
|
"avatar message . . . "
|
||||||
". more . . . "
|
". more . . . "
|
||||||
". reactions . . . ";
|
". reactions . . . "
|
||||||
|
". reminders . . . ";
|
||||||
|
|
||||||
.message_edit {
|
.message_edit {
|
||||||
/* No top margin when there's a sender row */
|
/* No top margin when there's a sender row */
|
||||||
@@ -287,12 +307,13 @@
|
|||||||
"avatar sender controls . time"
|
"avatar sender controls . time"
|
||||||
"avatar sender . . . "
|
"avatar sender . . . "
|
||||||
". more . . . "
|
". more . . . "
|
||||||
". reactions . . . ";
|
". reactions . . . "
|
||||||
|
". reminders . . . ";
|
||||||
grid-template-rows:
|
grid-template-rows:
|
||||||
var(--message-box-vertical-margin) var(
|
var(--message-box-vertical-margin) var(
|
||||||
--message-box-avatar-height
|
--message-box-avatar-height
|
||||||
)
|
)
|
||||||
minmax(0, auto) repeat(3, auto);
|
minmax(0, auto) repeat(4, auto);
|
||||||
/* We align items to the baseline on me messages,
|
/* We align items to the baseline on me messages,
|
||||||
and unset the align-content property. */
|
and unset the align-content property. */
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
|
|||||||
@@ -74,3 +74,7 @@
|
|||||||
{{#if (and (not is_hidden) msg.message_reactions)}}
|
{{#if (and (not is_hidden) msg.message_reactions)}}
|
||||||
{{> message_reactions . }}
|
{{> message_reactions . }}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if (and (not is_hidden) msg.reminders)}}
|
||||||
|
{{> message_reminders . }}
|
||||||
|
{{/if}}
|
||||||
|
|||||||
14
web/templates/message_reminders.hbs
Normal file
14
web/templates/message_reminders.hbs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<div class="message-reminders">
|
||||||
|
{{#each this/msg/reminders}}
|
||||||
|
<p class="message-reminder">
|
||||||
|
{{#tr}}
|
||||||
|
<z-link>Reminder</z-link> scheduled for {formatted_delivery_time}.
|
||||||
|
{{#*inline "z-link"~}}
|
||||||
|
<a href="#reminders" class="message-reminder-overlay-link" data-reminder-id="{{ this/reminder_id }}">
|
||||||
|
{{> @partial-block}}
|
||||||
|
</a>
|
||||||
|
{{~/inline}}
|
||||||
|
{{/tr}}
|
||||||
|
</p>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
Reference in New Issue
Block a user