mirror of https://github.com/zulip/zulip.git
recent_topics: Add Private message to recent_topics.
This commit adds private messages to the Recent topics view, to make it an all-encompassing overview of recent activity visible to the user. We add a filter "Include PM" to toggle whether PMs should be shown in recent topics. Fixes #19449.
This commit is contained in:
parent
a3f6220fe4
commit
550a32bb20
|
@ -25,6 +25,7 @@ const message_lists = mock_esm("../../static/js/message_lists");
|
|||
const message_util = mock_esm("../../static/js/message_util");
|
||||
const notifications = mock_esm("../../static/js/notifications");
|
||||
const pm_list = mock_esm("../../static/js/pm_list");
|
||||
const recent_topics_data = mock_esm("../../static/js/recent_topics_data");
|
||||
const resize = mock_esm("../../static/js/resize");
|
||||
const stream_list = mock_esm("../../static/js/stream_list");
|
||||
const unread_ops = mock_esm("../../static/js/unread_ops");
|
||||
|
@ -91,6 +92,7 @@ run_test("insert_message", ({override}) => {
|
|||
helper.redirect(message_util, "add_new_messages_data");
|
||||
helper.redirect(message_util, "add_new_messages");
|
||||
helper.redirect(notifications, "received_messages");
|
||||
helper.redirect(recent_topics_data, "process_message");
|
||||
helper.redirect(resize, "resize_page_components");
|
||||
helper.redirect(stream_list, "update_streams_sidebar");
|
||||
helper.redirect(unread_ops, "process_visible");
|
||||
|
@ -113,6 +115,7 @@ run_test("insert_message", ({override}) => {
|
|||
[unread_ops, "process_visible"],
|
||||
[notifications, "received_messages"],
|
||||
[stream_list, "update_streams_sidebar"],
|
||||
[recent_topics_data, "process_message"],
|
||||
]);
|
||||
|
||||
// Despite all of our stubbing/mocking, the call to
|
||||
|
|
|
@ -146,7 +146,6 @@ mock_esm("../../static/js/top_left_corner", {
|
|||
mock_esm("../../static/js/unread", {
|
||||
num_unread_for_topic: (stream_id, topic) => {
|
||||
if (stream_id === 1 && topic === "topic-1") {
|
||||
// Only stream1, topic-1 is read.
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
|
@ -281,6 +280,7 @@ function generate_topic_data(topic_info_array) {
|
|||
other_sender_names_html: "",
|
||||
invite_only: false,
|
||||
is_web_public: true,
|
||||
is_private: false,
|
||||
last_msg_time: "Just now",
|
||||
last_msg_url: "https://www.example.com",
|
||||
full_last_msg_date_time: "date at time",
|
||||
|
@ -290,7 +290,7 @@ function generate_topic_data(topic_info_array) {
|
|||
stream_id,
|
||||
stream_url: "https://www.example.com",
|
||||
topic,
|
||||
topic_key: get_topic_key(stream_id, topic),
|
||||
conversation_key: get_topic_key(stream_id, topic),
|
||||
topic_url: "https://www.example.com",
|
||||
unread_count,
|
||||
muted,
|
||||
|
@ -343,6 +343,7 @@ test("test_recent_topics_show", ({mock_template, override}) => {
|
|||
filter_participated: false,
|
||||
filter_unread: false,
|
||||
filter_muted: false,
|
||||
filter_pm: false,
|
||||
search_val: "",
|
||||
is_spectator: false,
|
||||
};
|
||||
|
@ -375,6 +376,7 @@ test("test_filter_all", ({mock_template}) => {
|
|||
filter_participated: false,
|
||||
filter_unread: false,
|
||||
filter_muted: false,
|
||||
filter_pm: false,
|
||||
search_val: "",
|
||||
is_spectator: true,
|
||||
};
|
||||
|
@ -401,8 +403,8 @@ test("test_filter_all", ({mock_template}) => {
|
|||
rt.process_messages([messages[0]]);
|
||||
|
||||
expected_data_to_replace_in_list_widget = [
|
||||
{last_msg_id: 10, participated: true},
|
||||
{last_msg_id: 1, participated: true},
|
||||
{last_msg_id: 10, participated: true, type: "stream"},
|
||||
{last_msg_id: 1, participated: true, type: "stream"},
|
||||
];
|
||||
|
||||
row_data = row_data.concat(generate_topic_data([[1, "topic-7", 1, true, true]]));
|
||||
|
@ -429,6 +431,7 @@ test("test_filter_unread", ({mock_template}) => {
|
|||
filter_participated: false,
|
||||
filter_unread: expected_filter_unread,
|
||||
filter_muted: false,
|
||||
filter_pm: false,
|
||||
search_val: "",
|
||||
is_spectator: false,
|
||||
});
|
||||
|
@ -482,34 +485,42 @@ test("test_filter_unread", ({mock_template}) => {
|
|||
{
|
||||
last_msg_id: 11,
|
||||
participated: true,
|
||||
type: "stream",
|
||||
},
|
||||
{
|
||||
last_msg_id: 10,
|
||||
participated: true,
|
||||
type: "stream",
|
||||
},
|
||||
{
|
||||
last_msg_id: 9,
|
||||
participated: true,
|
||||
type: "stream",
|
||||
},
|
||||
{
|
||||
last_msg_id: 7,
|
||||
participated: true,
|
||||
type: "stream",
|
||||
},
|
||||
{
|
||||
last_msg_id: 5,
|
||||
participated: false,
|
||||
type: "stream",
|
||||
},
|
||||
{
|
||||
last_msg_id: 4,
|
||||
participated: false,
|
||||
type: "stream",
|
||||
},
|
||||
{
|
||||
last_msg_id: 3,
|
||||
participated: true,
|
||||
type: "stream",
|
||||
},
|
||||
{
|
||||
last_msg_id: 1,
|
||||
participated: true,
|
||||
type: "stream",
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -539,6 +550,7 @@ test("test_filter_participated", ({mock_template}) => {
|
|||
filter_participated: expected_filter_participated,
|
||||
filter_unread: false,
|
||||
filter_muted: false,
|
||||
filter_pm: false,
|
||||
search_val: "",
|
||||
is_spectator: false,
|
||||
});
|
||||
|
@ -602,34 +614,42 @@ test("test_filter_participated", ({mock_template}) => {
|
|||
{
|
||||
last_msg_id: 11,
|
||||
participated: true,
|
||||
type: "stream",
|
||||
},
|
||||
{
|
||||
last_msg_id: 10,
|
||||
participated: true,
|
||||
type: "stream",
|
||||
},
|
||||
{
|
||||
last_msg_id: 9,
|
||||
participated: true,
|
||||
type: "stream",
|
||||
},
|
||||
{
|
||||
last_msg_id: 7,
|
||||
participated: true,
|
||||
type: "stream",
|
||||
},
|
||||
{
|
||||
last_msg_id: 5,
|
||||
participated: false,
|
||||
type: "stream",
|
||||
},
|
||||
{
|
||||
last_msg_id: 4,
|
||||
participated: false,
|
||||
type: "stream",
|
||||
},
|
||||
{
|
||||
last_msg_id: 3,
|
||||
participated: true,
|
||||
type: "stream",
|
||||
},
|
||||
{
|
||||
last_msg_id: 1,
|
||||
participated: true,
|
||||
type: "stream",
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -656,7 +676,7 @@ test("basic assertions", ({mock_template}) => {
|
|||
|
||||
mock_template("recent_topics_table.hbs", false, () => {});
|
||||
mock_template("recent_topic_row.hbs", true, (data, html) => {
|
||||
assert.ok(html.startsWith('<tr id="recent_topic'));
|
||||
assert.ok(html.startsWith('<tr id="recent_conversation'));
|
||||
});
|
||||
|
||||
stub_out_filter_buttons();
|
||||
|
@ -673,34 +693,42 @@ test("basic assertions", ({mock_template}) => {
|
|||
{
|
||||
last_msg_id: 11,
|
||||
participated: true,
|
||||
type: "stream",
|
||||
},
|
||||
{
|
||||
last_msg_id: 10,
|
||||
participated: true,
|
||||
type: "stream",
|
||||
},
|
||||
{
|
||||
last_msg_id: 9,
|
||||
participated: true,
|
||||
type: "stream",
|
||||
},
|
||||
{
|
||||
last_msg_id: 7,
|
||||
participated: true,
|
||||
type: "stream",
|
||||
},
|
||||
{
|
||||
last_msg_id: 5,
|
||||
participated: false,
|
||||
type: "stream",
|
||||
},
|
||||
{
|
||||
last_msg_id: 4,
|
||||
participated: false,
|
||||
type: "stream",
|
||||
},
|
||||
{
|
||||
last_msg_id: 3,
|
||||
participated: true,
|
||||
type: "stream",
|
||||
},
|
||||
{
|
||||
last_msg_id: 1,
|
||||
participated: true,
|
||||
type: "stream",
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -713,15 +741,16 @@ test("basic assertions", ({mock_template}) => {
|
|||
"4:topic-10,1:topic-7,1:topic-6,1:topic-5,1:topic-4,1:topic-3,1:topic-2,1:topic-1",
|
||||
);
|
||||
|
||||
// Process private message
|
||||
rt_data.process_message({
|
||||
type: "private",
|
||||
to_user_ids: "6,7,8",
|
||||
});
|
||||
|
||||
// Private msgs are not processed.
|
||||
assert.equal(all_topics.size, 8);
|
||||
all_topics = rt_data.get();
|
||||
assert.equal(all_topics.size, 9);
|
||||
assert.equal(
|
||||
Array.from(all_topics.keys()).toString(),
|
||||
"4:topic-10,1:topic-7,1:topic-6,1:topic-5,1:topic-4,1:topic-3,1:topic-2,1:topic-1",
|
||||
"4:topic-10,1:topic-7,1:topic-6,1:topic-5,1:topic-4,1:topic-3,1:topic-2,1:topic-1,6,7,8",
|
||||
);
|
||||
|
||||
// participated
|
||||
|
@ -745,7 +774,7 @@ test("basic assertions", ({mock_template}) => {
|
|||
all_topics = rt_data.get();
|
||||
assert.equal(
|
||||
Array.from(all_topics.keys()).toString(),
|
||||
"1:topic-3,4:topic-10,1:topic-7,1:topic-6,1:topic-5,1:topic-4,1:topic-2,1:topic-1",
|
||||
"1:topic-3,4:topic-10,1:topic-7,1:topic-6,1:topic-5,1:topic-4,1:topic-2,1:topic-1,6,7,8",
|
||||
);
|
||||
verify_topic_data(all_topics, stream1, topic3, id, true);
|
||||
|
||||
|
@ -762,7 +791,7 @@ test("basic assertions", ({mock_template}) => {
|
|||
all_topics = rt_data.get();
|
||||
assert.equal(
|
||||
Array.from(all_topics.keys()).toString(),
|
||||
"1:topic-7,1:topic-3,4:topic-10,1:topic-6,1:topic-5,1:topic-4,1:topic-2,1:topic-1",
|
||||
"1:topic-7,1:topic-3,4:topic-10,1:topic-6,1:topic-5,1:topic-4,1:topic-2,1:topic-1,6,7,8",
|
||||
);
|
||||
|
||||
// update_topic_is_muted now relies on external libraries completely
|
||||
|
|
|
@ -428,6 +428,7 @@ test("private_messages", () => {
|
|||
display_recipient: [{id: alice.user_id}],
|
||||
type: "private",
|
||||
unread: true,
|
||||
to_user_ids: alice.user_id.toString(),
|
||||
};
|
||||
|
||||
const read_message = {
|
||||
|
|
|
@ -481,7 +481,7 @@ export function initialize() {
|
|||
// The element's parent may re-render while it is being passed to
|
||||
// other functions, so, we get topic_key first.
|
||||
const $topic_row = $(e.target).closest("tr");
|
||||
const topic_key = $topic_row.attr("id").slice("recent_topics:".length - 1);
|
||||
const topic_key = $topic_row.attr("id").slice("recent_conversation:".length);
|
||||
const topic_row_index = $topic_row.index();
|
||||
recent_topics_ui.focus_clicked_element(
|
||||
topic_row_index,
|
||||
|
|
|
@ -9,6 +9,11 @@ import * as people from "./people";
|
|||
import * as recent_topics_util from "./recent_topics_util";
|
||||
|
||||
export function get_recipient_label(message) {
|
||||
// TODO: This code path is bit of a type-checking disaster; we mix
|
||||
// actual message objects with fake objects containing just a
|
||||
// couple fields, both those constructed here and potentially
|
||||
// passed in.
|
||||
|
||||
if (message === undefined) {
|
||||
if (message_lists.current.empty()) {
|
||||
// For empty narrows where there's a clear reply target,
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
import * as people from "./people";
|
||||
import {get_topic_key} from "./recent_topics_util";
|
||||
import {get_key_from_message} from "./recent_topics_util";
|
||||
|
||||
export const topics = new Map(); // Key is stream-id:topic.
|
||||
|
||||
export function process_message(msg) {
|
||||
// This function returns if topic_data
|
||||
// has changed or not.
|
||||
if (msg.type !== "stream") {
|
||||
// We don't process private messages yet.
|
||||
return false;
|
||||
}
|
||||
// Initialize topic data
|
||||
const key = get_topic_key(msg.stream_id, msg.topic);
|
||||
|
||||
// Initialize topic and pm data
|
||||
// Key for private message is the user id's
|
||||
// to whom the message is begin sent.
|
||||
const key = get_key_from_message(msg);
|
||||
if (!topics.has(key)) {
|
||||
topics.set(key, {
|
||||
last_msg_id: -1,
|
||||
participated: false,
|
||||
type: msg.type,
|
||||
});
|
||||
}
|
||||
// Update topic data
|
||||
|
|
|
@ -5,6 +5,7 @@ import render_recent_topic_row from "../templates/recent_topic_row.hbs";
|
|||
import render_recent_topics_filters from "../templates/recent_topics_filters.hbs";
|
||||
import render_recent_topics_body from "../templates/recent_topics_table.hbs";
|
||||
|
||||
import * as buddy_data from "./buddy_data";
|
||||
import * as compose_closed_ui from "./compose_closed_ui";
|
||||
import * as hash_util from "./hash_util";
|
||||
import {$t} from "./i18n";
|
||||
|
@ -21,7 +22,13 @@ import {page_params} from "./page_params";
|
|||
import * as people from "./people";
|
||||
import * as recent_senders from "./recent_senders";
|
||||
import {get, process_message, topics} from "./recent_topics_data";
|
||||
import {get_topic_key, is_in_focus, is_visible, set_visible} from "./recent_topics_util";
|
||||
import {
|
||||
get_key_from_message,
|
||||
get_topic_key,
|
||||
is_in_focus,
|
||||
is_visible,
|
||||
set_visible,
|
||||
} from "./recent_topics_util";
|
||||
import * as stream_data from "./stream_data";
|
||||
import * as stream_list from "./stream_list";
|
||||
import * as sub_store from "./sub_store";
|
||||
|
@ -69,7 +76,8 @@ export const COLUMNS = {
|
|||
// implement wraparound of elements with the right/left keys. Must be
|
||||
// increased when we add new actions, or rethought if we add optional
|
||||
// actions that only appear in some rows.
|
||||
const MAX_SELECTABLE_COLS = 4;
|
||||
const MAX_SELECTABLE_TOPIC_COLS = 4;
|
||||
const MAX_SELECTABLE_PM_COLS = 2;
|
||||
|
||||
// we use localstorage to persist the recent topic filters
|
||||
const ls_key = "recent_topic_filters";
|
||||
|
@ -77,6 +85,8 @@ const ls = localstorage();
|
|||
|
||||
let filters = new Set();
|
||||
|
||||
const recent_conversation_key_prefix = "recent_conversion:";
|
||||
|
||||
export function clear_for_tests() {
|
||||
filters.clear();
|
||||
topics.clear();
|
||||
|
@ -116,6 +126,33 @@ function is_table_focused() {
|
|||
return $current_focus_elem === "table";
|
||||
}
|
||||
|
||||
function get_row_type(row) {
|
||||
// Return "private" or "stream"
|
||||
// We use CSS method for finding row type until topics_widget gets initialized.
|
||||
if (!topics_widget) {
|
||||
const $topic_rows = $("#recent_topics_table table tbody tr");
|
||||
const $topic_row = $topic_rows.eq(row);
|
||||
const is_private = $topic_row.attr("data-private");
|
||||
if (is_private) {
|
||||
return "private";
|
||||
}
|
||||
return "stream";
|
||||
}
|
||||
|
||||
const current_list = topics_widget.get_current_list();
|
||||
const current_row = current_list[row];
|
||||
return current_row.type;
|
||||
}
|
||||
|
||||
function get_max_selectable_cols(row) {
|
||||
// returns maximum number of columns in stream message or private message row.
|
||||
const type = get_row_type(row);
|
||||
if (type === "private") {
|
||||
return MAX_SELECTABLE_PM_COLS;
|
||||
}
|
||||
return MAX_SELECTABLE_TOPIC_COLS;
|
||||
}
|
||||
|
||||
function set_table_focus(row, col, using_keyboard) {
|
||||
const $topic_rows = $("#recent_topics_table table tbody tr");
|
||||
if ($topic_rows.length === 0 || row < 0 || row >= $topic_rows.length) {
|
||||
|
@ -147,25 +184,35 @@ function set_table_focus(row, col, using_keyboard) {
|
|||
}
|
||||
}
|
||||
|
||||
const message = {
|
||||
// TODO: This fake "message" object is designed to allow using the
|
||||
// get_recipient_label helper inside compose_closed_ui. Surely
|
||||
// there's a more readable way to write this code.
|
||||
const type = get_row_type(row);
|
||||
let message;
|
||||
if (type === "private") {
|
||||
message = {
|
||||
display_reply_to: $topic_row.find(".recent_topic_name a").text(),
|
||||
};
|
||||
} else {
|
||||
message = {
|
||||
stream: $topic_row.find(".recent_topic_stream a").text(),
|
||||
topic: $topic_row.find(".recent_topic_name a").text(),
|
||||
};
|
||||
}
|
||||
compose_closed_ui.update_reply_recipient_label(message);
|
||||
return true;
|
||||
}
|
||||
|
||||
export function get_focused_row_message() {
|
||||
if (is_table_focused()) {
|
||||
const recent_topic_id_prefix_len = "recent_topic:".length;
|
||||
const $topic_rows = $("#recent_topics_table table tbody tr");
|
||||
if ($topic_rows.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const $topic_row = $topic_rows.eq(row_focus);
|
||||
const topic_id = $topic_row.attr("id").slice(recent_topic_id_prefix_len);
|
||||
const topic_last_msg_id = topics.get(topic_id).last_msg_id;
|
||||
const conversation_id = $topic_row.attr("id").slice(recent_conversation_key_prefix.length);
|
||||
const topic_last_msg_id = topics.get(conversation_id).last_msg_id;
|
||||
return message_store.get(topic_last_msg_id);
|
||||
}
|
||||
return undefined;
|
||||
|
@ -252,37 +299,84 @@ export function process_messages(messages) {
|
|||
}
|
||||
}
|
||||
|
||||
function format_topic(topic_data) {
|
||||
const last_msg = message_store.get(topic_data.last_msg_id);
|
||||
const stream = last_msg.stream;
|
||||
const stream_id = last_msg.stream_id;
|
||||
const stream_info = sub_store.get(stream_id);
|
||||
if (stream_info === undefined) {
|
||||
// stream was deleted
|
||||
return {};
|
||||
function message_to_conversation_unread_count(msg) {
|
||||
if (msg.type === "private") {
|
||||
return unread.num_unread_for_person(msg.to_user_ids);
|
||||
}
|
||||
const topic = last_msg.topic;
|
||||
return unread.num_unread_for_topic(msg.stream_id, msg.topic);
|
||||
}
|
||||
|
||||
function format_conversation(conversation_data) {
|
||||
const context = {};
|
||||
const last_msg = message_store.get(conversation_data.last_msg_id);
|
||||
const time = new Date(last_msg.timestamp * 1000);
|
||||
const last_msg_time = timerender.last_seen_status_from_date(time);
|
||||
const full_datetime = timerender.get_full_datetime(time);
|
||||
const type = last_msg.type;
|
||||
context.full_last_msg_date_time = timerender.get_full_datetime(time);
|
||||
context.conversation_key = get_key_from_message(last_msg);
|
||||
context.unread_count = message_to_conversation_unread_count(last_msg);
|
||||
context.last_msg_time = timerender.last_seen_status_from_date(time);
|
||||
context.is_private = last_msg.type === "private";
|
||||
let all_senders;
|
||||
let senders;
|
||||
let displayed_other_senders;
|
||||
let extra_sender_ids;
|
||||
|
||||
if (type === "stream") {
|
||||
const stream_info = sub_store.get(last_msg.stream_id);
|
||||
|
||||
// Stream info
|
||||
context.stream_id = last_msg.stream_id;
|
||||
context.stream = last_msg.stream;
|
||||
context.stream_color = stream_info.color;
|
||||
context.stream_url = hash_util.by_stream_url(context.stream_id);
|
||||
context.invite_only = stream_info.invite_only;
|
||||
context.is_web_public = stream_info.is_web_public;
|
||||
// Topic info
|
||||
context.topic = last_msg.topic;
|
||||
context.topic_url = hash_util.by_stream_topic_url(context.stream_id, context.topic);
|
||||
|
||||
// We hide the row according to filters or if it's muted.
|
||||
// We only supply the data to the topic rows and let jquery
|
||||
// display / hide them according to filters instead of
|
||||
// doing complete re-render.
|
||||
const topic_muted = Boolean(user_topics.is_topic_muted(stream_id, topic));
|
||||
const stream_muted = stream_data.is_muted(stream_id);
|
||||
const muted = topic_muted || stream_muted;
|
||||
const unread_count = unread.num_unread_for_topic(stream_id, topic);
|
||||
context.topic_muted = Boolean(user_topics.is_topic_muted(context.stream_id, context.topic));
|
||||
const stream_muted = stream_data.is_muted(context.stream_id);
|
||||
context.muted = context.topic_muted || stream_muted;
|
||||
|
||||
// Display in most recent sender first order
|
||||
const all_senders = recent_senders.get_topic_recent_senders(stream_id, topic);
|
||||
const senders = all_senders.slice(-MAX_AVATAR);
|
||||
const senders_info = people.sender_info_for_recent_topics_row(senders);
|
||||
all_senders = recent_senders.get_topic_recent_senders(context.stream_id, context.topic);
|
||||
senders = all_senders.slice(-MAX_AVATAR);
|
||||
|
||||
// Collect extra sender fullname for tooltip
|
||||
extra_sender_ids = all_senders.slice(0, -MAX_AVATAR);
|
||||
displayed_other_senders = extra_sender_ids.slice(-MAX_EXTRA_SENDERS);
|
||||
} else if (type === "private") {
|
||||
// Private message info
|
||||
context.pm_with = last_msg.display_reply_to;
|
||||
context.recipient_id = last_msg.recipient_id;
|
||||
context.pm_url = last_msg.pm_with_url;
|
||||
context.is_group = last_msg.display_recipient.length > 2;
|
||||
|
||||
// Display in most recent sender first order
|
||||
all_senders = last_msg.display_recipient;
|
||||
senders = all_senders.slice(-MAX_AVATAR).map((sender) => sender.id);
|
||||
|
||||
if (!context.is_group) {
|
||||
context.user_circle_class = buddy_data.get_user_circle_class(
|
||||
Number.parseInt(last_msg.to_user_ids, 10),
|
||||
);
|
||||
}
|
||||
|
||||
// Collect extra senders fullname for tooltip.
|
||||
const extra_sender_ids = all_senders.slice(0, -MAX_AVATAR);
|
||||
const displayed_other_senders = extra_sender_ids.slice(-MAX_EXTRA_SENDERS);
|
||||
extra_sender_ids = all_senders.slice(0, -MAX_AVATAR);
|
||||
displayed_other_senders = extra_sender_ids
|
||||
.slice(-MAX_EXTRA_SENDERS)
|
||||
.map((sender) => sender.id);
|
||||
}
|
||||
|
||||
context.senders = people.sender_info_for_recent_topics_row(senders);
|
||||
context.other_senders_count = Math.max(0, all_senders.length - MAX_AVATAR);
|
||||
extra_sender_ids = all_senders.slice(0, -MAX_AVATAR);
|
||||
const displayed_other_names = people.get_display_full_names(displayed_other_senders.reverse());
|
||||
|
||||
if (extra_sender_ids.length > MAX_EXTRA_SENDERS) {
|
||||
|
@ -301,39 +395,19 @@ function format_topic(topic_data) {
|
|||
),
|
||||
);
|
||||
}
|
||||
const other_sender_names_html = displayed_other_names
|
||||
context.other_sender_names_html = displayed_other_names
|
||||
.map((name) => _.escape(name))
|
||||
.join("<br />");
|
||||
context.participated = conversation_data.participated;
|
||||
context.last_msg_url = hash_util.by_conversation_and_time_url(last_msg);
|
||||
|
||||
return {
|
||||
// stream info
|
||||
stream_id,
|
||||
stream,
|
||||
stream_color: stream_info.color,
|
||||
invite_only: stream_info.invite_only,
|
||||
is_web_public: stream_info.is_web_public,
|
||||
stream_url: hash_util.by_stream_url(stream_id),
|
||||
|
||||
topic,
|
||||
topic_key: get_topic_key(stream_id, topic),
|
||||
unread_count,
|
||||
last_msg_time,
|
||||
last_msg_url: hash_util.by_conversation_and_time_url(last_msg),
|
||||
topic_url: hash_util.by_stream_topic_url(stream_id, topic),
|
||||
senders: senders_info,
|
||||
other_senders_count: Math.max(0, all_senders.length - MAX_AVATAR),
|
||||
other_sender_names_html,
|
||||
muted,
|
||||
topic_muted,
|
||||
participated: topic_data.participated,
|
||||
full_last_msg_date_time: full_datetime,
|
||||
};
|
||||
return context;
|
||||
}
|
||||
|
||||
function get_topic_row(topic_data) {
|
||||
const msg = message_store.get(topic_data.last_msg_id);
|
||||
const topic_key = get_topic_key(msg.stream_id, msg.topic);
|
||||
return $(`#${CSS.escape("recent_topic:" + topic_key)}`);
|
||||
const topic_key = get_key_from_message(msg);
|
||||
return $(`#${CSS.escape(recent_conversation_key_prefix + topic_key)}`);
|
||||
}
|
||||
|
||||
export function process_topic_edit(old_stream_id, old_topic, new_topic, new_stream_id) {
|
||||
|
@ -372,14 +446,14 @@ export function filters_should_hide_topic(topic_data) {
|
|||
const msg = message_store.get(topic_data.last_msg_id);
|
||||
const sub = sub_store.get(msg.stream_id);
|
||||
|
||||
if (sub === undefined || !sub.subscribed) {
|
||||
if ((sub === undefined || !sub.subscribed) && topic_data.type === "stream") {
|
||||
// Never try to process deactivated & unsubscribed stream msgs.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (filters.has("unread")) {
|
||||
const unreadCount = unread.num_unread_for_topic(msg.stream_id, msg.topic);
|
||||
if (unreadCount === 0) {
|
||||
const unread_count = message_to_conversation_unread_count(msg);
|
||||
if (unread_count === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -388,7 +462,7 @@ export function filters_should_hide_topic(topic_data) {
|
|||
return true;
|
||||
}
|
||||
|
||||
if (!filters.has("include_muted")) {
|
||||
if (!filters.has("include_muted") && topic_data.type === "stream") {
|
||||
const topic_muted = Boolean(user_topics.is_topic_muted(msg.stream_id, msg.topic));
|
||||
const stream_muted = stream_data.is_muted(msg.stream_id);
|
||||
if (topic_muted || stream_muted) {
|
||||
|
@ -396,6 +470,10 @@ export function filters_should_hide_topic(topic_data) {
|
|||
}
|
||||
}
|
||||
|
||||
if (!filters.has("include_private") && topic_data.type === "private") {
|
||||
return true;
|
||||
}
|
||||
|
||||
const search_keyword = $("#recent_topics_search").val();
|
||||
if (!topic_in_search_results(search_keyword, msg.stream, msg.topic)) {
|
||||
return true;
|
||||
|
@ -438,7 +516,7 @@ export function update_topic_is_muted(stream_id, topic) {
|
|||
}
|
||||
|
||||
export function update_topic_unread_count(message) {
|
||||
const topic_key = get_topic_key(message.stream_id, message.topic);
|
||||
const topic_key = get_key_from_message(message);
|
||||
inplace_rerender(topic_key);
|
||||
}
|
||||
|
||||
|
@ -490,6 +568,7 @@ export function update_filters_view() {
|
|||
filter_participated: filters.has("participated"),
|
||||
filter_unread: filters.has("unread"),
|
||||
filter_muted: filters.has("include_muted"),
|
||||
filter_pm: filters.has("include_private"),
|
||||
is_spectator: page_params.is_spectator,
|
||||
});
|
||||
$("#recent_filters_group").html(rendered_filters);
|
||||
|
@ -498,26 +577,40 @@ export function update_filters_view() {
|
|||
topics_widget.hard_redraw();
|
||||
}
|
||||
|
||||
function stream_sort(a, b) {
|
||||
const a_stream = message_store.get(a.last_msg_id).stream;
|
||||
const b_stream = message_store.get(b.last_msg_id).stream;
|
||||
if (a_stream > b_stream) {
|
||||
function sort_comparator(a, b) {
|
||||
// compares strings in lowercase and returns -1, 0, 1
|
||||
if (a.toLowerCase() > b.toLowerCase()) {
|
||||
return 1;
|
||||
} else if (a_stream === b_stream) {
|
||||
} else if (a.toLowerCase() === b.toLowerCase()) {
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
function topic_sort(a, b) {
|
||||
const a_topic = message_store.get(a.last_msg_id).topic;
|
||||
const b_topic = message_store.get(b.last_msg_id).topic;
|
||||
if (a_topic > b_topic) {
|
||||
return 1;
|
||||
} else if (a_topic === b_topic) {
|
||||
return 0;
|
||||
function stream_sort(a, b) {
|
||||
if (a.type === b.type) {
|
||||
const a_msg = message_store.get(a.last_msg_id);
|
||||
const b_msg = message_store.get(b.last_msg_id);
|
||||
|
||||
if (a.type === "stream") {
|
||||
return sort_comparator(a_msg.stream, b_msg.stream);
|
||||
}
|
||||
return -1;
|
||||
return sort_comparator(a_msg.display_reply_to, b_msg.display_reply_to);
|
||||
}
|
||||
// if type is not same sort between "private" and "stream"
|
||||
return sort_comparator(a.type, b.type);
|
||||
}
|
||||
|
||||
function topic_sort_key(conversation_data) {
|
||||
const message = message_store.get(conversation_data.last_msg_id);
|
||||
if (message.type === "private") {
|
||||
return message.display_reply_to;
|
||||
}
|
||||
return message.topic;
|
||||
}
|
||||
|
||||
function topic_sort(a, b) {
|
||||
return sort_comparator(topic_sort_key(a), topic_sort_key(b));
|
||||
}
|
||||
|
||||
function topic_offset_to_visible_area(topic_row) {
|
||||
|
@ -601,6 +694,7 @@ export function complete_rerender() {
|
|||
filter_participated: filters.has("participated"),
|
||||
filter_unread: filters.has("unread"),
|
||||
filter_muted: filters.has("include_muted"),
|
||||
filter_pm: filters.has("include_private"),
|
||||
search_val: $("#recent_topics_search").val() || "",
|
||||
is_spectator: page_params.is_spectator,
|
||||
});
|
||||
|
@ -611,7 +705,7 @@ export function complete_rerender() {
|
|||
name: "recent_topics_table",
|
||||
$parent_container: $("#recent_topics_table"),
|
||||
modifier(item) {
|
||||
return render_recent_topic_row(format_topic(item));
|
||||
return render_recent_topic_row(format_conversation(item));
|
||||
},
|
||||
filter: {
|
||||
// We use update_filters_view & filters_should_hide_topic to do all the
|
||||
|
@ -730,41 +824,67 @@ export function focus_clicked_element(topic_row_index, col, topic_key) {
|
|||
}
|
||||
|
||||
function left_arrow_navigation(row, col) {
|
||||
if (col === MAX_SELECTABLE_COLS - 1 && !has_unread(row)) {
|
||||
const type = get_row_type(row);
|
||||
|
||||
if (type === "stream" && col === MAX_SELECTABLE_TOPIC_COLS - 1 && !has_unread(row)) {
|
||||
col_focus -= 1;
|
||||
}
|
||||
col_focus -= 1;
|
||||
|
||||
col_focus -= 1;
|
||||
if (col_focus < 0) {
|
||||
col_focus = MAX_SELECTABLE_COLS - 1;
|
||||
col_focus = get_max_selectable_cols(row) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
function right_arrow_navigation(row, col) {
|
||||
if (col === 1 && !has_unread(row)) {
|
||||
const type = get_row_type(row);
|
||||
|
||||
if (type === "stream" && col === 1 && !has_unread(row)) {
|
||||
col_focus += 1;
|
||||
}
|
||||
col_focus += 1;
|
||||
|
||||
if (col_focus >= MAX_SELECTABLE_COLS) {
|
||||
col_focus += 1;
|
||||
if (col_focus >= get_max_selectable_cols(row)) {
|
||||
col_focus = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function up_arrow_navigation(row, col) {
|
||||
if (col === 2 && row - 1 >= 0 && !has_unread(row - 1)) {
|
||||
row_focus -= 1;
|
||||
if (row_focus < 0) {
|
||||
return;
|
||||
}
|
||||
const type = get_row_type(row);
|
||||
|
||||
if (type === "stream" && col === 2 && row - 1 >= 0 && !has_unread(row - 1)) {
|
||||
col_focus = 1;
|
||||
}
|
||||
row_focus -= 1;
|
||||
}
|
||||
|
||||
function down_arrow_navigation(row, col) {
|
||||
if (col === 2 && !has_unread(row + 1)) {
|
||||
const type = get_row_type(row);
|
||||
|
||||
if (type === "stream" && col === 2 && !has_unread(row + 1)) {
|
||||
col_focus = 1;
|
||||
}
|
||||
row_focus += 1;
|
||||
}
|
||||
|
||||
function check_row_type_transition(row, col) {
|
||||
// This function checks if the row is transitioning
|
||||
// from type "Private messages" to "Stream" or vice versa.
|
||||
// This helps in setting the col_focus as maximum column
|
||||
// of both the type are different.
|
||||
if (row < 0) {
|
||||
return false;
|
||||
}
|
||||
const max_col = get_max_selectable_cols(row);
|
||||
if (col > max_col - 1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function change_focused_element($elt, input_key) {
|
||||
// Called from hotkeys.js; like all logic in that module,
|
||||
// returning true will cause the caller to do
|
||||
|
@ -913,7 +1033,13 @@ export function change_focused_element($elt, input_key) {
|
|||
break;
|
||||
case "up_arrow":
|
||||
up_arrow_navigation(row_focus, col_focus);
|
||||
break;
|
||||
}
|
||||
|
||||
if (check_row_type_transition(row_focus, col_focus)) {
|
||||
col_focus = get_max_selectable_cols(row_focus) - 1;
|
||||
}
|
||||
|
||||
set_table_focus(row_focus, col_focus, true);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -29,3 +29,15 @@ export function is_in_focus() {
|
|||
export function get_topic_key(stream_id, topic) {
|
||||
return stream_id + ":" + topic.toLowerCase();
|
||||
}
|
||||
|
||||
export function get_key_from_message(msg) {
|
||||
if (msg.type === "private") {
|
||||
// The to_user_ids field on a private message object is a
|
||||
// string containing the user IDs involved in the message in
|
||||
// sorted order.
|
||||
return msg.to_user_ids;
|
||||
} else if (msg.type === "stream") {
|
||||
return get_topic_key(msg.stream_id, msg.topic);
|
||||
}
|
||||
throw new Error(`Invalid message type ${msg.type}`);
|
||||
}
|
||||
|
|
|
@ -661,6 +661,11 @@ body.dark-theme {
|
|||
|
||||
#recent_topics_table {
|
||||
border-color: hsla(0, 0%, 0%, 0.6);
|
||||
|
||||
.fa-envelope,
|
||||
.fa-group {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
thead,
|
||||
|
|
|
@ -70,6 +70,21 @@
|
|||
padding-right: 3px;
|
||||
}
|
||||
|
||||
.fa-group {
|
||||
font-size: 0.8rem;
|
||||
margin-left: 5px;
|
||||
/* color: hsl(105, 2%, 50%); */
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.fa-envelope {
|
||||
font-size: 0.7rem;
|
||||
margin-right: 2px;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.table_fix_head {
|
||||
padding: 0 !important;
|
||||
/* 100px = space occupied by `recent_topics_filter_buttons`( ~49px)
|
||||
|
@ -148,10 +163,31 @@
|
|||
background-color: hsl(105, 2%, 50%);
|
||||
}
|
||||
|
||||
.unread_count_pm {
|
||||
/* 10px of unread count + 23px for bell icon */
|
||||
margin-right: 33px;
|
||||
/* match the opacity with topic unread count without hover */
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.unread_hidden {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.user_circle {
|
||||
/* Shrink the user activity circle for the recent topics context. */
|
||||
min-width: 7px;
|
||||
height: 7px;
|
||||
margin-left: 8px;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.flex_container_pm {
|
||||
/* Flex container to fit in user circle and group icon */
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.flex_container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@ -287,11 +323,22 @@
|
|||
as new messages arrive from the server. */
|
||||
.recent_topic_stream {
|
||||
width: 25%;
|
||||
padding: 8px;
|
||||
padding: 8px 0 8px 8px;
|
||||
}
|
||||
|
||||
.recent_topic_name {
|
||||
width: 40%;
|
||||
|
||||
.line_clamp {
|
||||
/* This -webkit-box display property is webkit-specific, but
|
||||
it appears that line clamping works fine for this component
|
||||
on Firefox anyway. */
|
||||
/* stylelint-disable-next-line value-no-vendor-prefix */
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.recent_topic_users {
|
||||
|
@ -327,6 +374,11 @@
|
|||
margin-right: 5px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.unread_count_pm {
|
||||
/* Margin equal to size of recent topic actions */
|
||||
margin-right: 44px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,18 +1,44 @@
|
|||
<tr id="recent_topic:{{topic_key}}" {{#if unread_count}}class="unread_topic"{{/if}} data-unread-count="{{unread_count}}" data-muted="{{muted}}" data-participated="{{participated}}">
|
||||
<tr id="recent_conversation:{{conversation_key}}" {{#if unread_count}}class="unread_topic"{{/if}} data-unread-count="{{unread_count}}" data-muted="{{muted}}" data-participated="{{participated}}" data-private="{{is_private}}">
|
||||
<td class="recent_topic_stream">
|
||||
<div class="recent_topics_focusable">
|
||||
<div class="flex_container flex_container_pm">
|
||||
<div class="left_part recent_topics_focusable">
|
||||
{{#if is_private}}
|
||||
<span class="fa fa-envelope"></span>
|
||||
<a href="{{pm_url}}">Private messages</a>
|
||||
{{else}}
|
||||
<span id="stream_sidebar_privacy_swatch_{{stream_id}}" class="stream-privacy filter-icon" style="color: {{stream_color}}">
|
||||
{{> stream_privacy }}
|
||||
</span>
|
||||
<a href="{{stream_url}}">{{stream}}</a>
|
||||
<a href="{{topic_url}}">{{stream}}</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{!-- For presence/group indicator --}}
|
||||
{{#if is_private}}
|
||||
<div class="right_part">
|
||||
<span class="stream-privacy filter-icon">
|
||||
{{#if is_group}}
|
||||
<span class="fa fa-group"></span>
|
||||
{{else}}
|
||||
<span class="{{user_circle_class}} user_circle"></span>
|
||||
{{/if}}
|
||||
</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</td>
|
||||
<td class="recent_topic_name">
|
||||
<div class="flex_container">
|
||||
<div class="left_part recent_topics_focusable">
|
||||
<div class="left_part recent_topics_focusable line_clamp">
|
||||
{{#if is_private}}
|
||||
<a href="{{pm_url}}">{{pm_with}}</a>
|
||||
{{else}}
|
||||
<a href="{{topic_url}}">{{topic}}</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="right_part">
|
||||
{{#if is_private}}
|
||||
<span class="unread_count unread_count_pm {{#unless unread_count}}unread_hidden{{/unless}}">{{unread_count}}</span>
|
||||
{{else}}
|
||||
<div class="recent_topic_actions">
|
||||
<div class="recent_topics_focusable hidden-for-spectators">
|
||||
<span class="unread_count {{#unless unread_count}}unread_hidden{{/unless}} tippy-zulip-tooltip on_hover_topic_read" data-stream-id="{{stream_id}}" data-topic-name="{{topic}}" data-tippy-content="{{t 'Mark as read' }}" role="button" tabindex="0" aria-label="{{t 'Mark as read' }}">{{unread_count}}</span>
|
||||
|
@ -27,16 +53,17 @@
|
|||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class='recent_topic_users'>
|
||||
<ul class="recent_topics_participants">
|
||||
{{#if other_senders_count}}
|
||||
<li class="recent_topics_participant_item tippy-zulip-tooltip" data-tooltip-template-id="recent_topics_participant_overflow_tooltip:{{topic_key}}">
|
||||
<li class="recent_topics_participant_item tippy-zulip-tooltip" data-tooltip-template-id="recent_topics_participant_overflow_tooltip:{{conversation_key}}">
|
||||
<span class="recent_topics_participant_overflow">+{{other_senders_count}}</span>
|
||||
</li>
|
||||
<template id="recent_topics_participant_overflow_tooltip:{{topic_key}}">{{{other_sender_names_html}}}</template>
|
||||
<template id="recent_topics_participant_overflow_tooltip:{{conversation_key}}">{{{other_sender_names_html}}}</template>
|
||||
{{/if}}
|
||||
{{#each senders}}
|
||||
{{#if this.is_muted}}
|
||||
|
|
|
@ -1,4 +1,12 @@
|
|||
<button data-filter="all" type="button" class="btn btn-default btn-recent-filters">{{t 'All' }}</button>
|
||||
<button data-filter="include_private" type="button" class="btn btn-default btn-recent-filters {{#if is_spectator}}fake_disabled_button{{/if}}" role="checkbox" aria-checked="true">
|
||||
{{#if filter_pm}}
|
||||
<i class="fa fa-check-square-o"></i>
|
||||
{{else}}
|
||||
<i class="fa fa-square-o"></i>
|
||||
{{/if}}
|
||||
{{t 'Include PMs' }}
|
||||
</button>
|
||||
<button data-filter="include_muted" type="button" class="btn btn-default btn-recent-filters {{#if is_spectator}}fake_disabled_button{{/if}}" role="checkbox" aria-checked="false">
|
||||
{{#if filter_muted }}
|
||||
<i class="fa fa-check-square-o"></i>
|
||||
|
|
|
@ -134,6 +134,7 @@ EXEMPT_FILES = make_set(
|
|||
"static/js/realm_playground.js",
|
||||
"static/js/realm_user_settings_defaults.ts",
|
||||
"static/js/recent_topics_ui.js",
|
||||
"static/js/recent_topics_util.js",
|
||||
"static/js/reload.js",
|
||||
"static/js/reminder.js",
|
||||
"static/js/resize.js",
|
||||
|
|
Loading…
Reference in New Issue