recent_topics: Move from overlay to a narrow-like view.

Recent Topics is no longer an overlay now, but note that it is
also not a typical messages narrow. It can reside between
an overlay and a Filter in the sense that it is dispalyed as
a typical Filter narrow but has properties of an Overlay.

Compose box is not visible in this view as it will be confusing
to many users and hence compose shortcuts have also been disabled.

Keyboard shortcuts that apply on messages have also been disabled.

The remaining shortcuts that apply to a narrow are still accessible
here.
This commit is contained in:
Aman Agrawal
2020-07-05 15:49:09 +05:30
committed by Tim Abbott
parent 9ed3e47796
commit 1eafb1d8b3
19 changed files with 210 additions and 139 deletions

View File

@@ -18,10 +18,12 @@ const hash_util = zrequire("hash_util");
const hashchange = zrequire("hashchange");
const stream_data = zrequire("stream_data");
zrequire("navigate");
zrequire("recent_topics");
set_global("search", {
update_button_visibility: () => {},
});
set_global((recent_topics.is_visible = () => false));
set_global("document", "document-stub");
const history = set_global("history", {});

View File

@@ -74,6 +74,9 @@ set_global("current_msg_list", {
return 101;
},
});
set_global("recent_topics", {
is_visible: () => false,
});
function return_true() {
return true;
@@ -251,7 +254,6 @@ run_test("basic_chars", () => {
is_active,
settings_open,
info_overlay_open,
recent_topics_open: return_false,
});
test_normal_typing();
}
@@ -264,7 +266,6 @@ run_test("basic_chars", () => {
overlays.streams_open = return_false;
overlays.lightbox_open = return_false;
overlays.drafts_open = return_false;
overlays.recent_topics = return_false;
page_params.can_create_streams = true;
overlays.streams_open = return_true;
@@ -299,15 +300,6 @@ run_test("basic_chars", () => {
overlays.is_active = return_false;
assert_mapping("d", drafts, "launch");
// Test opening and closing of recent topics
overlays.is_active = return_true;
overlays.recent_topics_open = return_true;
assert_mapping("t", overlays, "close_overlay");
overlays.recent_topics_open = return_false;
test_normal_typing();
overlays.is_active = return_false;
assert_mapping("t", hashchange, "go_to_location");
// Next, test keys that only work on a selected message.
const message_view_only_keys = "@+>RjJkKsSuvi:GM";

View File

@@ -8,17 +8,26 @@ const {run_test} = require("../zjsunit/test");
const $ = require("../zjsunit/zjquery");
zrequire("message_util");
zrequire("narrow_state");
const noop = () => {};
set_global("hashchange", {
exit_overlay: noop,
set_global("top_left_corner", {
narrow_to_recent_topics: noop,
});
const overlays = set_global("overlays", {
open_overlay: (opts) => {
overlays.close_callback = opts.on_close;
},
recent_topics_open: () => true,
set_global("stream_list", {
handle_narrow_deactivated: noop,
});
set_global("compose_actions", {
cancel: noop,
});
set_global("narrow", {
narrow_title: "",
});
set_global("notifications", {
redraw_title: noop,
});
set_global("message_view_header", {
render_title_area: noop,
});
const people = zrequire("people");
@@ -74,6 +83,9 @@ const ListWidget = set_global("ListWidget", {
hard_redraw: noop,
render_item: (item) => ListWidget.modifier(item),
});
set_global("drafts", {
update_draft: noop,
});
// Custom Data
@@ -283,7 +295,7 @@ function verify_topic_data(all_topics, stream, topic, last_msg_id, participated)
assert.equal(topic_data.participated, participated);
}
let rt = zrequire("recent_topics");
let rt = reset_module("recent_topics");
function stub_out_filter_buttons() {
// TODO: We probably want more direct tests that make sure
@@ -299,7 +311,7 @@ function stub_out_filter_buttons() {
}
}
run_test("test_recent_topics_launch", () => {
run_test("test_recent_topics_show", () => {
// Note: unread count and urls are fake,
// since they are generated in external libraries
// and are not to be tested here.
@@ -325,10 +337,7 @@ run_test("test_recent_topics_launch", () => {
rt = reset_module("recent_topics");
rt.process_messages(messages);
rt.launch();
// Test if search text is selected
overlays.close_callback();
rt.show();
// incorrect topic_key
assert.equal(rt.inplace_rerender("stream_unknown:topic_unknown"), false);
@@ -360,8 +369,8 @@ run_test("test_filter_all", () => {
row_data = generate_topic_data([[1, "topic-1", 0, false, true]]);
i = row_data.length;
rt = reset_module("recent_topics");
stub_out_filter_buttons();
rt.is_visible = () => true;
rt.set_filter("all");
rt.process_messages([messages[0]]);
@@ -401,6 +410,7 @@ run_test("test_filter_unread", () => {
let i = 0;
rt = reset_module("recent_topics");
rt.is_visible = () => true;
stub_templates(() => "<recent_topics table stub>");
stub_out_filter_buttons();
@@ -466,9 +476,11 @@ run_test("test_filter_participated", () => {
let i = 0;
rt = reset_module("recent_topics");
rt.is_visible = () => true;
stub_templates(() => "<recent_topics table stub>");
stub_out_filter_buttons();
rt.process_messages(messages);
assert.equal(rt.inplace_rerender("1:topic-4"), true);
// Set muted filter
@@ -522,6 +534,7 @@ stub_templates(() => "<recent_topics table stub>");
run_test("basic assertions", () => {
rt = reset_module("recent_topics");
stub_out_filter_buttons();
rt.is_visible = () => true;
rt.set_filter("all");
rt.process_messages(messages);
let all_topics = rt.get();
@@ -601,6 +614,7 @@ run_test("basic assertions", () => {
run_test("test_reify_local_echo_message", () => {
rt = reset_module("recent_topics");
stub_out_filter_buttons();
rt.is_visible = () => true;
rt.set_filter("all");
rt.process_messages(messages);
@@ -765,6 +779,7 @@ run_test("test_topic_edit", () => {
run_test("test_search", () => {
rt = reset_module("recent_topics");
rt.is_visible = () => true;
assert.equal(rt.topic_in_search_results("t", "general", "Recent Topic"), true);
assert.equal(rt.topic_in_search_results("T", "general", "Recent Topic"), true);
assert.equal(rt.topic_in_search_results("to", "general", "Recent Topic"), true);

View File

@@ -82,7 +82,15 @@ run_test("narrowing", () => {
assert(!$(".top_left_mentions").hasClass("active-filter"));
assert(!$(".top_left_private_messages").hasClass("active-filter"));
assert(!$(".top_left_starred_messages").hasClass("active-filter"));
assert(!$(".top_left_recent_topics").hasClass("active-filter"));
assert(pm_closed);
top_left_corner.narrow_to_recent_topics();
assert(!$(".top_left_all_messages").hasClass("active-filter"));
assert(!$(".top_left_mentions").hasClass("active-filter"));
assert(!$(".top_left_private_messages").hasClass("active-filter"));
assert(!$(".top_left_starred_messages").hasClass("active-filter"));
assert($(".top_left_recent_topics").hasClass("active-filter"));
});
run_test("update_count_in_dom", () => {

View File

@@ -437,9 +437,9 @@ const message_events = zrequire("message_events");
run_test("insert_message", (override) => {
override(pm_list, "update_private_messages", noop);
override(overlays, "recent_topics_open", () => false);
const helper = test_helper();
set_global((recent_topics.is_visible = () => false));
const new_message = {
sender_id: isaac.user_id,
@@ -635,7 +635,6 @@ run_test("unread_ops", (override) => {
// Let the real code skip over details related to active overlays.
override(overlays, "is_active", () => false);
override(overlays, "recent_topics_open", () => false);
// First, test for a message list that cannot read messages. Here
// we use with_field to limit the scope of our stub function.

View File

@@ -111,6 +111,9 @@ zrequire("topic_list");
zrequire("sent_messages");
zrequire("top_left_corner");
zrequire("starred_messages");
zrequire("user_status");
zrequire("user_status_ui");
zrequire("recent_topics");
const ui_init = rewiremock.proxy(() => zrequire("ui_init"), {
"../../static/js/emojisets": {

View File

@@ -36,10 +36,19 @@ function set_hash(hash) {
}
}
function maybe_hide_recent_topics() {
if (recent_topics.is_visible()) {
recent_topics.hide();
return true;
}
return false;
}
exports.changehash = function (newhash) {
if (changing_hash) {
return;
}
maybe_hide_recent_topics();
message_viewport.stop_auto_scrolling();
set_hash(newhash);
};
@@ -53,8 +62,9 @@ exports.save_narrow = function (operators) {
};
function activate_home_tab() {
const coming_from_recent_topics = maybe_hide_recent_topics();
ui_util.change_tab_to("#message_feed_container");
narrow.deactivate();
narrow.deactivate(coming_from_recent_topics);
floating_recipient_bar.update();
search.update_button_visibility();
// We need to maybe scroll to the selected message
@@ -76,7 +86,6 @@ function is_overlay_hash(hash) {
"settings",
"organization",
"invite",
"recent_topics",
"keyboard-shortcuts",
"message-formatting",
"search-operators",
@@ -96,6 +105,7 @@ function do_hashchange_normal(from_reload) {
const hash = window.location.hash.split("/");
switch (hash[0]) {
case "#narrow": {
maybe_hide_recent_topics();
ui_util.change_tab_to("#message_feed_container");
const operators = hash_util.parse_narrow(hash);
if (operators === undefined) {
@@ -125,6 +135,9 @@ function do_hashchange_normal(from_reload) {
case "#":
activate_home_tab();
break;
case "#recent_topics":
recent_topics.show();
break;
case "#keyboard-shortcuts":
case "#message-formatting":
case "#search-operators":
@@ -133,7 +146,6 @@ function do_hashchange_normal(from_reload) {
case "#streams":
case "#organization":
case "#settings":
case "#recent_topics":
blueslip.error("overlay logic skipped for: " + hash);
break;
}
@@ -222,10 +234,6 @@ function do_hashchange_overlay(old_hash) {
return;
}
if (base === "recent_topics") {
recent_topics.launch();
return;
}
if (base === "keyboard-shortcuts") {
info_overlay.show("keyboard-shortcuts");
return;

View File

@@ -476,7 +476,11 @@ exports.process_hotkey = function (e, hotkey) {
case "vim_right":
case "tab":
case "shift_tab":
if (overlays.recent_topics_open()) {
if (
window.location.hash === "#recent_topics" &&
!popovers.any_active() &&
!overlays.is_active()
) {
return recent_topics.change_focused_element(e, event_name);
}
}
@@ -522,10 +526,6 @@ exports.process_hotkey = function (e, hotkey) {
overlays.close_overlay("drafts");
return true;
}
if (event_name === "open_recent_topics" && overlays.recent_topics_open()) {
overlays.close_overlay("recent_topics");
return true;
}
return false;
}
@@ -700,12 +700,6 @@ exports.process_hotkey = function (e, hotkey) {
// Shortcuts that don't require a message
switch (event_name) {
case "compose": // 'c': compose
compose_actions.start("stream", {trigger: "compose_hotkey"});
return true;
case "compose_private_message":
compose_actions.start("private", {trigger: "compose_hotkey"});
return true;
case "narrow_private":
return do_narrow_action((target, opts) => {
narrow.by("is", "private", opts);
@@ -738,6 +732,26 @@ exports.process_hotkey = function (e, hotkey) {
case "p_key":
narrow.narrow_to_next_pm_string();
return true;
case "open_recent_topics":
hashchange.go_to_location("#");
return true;
}
// We don't want hotkeys below this to work when recent topics is
// open. These involve compose box hotkeys and hotkeys that can only
// be done performed on a message.
if (recent_topics.is_visible()) {
return true;
}
// Compose box hotkeys
switch (event_name) {
case "compose": // 'c': compose
compose_actions.start("stream", {trigger: "compose_hotkey"});
return true;
case "compose_private_message":
compose_actions.start("private", {trigger: "compose_hotkey"});
return true;
case "open_drafts":
drafts.launch();
return true;
@@ -755,9 +769,6 @@ exports.process_hotkey = function (e, hotkey) {
case "copy_with_c":
copy_and_paste.copy_handler();
return true;
case "open_recent_topics":
hashchange.go_to_location("recent_topics");
return true;
}
if (current_msg_list.empty()) {

View File

@@ -85,6 +85,10 @@ exports.update_top_of_narrow_notices = function (msg_list) {
if (msg_list.data.fetch_status.has_found_oldest() && current_msg_list !== home_msg_list) {
const filter = narrow_state.filter();
if (filter === undefined && recent_topics.is_visible()) {
// user moved away from the narrow / filter to recent topics.
return;
}
// Potentially display the notice that lets users know
// that not all messages were searched. One could
// imagine including `filter.is_search()` in these

View File

@@ -15,6 +15,12 @@ function get_formatted_sub_count(sub_count) {
function make_message_view_header(filter) {
const message_view_header = {};
if (recent_topics.is_visible()) {
return {
title: i18n.t("Recent topics (beta)"),
icon: "clock-o",
};
}
if (filter === undefined) {
return {
title: i18n.t("All messages"),

View File

@@ -768,7 +768,7 @@ function handle_post_narrow_deactivate_processes() {
message_scroll.update_top_of_narrow_notices(home_msg_list);
}
exports.deactivate = function () {
exports.deactivate = function (coming_from_recent_topics = false) {
// NOTE: Never call this function independently,
// always use hashchange.go_to_location("") to
// activate All message narrow.
@@ -785,7 +785,9 @@ exports.deactivate = function () {
home_msg_list in it.
*/
search.clear_search_form();
if (narrow_state.filter() === undefined) {
// Both All messages and Recent Topics have `undefined` filter.
// Return if already in the All message narrow.
if (narrow_state.filter() === undefined && !coming_from_recent_topics) {
return;
}
unnarrow_times = {start_time: new Date()};

View File

@@ -17,6 +17,8 @@ exports.active = function () {
};
exports.filter = function () {
// Both, `All messages` and
// `Recent topics` have `current_filter=undefined`
return current_filter;
};

View File

@@ -38,10 +38,6 @@ exports.drafts_open = function () {
return open_overlay_name === "drafts";
};
exports.recent_topics_open = function () {
return open_overlay_name === "recent_topics";
};
// To address bugs where mouse might apply to the streams/settings
// overlays underneath an open modal within those settings UI, we add
// this inline style to '.overlay.show', overriding the

View File

@@ -280,7 +280,7 @@ exports.filters_should_hide_topic = function (topic_data) {
};
exports.inplace_rerender = function (topic_key) {
if (!overlays.recent_topics_open()) {
if (!exports.is_visible()) {
return false;
}
if (!topics.has(topic_key)) {
@@ -392,7 +392,7 @@ function topic_sort(a, b) {
}
exports.complete_rerender = function () {
if (!overlays.recent_topics_open()) {
if (!exports.is_visible()) {
return;
}
// Prepare header
@@ -406,7 +406,7 @@ exports.complete_rerender = function () {
show_selected_filters();
// Show topics list
const container = $(".recent_topics_table table tbody");
const container = $("#recent_topics_table table tbody");
container.empty();
const mapped_topic_values = Array.from(exports.get().values()).map((value) => value);
@@ -433,14 +433,33 @@ exports.complete_rerender = function () {
revive_current_focus();
};
exports.launch = function () {
overlays.open_overlay({
name: "recent_topics",
overlay: $("#recent_topics_overlay"),
on_close() {
hashchange.exit_overlay();
},
});
exports.is_visible = function () {
return $("#recent_topics_view").is(":visible");
};
exports.show = function () {
// Hide selected elements in the left sidebar.
top_left_corner.narrow_to_recent_topics();
stream_list.handle_narrow_deactivated();
// Hide "middle-column" which has html for rendering
// a messages narrow. We hide it and show recent topics.
$("#message_feed_container").hide();
$("#recent_topics_view").show();
// Save text in compose box if open.
drafts.update_draft();
// Close the compose box, this removes
// any arbitrary bug for compose box in recent topics.
// This is required since, Recent Topics is the only view
// with no compose box.
compose_actions.cancel();
narrow.narrow_title = "Recent topics (beta)";
narrow_state.set_current_filter(undefined);
notifications.redraw_title();
message_view_header.render_title_area();
exports.complete_rerender();
};
@@ -448,6 +467,30 @@ function filter_buttons() {
return $("#recent_filters_group").children();
}
exports.hide = function () {
$("#message_feed_container").show();
$("#recent_topics_view").hide();
// On firefox (and flaky on other browsers), focus
// remains on search box even after it is hidden. We
// forcefully blur it so that focus returns to the visible
// focused element.
$("#recent_topics_search").blur();
// This solves a bug with message_view_header
// being broken sometimes when we narrow
// to a filter and back to recent topics
// before it completely re-rerenders.
message_view_header.render_title_area();
// Fixes misaligned message_view and hidden
// floating_recipient_bar.
panels.resize_app();
// This makes sure user lands on the selected message
// and not always at the top of the narrow.
navigate.plan_scroll_to_selected();
};
exports.change_focused_element = function (e, input_key) {
// Called from hotkeys.js; like all logic in that module,
// returning true will cause the caller to do

View File

@@ -34,15 +34,16 @@ exports.update_dom_with_unread_counts = function (counts) {
unread_ui.animate_mention_changes(mentioned_li, counts.mentioned_message_count);
};
function deselect_top_left_corner_items() {
function remove(elem) {
elem.removeClass("active-filter active-sub-filter");
}
function remove(elem) {
elem.removeClass("active-filter active-sub-filter");
}
function deselect_top_left_corner_items() {
remove($(".top_left_all_messages"));
remove($(".top_left_private_messages"));
remove($(".top_left_starred_messages"));
remove($(".top_left_mentions"));
remove($(".top_left_recent_topics"));
}
function should_expand_pm_list(filter) {
@@ -109,4 +110,13 @@ exports.handle_narrow_deactivated = function () {
filter_li.addClass("active-filter");
};
exports.narrow_to_recent_topics = function () {
remove($(".top_left_all_messages"));
remove($(".top_left_private_messages"));
remove($(".top_left_starred_messages"));
remove($(".top_left_mentions"));
$(".top_left_recent_topics").addClass("active-filter");
pm_list.close();
};
window.top_left_corner = exports;

View File

@@ -435,6 +435,10 @@ on a dark background, and don't change the dark labels dark either. */
}
}
.recent_topics_container {
background-color: hsl(212, 28%, 18%) !important;
}
#recent_topics_table tr {
background-color: hsl(212, 28%, 18%);
@@ -444,18 +448,18 @@ on a dark background, and don't change the dark labels dark either. */
}
#recent_topics_table .unread_topic {
background-color: hsl(211, 28%, 16%);
background-color: hsla(212, 30%, 22%, 0.6);
}
.btn-recent-selected,
.recent_topics_table thead th {
#recent_topics_table thead th {
background-color: hsl(0, 0%, 0%) !important;
&:hover {
background-color: hsl(211, 29%, 14%) !important;
}
}
.recent_topics_table td a {
#recent_topics_table td a {
color: hsl(206, 89%, 74%);
text-decoration: none;
&:hover {
@@ -463,6 +467,10 @@ on a dark background, and don't change the dark labels dark either. */
}
}
#recent_topics_table {
border: 1px solid hsla(0, 0%, 0%, 0.6);
}
thead,
.drafts-container .drafts-header,
.recent_topics_container .recent_topics_header,

View File

@@ -1,59 +1,21 @@
.recent_topics_container {
position: relative;
height: 95%;
width: 70%;
max-width: 1200px;
max-height: 1000px;
padding: 0;
border-radius: 4px;
background-color: hsl(0, 0%, 100%);
overflow: hidden;
display: flex;
flex-direction: column;
max-height: 100vh;
@media (width < $md_min) {
height: 95%;
max-width: none;
width: 90%;
}
.recent_topics_header {
padding-top: 4px;
padding-bottom: 8px;
text-align: center;
border-bottom: 1px solid hsl(0, 0%, 87%);
h1 {
margin: 0;
font-size: 1.1em;
text-transform: uppercase;
}
.exit {
font-weight: 400;
position: absolute;
top: 10px;
right: 10px;
color: hsl(0, 0%, 67%);
cursor: pointer;
.exit-sign {
position: relative;
top: 3px;
margin-left: 3px;
font-size: 1.5rem;
font-weight: 600;
cursor: pointer;
}
}
}
.recent_topics_table {
margin: 0;
padding: 15px;
#recent_topics_table {
max-width: 100%;
padding-top: 12px;
overflow: hidden !important;
display: flex;
flex-direction: column;
border: 1px solid hsl(0, 0%, 88%);
border-radius: 10px 10px 0 0;
margin-top: 50px;
td {
vertical-align: middle;
@@ -100,7 +62,6 @@
.table_fix_head {
padding: 10px;
padding-top: 0 !important;
overflow-y: auto;
max-height: 89%;
}
@@ -109,6 +70,11 @@
border-collapse: separate;
}
.table_fix_head {
max-height: 80vh;
padding-top: 0px !important;
}
#recent_topics_filter_buttons {
margin: 0 10px 0 10px;
display: flex;
@@ -131,6 +97,10 @@
padding-top: 6px !important;
}
#recent_topics_search_clear {
margin-top: -10px !important;
}
.btn-recent-filters {
border-radius: 40px;
margin: 0 5px 10px 0;
@@ -222,7 +192,7 @@
}
tr {
background-color: hsl(0, 0%, 100%);
background-color: hsl(100, 11%, 96%);
&:hover {
background-color: hsl(210, 100%, 97%);
@@ -245,7 +215,7 @@
border-bottom: 1px solid hsla(0, 0%, 0%, 0.2) !important;
position: sticky;
top: 0;
z-index: 1000;
z-index: 1;
&.active::after,
&[data-sort]:hover::after {
@@ -299,3 +269,8 @@
}
}
}
#recent_topics_view {
display: none;
position: relative;
}

View File

@@ -45,7 +45,6 @@
{% include "zerver/app/lightbox_overlay.html" %}
{% include "zerver/app/subscriptions.html" %}
{% include "zerver/app/drafts.html" %}
{% include "zerver/app/recent_topics.html" %}
<div id="settings_overlay_container" class="overlay" data-overlay="settings" aria-hidden="true">
{% include "zerver/app/settings_overlay.html" %}
</div>
@@ -94,6 +93,11 @@
</div>
<div class="column-middle">
<div class="column-middle-inner tab-content">
<div id="recent_topics_view">
<div class="recent_topics_container">
<div id="recent_topics_table"></div>
</div>
</div>
<div class="tab-pane active" id="message_feed_container">
<div class="fixed-app" id="floating_recipient_bar">
<div class="app-main recipient_bar_content">

View File

@@ -1,17 +0,0 @@
<div id="recent_topics_overlay" class="overlay new-style" data-overlay="recent_topics">
<div class="flex overlay-content">
<div class="recent_topics_container modal-bg">
<div class="recent_topics_header">
<h1>{% trans %}Recent topics (beta){% endtrans %}</h1>
<div class="exit">
<span class="exit-sign">&times;</span>
</div>
<div class="recent-hotkey-reminder">
{% trans %}Pro tip: You can use 't' to view recent topics.{% endtrans %}
</div>
</div>
<div class="recent_topics_table" id="recent_topics_table">
</div>
</div>
</div>
</div>