2021-03-11 05:43:45 +01:00
|
|
|
import $ from "jquery";
|
|
|
|
|
2021-03-30 06:23:09 +02:00
|
|
|
import {all_messages_data} from "./all_messages_data";
|
2021-03-16 23:38:59 +01:00
|
|
|
import * as blueslip from "./blueslip";
|
2021-02-28 21:31:57 +01:00
|
|
|
import * as channel from "./channel";
|
|
|
|
import * as compose from "./compose";
|
2021-02-28 21:32:22 +01:00
|
|
|
import * as compose_actions from "./compose_actions";
|
2021-02-28 21:31:57 +01:00
|
|
|
import * as compose_fade from "./compose_fade";
|
|
|
|
import * as compose_state from "./compose_state";
|
|
|
|
import * as condense from "./condense";
|
|
|
|
import {Filter} from "./filter";
|
|
|
|
import * as hashchange from "./hashchange";
|
|
|
|
import * as message_edit from "./message_edit";
|
|
|
|
import * as message_fetch from "./message_fetch";
|
|
|
|
import * as message_list from "./message_list";
|
|
|
|
import {MessageListData} from "./message_list_data";
|
2021-03-30 02:21:21 +02:00
|
|
|
import * as message_lists from "./message_lists";
|
2021-02-28 21:31:57 +01:00
|
|
|
import * as message_scroll from "./message_scroll";
|
|
|
|
import * as message_store from "./message_store";
|
|
|
|
import * as message_view_header from "./message_view_header";
|
2021-03-26 15:21:47 +01:00
|
|
|
import * as narrow_banner from "./narrow_banner";
|
2021-02-28 21:31:57 +01:00
|
|
|
import * as narrow_state from "./narrow_state";
|
|
|
|
import * as notifications from "./notifications";
|
2021-03-25 22:35:45 +01:00
|
|
|
import {page_params} from "./page_params";
|
2021-02-28 21:31:57 +01:00
|
|
|
import * as people from "./people";
|
|
|
|
import * as recent_topics from "./recent_topics";
|
|
|
|
import * as resize from "./resize";
|
|
|
|
import * as search from "./search";
|
|
|
|
import * as search_pill from "./search_pill";
|
|
|
|
import * as search_pill_widget from "./search_pill_widget";
|
|
|
|
import * as stream_data from "./stream_data";
|
|
|
|
import * as stream_list from "./stream_list";
|
|
|
|
import * as stream_topic_history from "./stream_topic_history";
|
|
|
|
import * as top_left_corner from "./top_left_corner";
|
|
|
|
import * as topic_generator from "./topic_generator";
|
|
|
|
import * as typing_events from "./typing_events";
|
|
|
|
import * as ui_util from "./ui_util";
|
|
|
|
import * as unread from "./unread";
|
|
|
|
import * as unread_ops from "./unread_ops";
|
|
|
|
import * as util from "./util";
|
|
|
|
import * as widgetize from "./widgetize";
|
2020-07-24 06:02:07 +02:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
let unnarrow_times;
|
2012-10-24 00:29:06 +02:00
|
|
|
|
2020-01-29 20:31:04 +01:00
|
|
|
const LARGER_THAN_MAX_MESSAGE_ID = 10000000000000000;
|
|
|
|
|
2013-12-06 00:03:08 +01:00
|
|
|
function report_narrow_time(initial_core_time, initial_free_time, network_time) {
|
2013-12-18 19:55:18 +01:00
|
|
|
channel.post({
|
2020-07-15 01:29:15 +02:00
|
|
|
url: "/json/report/narrow_times",
|
2020-07-15 00:34:28 +02:00
|
|
|
data: {
|
|
|
|
initial_core: initial_core_time.toString(),
|
|
|
|
initial_free: initial_free_time.toString(),
|
|
|
|
network: network_time.toString(),
|
|
|
|
},
|
2013-12-06 00:03:08 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function maybe_report_narrow_time(msg_list) {
|
2020-07-15 00:34:28 +02:00
|
|
|
if (
|
|
|
|
msg_list.network_time === undefined ||
|
|
|
|
msg_list.initial_core_time === undefined ||
|
|
|
|
msg_list.initial_free_time === undefined
|
|
|
|
) {
|
2013-12-06 00:03:08 +01:00
|
|
|
return;
|
|
|
|
}
|
2020-07-15 00:34:28 +02:00
|
|
|
report_narrow_time(
|
|
|
|
msg_list.initial_core_time - msg_list.start_time,
|
|
|
|
msg_list.initial_free_time - msg_list.start_time,
|
|
|
|
msg_list.network_time - msg_list.start_time,
|
|
|
|
);
|
2013-12-06 00:03:08 +01:00
|
|
|
}
|
|
|
|
|
2014-02-13 18:49:44 +01:00
|
|
|
function report_unnarrow_time() {
|
2020-07-15 00:34:28 +02:00
|
|
|
if (
|
|
|
|
unnarrow_times === undefined ||
|
2014-02-13 18:49:44 +01:00
|
|
|
unnarrow_times.start_time === undefined ||
|
|
|
|
unnarrow_times.initial_core_time === undefined ||
|
2020-07-15 00:34:28 +02:00
|
|
|
unnarrow_times.initial_free_time === undefined
|
|
|
|
) {
|
2014-02-13 18:49:44 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const initial_core_time = unnarrow_times.initial_core_time - unnarrow_times.start_time;
|
|
|
|
const initial_free_time = unnarrow_times.initial_free_time - unnarrow_times.start_time;
|
2014-02-13 18:49:44 +01:00
|
|
|
|
|
|
|
channel.post({
|
2020-07-15 01:29:15 +02:00
|
|
|
url: "/json/report/unnarrow_times",
|
2020-07-15 00:34:28 +02:00
|
|
|
data: {
|
|
|
|
initial_core: initial_core_time.toString(),
|
|
|
|
initial_free: initial_free_time.toString(),
|
|
|
|
},
|
2014-02-13 18:49:44 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
unnarrow_times = {};
|
|
|
|
}
|
|
|
|
|
2021-02-28 21:31:57 +01:00
|
|
|
export function save_pre_narrow_offset_for_reload() {
|
2021-03-30 02:21:21 +02:00
|
|
|
if (message_lists.current.selected_id() !== -1) {
|
|
|
|
if (message_lists.current.selected_row().length === 0) {
|
2018-05-03 14:55:01 +02:00
|
|
|
blueslip.debug("narrow.activate missing selected row", {
|
2021-03-30 02:21:21 +02:00
|
|
|
selected_id: message_lists.current.selected_id(),
|
|
|
|
selected_idx: message_lists.current.selected_idx(),
|
|
|
|
selected_idx_exact: message_lists.current
|
2020-07-15 00:34:28 +02:00
|
|
|
.all_messages()
|
2021-03-30 02:21:21 +02:00
|
|
|
.indexOf(message_lists.current.get(message_lists.current.selected_id())),
|
|
|
|
render_start: message_lists.current.view._render_win_start,
|
|
|
|
render_end: message_lists.current.view._render_win_end,
|
2018-05-03 14:55:01 +02:00
|
|
|
});
|
|
|
|
}
|
2021-03-30 02:21:21 +02:00
|
|
|
message_lists.current.pre_narrow_offset = message_lists.current.selected_row().offset().top;
|
2018-05-03 14:55:01 +02:00
|
|
|
}
|
2021-02-28 21:31:57 +01:00
|
|
|
}
|
2018-05-03 14:55:01 +02:00
|
|
|
|
2021-02-28 21:31:57 +01:00
|
|
|
export let narrow_title = "home";
|
2021-02-28 20:21:01 +01:00
|
|
|
|
2021-02-28 21:31:57 +01:00
|
|
|
export function set_narrow_title(title) {
|
|
|
|
narrow_title = title;
|
2021-02-28 20:21:01 +01:00
|
|
|
notifications.redraw_title();
|
2021-02-28 21:31:57 +01:00
|
|
|
}
|
2021-02-28 20:21:01 +01:00
|
|
|
|
2019-12-11 03:13:17 +01:00
|
|
|
function update_narrow_title(filter) {
|
2015-12-10 20:37:01 +01:00
|
|
|
// Take the most detailed part of the narrow to use as the title.
|
|
|
|
// If the operator is something other than "stream", "topic", or
|
|
|
|
// "is", we shouldn't update the narrow title
|
|
|
|
if (filter.has_operator("stream")) {
|
|
|
|
if (filter.has_operator("topic")) {
|
2021-02-28 21:31:57 +01:00
|
|
|
set_narrow_title(filter.operands("topic")[0]);
|
2015-12-10 20:37:01 +01:00
|
|
|
} else {
|
2021-02-28 21:31:57 +01:00
|
|
|
set_narrow_title(filter.operands("stream")[0]);
|
2015-12-10 20:37:01 +01:00
|
|
|
}
|
|
|
|
} else if (filter.has_operator("is")) {
|
2021-02-28 20:21:01 +01:00
|
|
|
const title = filter.operands("is")[0];
|
2021-02-28 21:31:57 +01:00
|
|
|
set_narrow_title(title.charAt(0).toUpperCase() + title.slice(1) + " messages");
|
2018-03-03 17:12:38 +01:00
|
|
|
} else if (filter.has_operator("pm-with") || filter.has_operator("group-pm-with")) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const emails = filter.public_operators()[0].operand;
|
|
|
|
const user_ids = people.emails_strings_to_user_ids_string(emails);
|
2019-06-28 02:17:43 +02:00
|
|
|
if (user_ids !== undefined) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const names = people.get_recipients(user_ids);
|
2019-06-28 02:17:43 +02:00
|
|
|
if (filter.has_operator("pm-with")) {
|
2021-02-28 21:31:57 +01:00
|
|
|
set_narrow_title(names);
|
2019-06-28 02:17:43 +02:00
|
|
|
} else {
|
2021-02-28 21:31:57 +01:00
|
|
|
set_narrow_title(names + " and others");
|
2019-06-28 02:17:43 +02:00
|
|
|
}
|
2018-03-03 17:12:38 +01:00
|
|
|
} else {
|
2020-07-15 01:29:15 +02:00
|
|
|
if (emails.includes(",")) {
|
2021-02-28 21:31:57 +01:00
|
|
|
set_narrow_title("Invalid users");
|
2019-06-28 02:17:43 +02:00
|
|
|
} else {
|
2021-02-28 21:31:57 +01:00
|
|
|
set_narrow_title("Invalid user");
|
2019-06-28 02:17:43 +02:00
|
|
|
}
|
2018-03-03 17:12:38 +01:00
|
|
|
}
|
2015-12-10 20:37:01 +01:00
|
|
|
}
|
2019-12-11 03:13:17 +01:00
|
|
|
}
|
|
|
|
|
2021-02-28 21:31:57 +01:00
|
|
|
export function activate(raw_operators, opts) {
|
2020-05-19 19:45:36 +02:00
|
|
|
/* Main entrypoint for switching to a new view / message list.
|
|
|
|
Note that for historical reasons related to the current
|
2021-03-30 02:21:21 +02:00
|
|
|
client-side caching structure, the "All messages"/message_lists.home
|
2020-05-19 19:45:36 +02:00
|
|
|
view is reached via `narrow.deactivate()`.
|
|
|
|
|
|
|
|
The name is based on "narrowing to a subset of the user's
|
|
|
|
messages.". Supported parameters:
|
|
|
|
|
|
|
|
raw_operators: Narrowing/search operators; used to construct
|
|
|
|
a Filter object that decides which messages belong in the
|
2021-03-30 02:21:21 +02:00
|
|
|
view. Required (See the above note on how `message_lists.home` works)
|
2020-05-19 19:45:36 +02:00
|
|
|
|
|
|
|
All other options are encoded via the `opts` dictionary:
|
|
|
|
|
|
|
|
* trigger: Optional parameter used mainly for logging and some
|
|
|
|
custom UI behavior for certain buttons. Generally aim to
|
|
|
|
have this be unique for each UI widget that can trigger narrowing.
|
|
|
|
|
|
|
|
* change_hash: Whether this narrow should change the URL
|
|
|
|
fragment ("hash") in the URL bar. Should be true unless the
|
|
|
|
URL is already correct (E.g. because the hashchange logic
|
|
|
|
itself is triggering the change of view).
|
|
|
|
|
|
|
|
* then_select_id: If the caller wants us to do the narrow
|
|
|
|
centered on a specific message ID ("anchor" in the API
|
2020-05-21 21:41:55 +02:00
|
|
|
parlance), specify that here. Useful e.g. when the user
|
2020-05-19 19:45:36 +02:00
|
|
|
clicks on a specific message; implied by a `near:` operator.
|
|
|
|
|
|
|
|
* then_select_offset: Offset from the top of the page in pixels
|
|
|
|
at which to place the then_select_id message following
|
|
|
|
rendering. Important to avoid what would otherwise feel like
|
|
|
|
visual glitches after clicking on a specific message's headig
|
|
|
|
or rerendering due to server-side changes.
|
|
|
|
*/
|
|
|
|
|
2020-09-24 19:39:58 +02:00
|
|
|
if (recent_topics.is_visible()) {
|
|
|
|
recent_topics.hide();
|
|
|
|
}
|
|
|
|
|
2019-12-11 03:13:17 +01:00
|
|
|
const start_time = new Date();
|
|
|
|
const was_narrowed_already = narrow_state.active();
|
|
|
|
// most users aren't going to send a bunch of a out-of-narrow messages
|
|
|
|
// and expect to visit a list of narrows, so let's get these out of the way.
|
|
|
|
notifications.clear_compose_notifications();
|
|
|
|
|
|
|
|
// Open tooltips are only interesting for current narrow,
|
|
|
|
// so hide them when activating a new one.
|
|
|
|
$(".tooltip").hide();
|
|
|
|
|
|
|
|
if (raw_operators.length === 0) {
|
2021-02-28 21:31:57 +01:00
|
|
|
deactivate();
|
2020-09-24 07:50:36 +02:00
|
|
|
return;
|
2019-12-11 03:13:17 +01:00
|
|
|
}
|
|
|
|
const filter = new Filter(raw_operators);
|
|
|
|
const operators = filter.operators();
|
|
|
|
|
|
|
|
update_narrow_title(filter);
|
2020-06-14 12:33:12 +02:00
|
|
|
message_scroll.hide_top_of_narrow_notices();
|
2020-06-15 11:53:00 +02:00
|
|
|
message_scroll.hide_indicators();
|
2019-12-11 03:07:51 +01:00
|
|
|
|
2020-07-15 00:34:28 +02:00
|
|
|
blueslip.debug("Narrowed", {
|
|
|
|
operators: operators.map((e) => e.operator),
|
|
|
|
trigger: opts ? opts.trigger : undefined,
|
2021-03-30 02:21:21 +02:00
|
|
|
previous_id: message_lists.current.selected_id(),
|
2020-07-15 00:34:28 +02:00
|
|
|
});
|
2013-07-19 21:03:46 +02:00
|
|
|
|
2020-02-09 04:21:30 +01:00
|
|
|
opts = {
|
2018-04-23 06:02:11 +02:00
|
|
|
then_select_id: -1,
|
2018-04-23 06:17:56 +02:00
|
|
|
then_select_offset: undefined,
|
2013-05-21 19:34:15 +02:00
|
|
|
change_hash: true,
|
2020-07-15 01:29:15 +02:00
|
|
|
trigger: "unknown",
|
2020-02-09 04:21:30 +01:00
|
|
|
...opts,
|
|
|
|
};
|
2017-08-23 21:43:11 +02:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const id_info = {
|
2018-06-01 01:07:45 +02:00
|
|
|
target_id: undefined,
|
|
|
|
local_select_id: undefined,
|
|
|
|
final_select_id: undefined,
|
|
|
|
};
|
|
|
|
|
2017-08-23 21:43:11 +02:00
|
|
|
// These two narrowing operators specify what message should be
|
|
|
|
// selected and should be the center of the narrow.
|
2013-07-31 20:33:38 +02:00
|
|
|
if (filter.has_operator("near")) {
|
2020-10-07 09:17:30 +02:00
|
|
|
id_info.target_id = Number.parseInt(filter.operands("near")[0], 10);
|
2013-07-31 20:33:38 +02:00
|
|
|
}
|
2013-07-31 20:54:51 +02:00
|
|
|
if (filter.has_operator("id")) {
|
2020-10-07 09:17:30 +02:00
|
|
|
id_info.target_id = Number.parseInt(filter.operands("id")[0], 10);
|
2013-07-31 20:54:51 +02:00
|
|
|
}
|
2012-12-07 20:52:39 +01:00
|
|
|
|
2018-06-01 07:50:17 +02:00
|
|
|
if (opts.then_select_id > 0) {
|
|
|
|
// We override target_id in this case, since the user could be
|
|
|
|
// having a near: narrow auto-reloaded.
|
|
|
|
id_info.target_id = opts.then_select_id;
|
|
|
|
if (opts.then_select_offset === undefined) {
|
2021-03-30 02:21:21 +02:00
|
|
|
const row = message_lists.current.get_row(opts.then_select_id);
|
2018-06-01 07:50:17 +02:00
|
|
|
if (row.length > 0) {
|
|
|
|
opts.then_select_offset = row.offset().top;
|
|
|
|
}
|
2018-05-13 14:31:44 +02:00
|
|
|
}
|
2018-06-01 07:50:17 +02:00
|
|
|
}
|
2018-05-13 14:31:44 +02:00
|
|
|
|
2013-11-26 19:06:21 +01:00
|
|
|
if (!was_narrowed_already) {
|
2018-08-04 08:42:57 +02:00
|
|
|
unread.set_messages_read_in_narrow(false);
|
2013-11-26 19:06:21 +01:00
|
|
|
}
|
|
|
|
|
2018-05-03 15:05:57 +02:00
|
|
|
// IMPORTANT! At this point we are heavily committed to
|
|
|
|
// populating the new narrow, so we update our narrow_state.
|
|
|
|
// From here on down, any calls to the narrow_state API will
|
|
|
|
// reflect the upcoming narrow.
|
2017-04-25 15:25:31 +02:00
|
|
|
narrow_state.set_current_filter(filter);
|
2018-05-03 15:05:57 +02:00
|
|
|
|
2021-01-25 06:23:16 +01:00
|
|
|
const excludes_muted_topics = narrow_state.excludes_muted_topics();
|
2012-12-12 19:00:50 +01:00
|
|
|
|
2013-07-03 21:39:41 +02:00
|
|
|
// Save how far from the pointer the top of the message list was.
|
2021-02-28 21:31:57 +01:00
|
|
|
save_pre_narrow_offset_for_reload();
|
2013-07-25 22:08:16 +02:00
|
|
|
|
2020-07-16 23:29:01 +02:00
|
|
|
let msg_data = new MessageListData({
|
2018-07-07 15:55:39 +02:00
|
|
|
filter: narrow_state.filter(),
|
2021-01-25 06:23:16 +01:00
|
|
|
excludes_muted_topics,
|
2018-05-14 17:47:14 +02:00
|
|
|
});
|
|
|
|
|
2018-05-14 19:57:46 +02:00
|
|
|
// Populate the message list if we can apply our filter locally (i.e.
|
|
|
|
// with no backend help) and we have the message we want to select.
|
2018-06-01 01:07:45 +02:00
|
|
|
// Also update id_info accordingly.
|
2018-05-31 17:21:00 +02:00
|
|
|
// original back.
|
2021-02-28 21:31:57 +01:00
|
|
|
maybe_add_local_messages({
|
2020-07-20 22:18:43 +02:00
|
|
|
id_info,
|
|
|
|
msg_data,
|
2018-05-31 17:21:00 +02:00
|
|
|
});
|
2018-05-14 19:57:46 +02:00
|
|
|
|
2018-06-01 01:07:45 +02:00
|
|
|
if (!id_info.local_select_id) {
|
|
|
|
// If we're not actually read to select an ID, we need to
|
|
|
|
// trash the `MessageListData` object that we just constructed
|
|
|
|
// and pass an empty one to MessageList, because the block of
|
|
|
|
// messages in the MessageListData built inside
|
|
|
|
// maybe_add_local_messages is likely not be contiguous with
|
|
|
|
// the block we're about to request from the server instead.
|
|
|
|
msg_data = new MessageListData({
|
2018-07-07 15:55:39 +02:00
|
|
|
filter: narrow_state.filter(),
|
2021-01-25 06:23:16 +01:00
|
|
|
excludes_muted_topics,
|
2018-06-01 01:07:45 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const msg_list = new message_list.MessageList({
|
2018-05-14 17:47:14 +02:00
|
|
|
data: msg_data,
|
2020-07-15 01:29:15 +02:00
|
|
|
table_name: "zfilt",
|
2018-07-07 15:55:39 +02:00
|
|
|
collapse_messages: !narrow_state.filter().is_search(),
|
2018-05-14 15:46:25 +02:00
|
|
|
});
|
2017-04-25 15:25:31 +02:00
|
|
|
|
2013-12-06 00:03:08 +01:00
|
|
|
msg_list.start_time = start_time;
|
2013-07-03 21:39:41 +02:00
|
|
|
|
2021-03-30 02:21:21 +02:00
|
|
|
// Show the new set of messages. It is important to set message_lists.current to
|
|
|
|
// the view right as it's being shown, because we rely on message_lists.current
|
2013-12-09 19:20:55 +01:00
|
|
|
// being shown for deciding when to condense messages.
|
|
|
|
$("body").addClass("narrowed_view");
|
|
|
|
$("#zfilt").addClass("focused_table");
|
|
|
|
$("#zhome").removeClass("focused_table");
|
2016-06-18 19:34:20 +02:00
|
|
|
|
2020-07-04 16:25:41 +02:00
|
|
|
ui_util.change_tab_to("#message_feed_container");
|
2018-08-04 08:30:52 +02:00
|
|
|
message_list.set_narrowed(msg_list);
|
2021-03-30 02:21:21 +02:00
|
|
|
message_lists.set_current(message_list.narrowed);
|
2013-02-12 20:01:24 +01:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
let then_select_offset;
|
2018-06-01 07:50:17 +02:00
|
|
|
if (id_info.target_id === id_info.final_select_id) {
|
|
|
|
then_select_offset = opts.then_select_offset;
|
|
|
|
}
|
2018-05-13 14:31:44 +02:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const select_immediately = id_info.local_select_id !== undefined;
|
2018-05-03 14:09:37 +02:00
|
|
|
|
2021-05-06 21:49:45 +02:00
|
|
|
{
|
2019-11-02 00:06:25 +01:00
|
|
|
let anchor;
|
2018-05-03 03:53:26 +02:00
|
|
|
|
2019-07-21 15:55:53 +02:00
|
|
|
// Either we're trying to center the narrow around a
|
|
|
|
// particular message ID (which could be max_int), or we're
|
|
|
|
// asking the server to figure out for us what the first
|
|
|
|
// unread message is, and center the narrow around that.
|
2020-01-29 20:31:04 +01:00
|
|
|
if (id_info.final_select_id === undefined) {
|
2020-01-29 03:29:15 +01:00
|
|
|
anchor = "first_unread";
|
2020-01-29 20:31:04 +01:00
|
|
|
} else if (id_info.final_select_id === -1) {
|
|
|
|
// This case should never happen in this code path; it's
|
|
|
|
// here in case we choose to extract this as an
|
|
|
|
// independent reusable function.
|
|
|
|
anchor = "oldest";
|
|
|
|
} else if (id_info.final_select_id === LARGER_THAN_MAX_MESSAGE_ID) {
|
|
|
|
anchor = "newest";
|
|
|
|
} else {
|
|
|
|
anchor = id_info.final_select_id;
|
2018-05-03 03:53:26 +02:00
|
|
|
}
|
|
|
|
|
2018-05-03 14:09:37 +02:00
|
|
|
message_fetch.load_messages_for_narrow({
|
2020-07-20 22:18:43 +02:00
|
|
|
anchor,
|
|
|
|
cont() {
|
2018-05-04 17:44:17 +02:00
|
|
|
if (!select_immediately) {
|
2021-02-28 21:31:57 +01:00
|
|
|
update_selection({
|
2020-07-20 22:18:43 +02:00
|
|
|
id_info,
|
2018-05-03 14:34:29 +02:00
|
|
|
select_offset: then_select_offset,
|
|
|
|
});
|
2018-05-03 14:09:37 +02:00
|
|
|
}
|
|
|
|
msg_list.network_time = new Date();
|
|
|
|
maybe_report_narrow_time(msg_list);
|
|
|
|
},
|
|
|
|
});
|
2021-05-06 21:49:45 +02:00
|
|
|
}
|
2013-02-20 19:35:15 +01:00
|
|
|
|
2018-05-04 17:44:17 +02:00
|
|
|
if (select_immediately) {
|
2021-02-28 21:31:57 +01:00
|
|
|
update_selection({
|
2020-07-20 22:18:43 +02:00
|
|
|
id_info,
|
2018-05-03 14:34:29 +02:00
|
|
|
select_offset: then_select_offset,
|
|
|
|
});
|
2013-04-10 17:55:02 +02:00
|
|
|
}
|
2012-10-26 16:59:38 +02:00
|
|
|
|
2013-03-19 23:45:51 +01:00
|
|
|
// Put the narrow operators in the URL fragment.
|
|
|
|
// Disabled when the URL fragment was the source
|
|
|
|
// of this narrow.
|
2013-08-01 17:47:48 +02:00
|
|
|
if (opts.change_hash) {
|
2013-03-19 23:45:51 +01:00
|
|
|
hashchange.save_narrow(operators);
|
2013-08-01 17:47:48 +02:00
|
|
|
}
|
2013-03-19 23:45:51 +01:00
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
if (page_params.search_pills_enabled && opts.trigger !== "search") {
|
2018-07-23 02:20:57 +02:00
|
|
|
search_pill_widget.widget.clear(true);
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
|
|
|
|
for (const operator of operators) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const search_string = Filter.unparse([operator]);
|
2018-07-23 02:20:57 +02:00
|
|
|
search_pill.append_search_string(search_string, search_pill_widget.widget);
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
}
|
2018-07-14 16:10:00 +02:00
|
|
|
}
|
|
|
|
|
2019-10-09 23:41:24 +02:00
|
|
|
if (filter.contains_only_private_messages()) {
|
2019-03-06 23:46:53 +01:00
|
|
|
compose.update_closed_compose_buttons_for_private();
|
2018-08-09 05:59:55 +02:00
|
|
|
} else {
|
2019-03-06 23:46:53 +01:00
|
|
|
compose.update_closed_compose_buttons_for_stream();
|
2018-08-09 05:59:55 +02:00
|
|
|
}
|
|
|
|
|
2012-12-18 23:38:55 +01:00
|
|
|
search.update_button_visibility();
|
2013-10-10 15:54:18 +02:00
|
|
|
|
2018-02-16 15:56:25 +01:00
|
|
|
compose_actions.on_narrow(opts);
|
2013-10-10 15:54:18 +02:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const current_filter = narrow_state.filter();
|
2017-08-12 16:49:10 +02:00
|
|
|
|
2017-08-12 17:26:12 +02:00
|
|
|
top_left_corner.handle_narrow_activated(current_filter);
|
2017-08-12 16:49:10 +02:00
|
|
|
stream_list.handle_narrow_activated(current_filter);
|
2018-08-06 18:09:51 +02:00
|
|
|
typing_events.render_notifications_for_narrow();
|
2020-07-08 23:44:01 +02:00
|
|
|
message_view_header.initialize();
|
2017-08-12 16:49:10 +02:00
|
|
|
|
2013-12-06 00:03:08 +01:00
|
|
|
msg_list.initial_core_time = new Date();
|
2020-07-02 01:45:54 +02:00
|
|
|
setTimeout(() => {
|
2020-04-13 18:53:07 +02:00
|
|
|
resize.resize_stream_filters_container();
|
2013-12-06 00:03:08 +01:00
|
|
|
msg_list.initial_free_time = new Date();
|
|
|
|
maybe_report_narrow_time(msg_list);
|
|
|
|
}, 0);
|
2021-02-28 21:31:57 +01:00
|
|
|
}
|
2012-10-03 20:49:58 +02:00
|
|
|
|
2018-06-01 01:07:45 +02:00
|
|
|
function min_defined(a, b) {
|
|
|
|
if (a === undefined) {
|
|
|
|
return b;
|
|
|
|
}
|
|
|
|
if (b === undefined) {
|
|
|
|
return a;
|
|
|
|
}
|
|
|
|
return a < b ? a : b;
|
|
|
|
}
|
|
|
|
|
2018-05-14 19:57:46 +02:00
|
|
|
function load_local_messages(msg_data) {
|
2018-05-04 17:44:17 +02:00
|
|
|
// This little helper loads messages into our narrow message
|
2018-05-14 19:57:46 +02:00
|
|
|
// data and returns true unless it's empty. We use this for
|
2021-03-30 06:23:09 +02:00
|
|
|
// cases when our local cache (all_messages_data) has at least
|
2018-05-04 17:44:17 +02:00
|
|
|
// one message the user will expect to see in the new narrow.
|
|
|
|
|
2021-03-30 06:23:09 +02:00
|
|
|
const in_msgs = all_messages_data.all_messages();
|
2018-05-14 19:57:46 +02:00
|
|
|
msg_data.add_messages(in_msgs);
|
|
|
|
|
|
|
|
return !msg_data.empty();
|
2018-05-04 17:44:17 +02:00
|
|
|
}
|
|
|
|
|
2021-02-28 21:31:57 +01:00
|
|
|
export function maybe_add_local_messages(opts) {
|
2019-07-21 15:55:53 +02:00
|
|
|
// This function determines whether we need to go to the server to
|
|
|
|
// fetch messages for the requested narrow, or whether we have the
|
|
|
|
// data cached locally to render the narrow correctly without
|
|
|
|
// waiting for the server. There are two high-level outcomes:
|
|
|
|
//
|
|
|
|
// 1. We're centering this narrow on the first unread message: In
|
|
|
|
// this case final_select_id is left undefined or first unread
|
|
|
|
// message id locally.
|
|
|
|
//
|
|
|
|
// 2. We're centering this narrow on the most recent matching
|
|
|
|
// message. In this case we select final_select_id to the latest
|
|
|
|
// message in the local cache (if the local cache has the latest
|
|
|
|
// messages for this narrow) or max_int (if it doesn't).
|
|
|
|
//
|
|
|
|
// In either case, this function does two very closely related
|
|
|
|
// things, both of which are somewhat optional:
|
2018-05-04 17:44:17 +02:00
|
|
|
//
|
2018-06-01 01:07:45 +02:00
|
|
|
// - update id_info with more complete values
|
2018-05-04 17:44:17 +02:00
|
|
|
// - add messages into our message list from our local cache
|
2019-11-02 00:06:25 +01:00
|
|
|
const id_info = opts.id_info;
|
|
|
|
const msg_data = opts.msg_data;
|
|
|
|
const unread_info = narrow_state.get_first_unread_info();
|
2018-06-01 01:07:45 +02:00
|
|
|
|
2019-07-21 15:55:53 +02:00
|
|
|
// If we don't have a specific message we're hoping to select
|
|
|
|
// (i.e. no `target_id`) and the narrow's filter doesn't
|
|
|
|
// allow_use_first_unread_when_narrowing, we want to just render
|
|
|
|
// the latest messages matching the filter. To ensure this, we
|
|
|
|
// set an initial value final_select_id to `max_int`.
|
|
|
|
//
|
|
|
|
// While that's a confusing naming choice (`final_select_id` is
|
|
|
|
// meant to be final in the context of the caller), this sets the
|
|
|
|
// default behavior to be fetching and then selecting the very
|
|
|
|
// latest message in this narrow.
|
|
|
|
//
|
|
|
|
// If we're able to render the narrow locally, we'll end up
|
|
|
|
// overwriting this value with the ID of the latest message in the
|
|
|
|
// narrow later in this function.
|
|
|
|
if (!id_info.target_id && !narrow_state.filter().allow_use_first_unread_when_narrowing()) {
|
|
|
|
// Note that this may be overwritten; see above comment.
|
2020-01-29 20:31:04 +01:00
|
|
|
id_info.final_select_id = LARGER_THAN_MAX_MESSAGE_ID;
|
2019-07-21 15:55:53 +02:00
|
|
|
}
|
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
if (unread_info.flavor === "cannot_compute") {
|
2019-07-21 15:55:53 +02:00
|
|
|
// Full-text search and potentially other future cases where
|
|
|
|
// we can't check which messages match on the frontend, so it
|
|
|
|
// doesn't matter what's in our cache, we must go to the server.
|
2018-06-01 01:07:45 +02:00
|
|
|
if (id_info.target_id) {
|
|
|
|
// TODO: Ideally, in this case we should be asking the
|
|
|
|
// server to give us the first unread or the target_id,
|
|
|
|
// whichever is first (i.e. basically the `found` logic
|
|
|
|
// below), but the server doesn't support that query.
|
|
|
|
id_info.final_select_id = id_info.target_id;
|
|
|
|
}
|
|
|
|
// if we can't compute a next unread id, just return without
|
|
|
|
// setting local_select_id, so that we go to the server.
|
|
|
|
return;
|
|
|
|
}
|
2018-05-04 17:44:17 +02:00
|
|
|
|
2018-07-07 15:55:39 +02:00
|
|
|
// We can now assume narrow_state.filter().can_apply_locally(),
|
2018-06-01 01:07:45 +02:00
|
|
|
// because !can_apply_locally => cannot_compute
|
|
|
|
|
2020-07-15 00:34:28 +02:00
|
|
|
if (
|
|
|
|
unread_info.flavor === "found" &&
|
|
|
|
narrow_state.filter().allow_use_first_unread_when_narrowing()
|
|
|
|
) {
|
2019-07-21 15:55:53 +02:00
|
|
|
// We have at least one unread message in this narrow, and the
|
|
|
|
// narrow is one where we use the first unread message in
|
|
|
|
// narrowing positioning decisions. So either we aim for the
|
|
|
|
// first unread message, or the target_id (if any), whichever
|
|
|
|
// is earlier. See #2091 for a detailed explanation of why we
|
|
|
|
// need to look at unread here.
|
2020-07-15 00:34:28 +02:00
|
|
|
id_info.final_select_id = min_defined(id_info.target_id, unread_info.msg_id);
|
2018-06-01 01:07:45 +02:00
|
|
|
|
|
|
|
if (!load_local_messages(msg_data)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now that we know what message ID we're going to land on, we
|
|
|
|
// can see if we can take the user there locally.
|
|
|
|
if (msg_data.get(id_info.final_select_id)) {
|
|
|
|
id_info.local_select_id = id_info.final_select_id;
|
2018-05-04 17:44:17 +02:00
|
|
|
}
|
2018-06-01 01:07:45 +02:00
|
|
|
|
|
|
|
// If we don't have the first unread message locally, we must
|
|
|
|
// go to the server to get it before we can render the narrow.
|
|
|
|
return;
|
2018-05-04 17:44:17 +02:00
|
|
|
}
|
|
|
|
|
2019-07-21 15:55:53 +02:00
|
|
|
// In all cases below here, the first unread message is irrelevant
|
|
|
|
// to our positioning decisions, either because there are no
|
|
|
|
// unread messages (unread_info.flavor === 'not_found') or because
|
|
|
|
// this is a mixed narrow where we prefer the bottom of the feed
|
|
|
|
// to the first unread message for positioning (and the narrow
|
|
|
|
// will be configured to not mark messages as read).
|
2018-06-01 01:07:45 +02:00
|
|
|
|
|
|
|
if (!id_info.target_id) {
|
|
|
|
// Without unread messages or a target ID, we're narrowing to
|
2019-07-21 15:55:53 +02:00
|
|
|
// the very latest message or first unread if matching the narrow allows.
|
2018-06-01 01:07:45 +02:00
|
|
|
|
2021-03-30 06:23:09 +02:00
|
|
|
if (!all_messages_data.fetch_status.has_found_newest()) {
|
|
|
|
// If all_messages_data is not caught up, then we cannot
|
2018-06-01 01:07:45 +02:00
|
|
|
// populate the latest messages for the target narrow
|
|
|
|
// correctly from there, so we must go to the server.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!load_local_messages(msg_data)) {
|
|
|
|
return;
|
2018-05-04 17:44:17 +02:00
|
|
|
}
|
2021-03-30 06:23:09 +02:00
|
|
|
// Otherwise, we have matching messages, and all_messages_data
|
2018-06-01 01:07:45 +02:00
|
|
|
// is caught up, so the last message in our now-populated
|
|
|
|
// msg_data object must be the last message matching the
|
|
|
|
// narrow the server could give us, so we can render locally.
|
2019-07-21 15:55:53 +02:00
|
|
|
// and use local latest message id instead of max_int if set earlier.
|
2019-11-02 00:06:25 +01:00
|
|
|
const last_msg = msg_data.last();
|
2018-06-01 01:07:45 +02:00
|
|
|
id_info.final_select_id = last_msg.id;
|
|
|
|
id_info.local_select_id = id_info.final_select_id;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We have a target_id and no unread messages complicating things,
|
|
|
|
// so we definitely want to land on the target_id message.
|
|
|
|
id_info.final_select_id = id_info.target_id;
|
2018-05-05 02:25:46 +02:00
|
|
|
|
narrow: Use search reading behavior in all searches.
In 452e226ea25c1315edb1f31be406475d82c56a88 and
648a60baf63f9afade83148bd9ae1fc480510178, we changed how `search:`
narrows work to:
(1) Never mark messages as read inside searches (search:)
(2) Take you to the bottom, not the first unread, if a `near:` or
similar wasn't specified.
This is far better behavior for these use cases, because in these
narrows, you can't actually see all the context around the target
messages, so marking them as read is counterproductive. This is
especially important in `has:mention` where you goal is likely
specifically to keep track of which threads mentioning you haven't
been read. But in many other narrows, the current behavior is
effectively (1) setting the read bit on random messages and (2) if the
search term matches many messages in a muted stream with 1000s of
unreads, making it hard or impossible to find recent search matches.
The new behavior is that any narrow that is structurally a search of
history (including everything that that isn't a stream, topic,
pm-with, "all messages" or "private messages") gets that new behavior
of being unable to mark messages as read and narrows taking you to the
latest matching messages.
A few corner cases of interest:
* `is:private` is keeping the old behavior, because users on
chat.zulip.org found it confusing for `is:private` to not mark
messages as read when one could see them all. Possibly a more
complex answer is required here.
* `near:` narrows are getting the new behavior, even if it's a stream:
+ topic: narrow. This is debatable, but is probably better than
what was happening before.
Modified significantly by tabbott for cleanliness of implementation,
this commit message, and unit tests.
Fixes #9893. Follow-up to #12556.
2019-07-22 01:55:04 +02:00
|
|
|
// TODO: We could improve on this next condition by considering
|
2021-03-30 06:23:09 +02:00
|
|
|
// cases where
|
|
|
|
// `all_messages_data.fetch_status.has_found_oldest()`; which
|
|
|
|
// would come up with e.g. `near: 0` in a small organization.
|
narrow: Use search reading behavior in all searches.
In 452e226ea25c1315edb1f31be406475d82c56a88 and
648a60baf63f9afade83148bd9ae1fc480510178, we changed how `search:`
narrows work to:
(1) Never mark messages as read inside searches (search:)
(2) Take you to the bottom, not the first unread, if a `near:` or
similar wasn't specified.
This is far better behavior for these use cases, because in these
narrows, you can't actually see all the context around the target
messages, so marking them as read is counterproductive. This is
especially important in `has:mention` where you goal is likely
specifically to keep track of which threads mentioning you haven't
been read. But in many other narrows, the current behavior is
effectively (1) setting the read bit on random messages and (2) if the
search term matches many messages in a muted stream with 1000s of
unreads, making it hard or impossible to find recent search matches.
The new behavior is that any narrow that is structurally a search of
history (including everything that that isn't a stream, topic,
pm-with, "all messages" or "private messages") gets that new behavior
of being unable to mark messages as read and narrows taking you to the
latest matching messages.
A few corner cases of interest:
* `is:private` is keeping the old behavior, because users on
chat.zulip.org found it confusing for `is:private` to not mark
messages as read when one could see them all. Possibly a more
complex answer is required here.
* `near:` narrows are getting the new behavior, even if it's a stream:
+ topic: narrow. This is debatable, but is probably better than
what was happening before.
Modified significantly by tabbott for cleanliness of implementation,
this commit message, and unit tests.
Fixes #9893. Follow-up to #12556.
2019-07-22 01:55:04 +02:00
|
|
|
//
|
|
|
|
// And similarly for `near: max_int` with has_found_newest.
|
2020-07-15 00:34:28 +02:00
|
|
|
if (
|
2021-03-30 06:23:09 +02:00
|
|
|
all_messages_data.empty() ||
|
|
|
|
id_info.target_id < all_messages_data.first().id ||
|
|
|
|
id_info.target_id > all_messages_data.last().id
|
2020-07-15 00:34:28 +02:00
|
|
|
) {
|
2018-06-01 01:07:45 +02:00
|
|
|
// If the target message is outside the range that we had
|
|
|
|
// available for local population, we must go to the server.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!load_local_messages(msg_data)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (msg_data.get(id_info.target_id)) {
|
|
|
|
// We have a range of locally renderable messages, including
|
|
|
|
// our target, so we can render the narrow locally.
|
|
|
|
id_info.local_select_id = id_info.final_select_id;
|
|
|
|
return;
|
2018-05-04 17:44:17 +02:00
|
|
|
}
|
|
|
|
|
2018-06-01 01:07:45 +02:00
|
|
|
// Note: Arguably, we could have a use_closest sort of condition
|
|
|
|
// here to handle cases where `target_id` doesn't match the narrow
|
|
|
|
// but is within the locally renderable range. But
|
|
|
|
// !can_apply_locally + target_id is a rare combination in the
|
|
|
|
// first place, so we don't bother.
|
|
|
|
return;
|
2021-02-28 21:31:57 +01:00
|
|
|
}
|
2018-05-04 17:44:17 +02:00
|
|
|
|
2021-02-28 21:31:57 +01:00
|
|
|
export function update_selection(opts) {
|
2018-05-03 14:34:29 +02:00
|
|
|
if (message_list.narrowed.empty()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const id_info = opts.id_info;
|
|
|
|
const select_offset = opts.select_offset;
|
2018-05-03 14:34:29 +02:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
let msg_id = id_info.final_select_id;
|
2018-06-01 01:07:45 +02:00
|
|
|
if (msg_id === undefined) {
|
2018-05-03 14:34:29 +02:00
|
|
|
msg_id = message_list.narrowed.first_unread_message_id();
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const preserve_pre_narrowing_screen_position =
|
2020-07-15 00:34:28 +02:00
|
|
|
message_list.narrowed.get(msg_id) !== undefined && select_offset !== undefined;
|
2018-05-03 14:34:29 +02:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const then_scroll = !preserve_pre_narrowing_screen_position;
|
2018-05-03 14:34:29 +02:00
|
|
|
|
2018-05-06 21:43:17 +02:00
|
|
|
message_list.narrowed.select_id(msg_id, {
|
2020-07-20 22:18:43 +02:00
|
|
|
then_scroll,
|
2018-05-06 21:43:17 +02:00
|
|
|
use_closest: true,
|
|
|
|
force_rerender: true,
|
|
|
|
});
|
2018-05-03 14:34:29 +02:00
|
|
|
|
|
|
|
if (preserve_pre_narrowing_screen_position) {
|
|
|
|
// Scroll so that the selected message is in the same
|
|
|
|
// position in the viewport as it was prior to
|
|
|
|
// narrowing
|
|
|
|
message_list.narrowed.view.set_message_offset(select_offset);
|
|
|
|
}
|
|
|
|
unread_ops.process_visible();
|
2021-02-28 21:31:57 +01:00
|
|
|
}
|
2018-05-03 14:34:29 +02:00
|
|
|
|
2021-02-28 21:31:57 +01:00
|
|
|
export function activate_stream_for_cycle_hotkey(stream_name) {
|
2017-08-16 19:06:07 +02:00
|
|
|
// This is the common code for A/D hotkeys.
|
2020-07-15 00:34:28 +02:00
|
|
|
const filter_expr = [{operator: "stream", operand: stream_name}];
|
2021-02-28 21:31:57 +01:00
|
|
|
activate(filter_expr, {});
|
|
|
|
}
|
2017-08-16 19:06:07 +02:00
|
|
|
|
2021-02-28 21:31:57 +01:00
|
|
|
export function stream_cycle_backward() {
|
2019-11-02 00:06:25 +01:00
|
|
|
const curr_stream = narrow_state.stream();
|
2017-08-16 19:06:07 +02:00
|
|
|
|
|
|
|
if (!curr_stream) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const stream_name = topic_generator.get_prev_stream(curr_stream);
|
2017-08-16 19:06:07 +02:00
|
|
|
|
|
|
|
if (!stream_name) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-02-28 21:31:57 +01:00
|
|
|
activate_stream_for_cycle_hotkey(stream_name);
|
|
|
|
}
|
2017-08-16 19:06:07 +02:00
|
|
|
|
2021-02-28 21:31:57 +01:00
|
|
|
export function stream_cycle_forward() {
|
2019-11-02 00:06:25 +01:00
|
|
|
const curr_stream = narrow_state.stream();
|
2017-08-16 19:06:07 +02:00
|
|
|
|
|
|
|
if (!curr_stream) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const stream_name = topic_generator.get_next_stream(curr_stream);
|
2017-08-16 19:06:07 +02:00
|
|
|
|
|
|
|
if (!stream_name) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-02-28 21:31:57 +01:00
|
|
|
activate_stream_for_cycle_hotkey(stream_name);
|
|
|
|
}
|
2017-08-16 19:06:07 +02:00
|
|
|
|
2021-02-28 21:31:57 +01:00
|
|
|
export function narrow_to_next_topic() {
|
2020-03-15 17:33:48 +01:00
|
|
|
const curr_info = {
|
|
|
|
stream: narrow_state.stream(),
|
|
|
|
topic: narrow_state.topic(),
|
|
|
|
};
|
2017-04-21 23:49:33 +02:00
|
|
|
|
2020-07-15 00:34:28 +02:00
|
|
|
const next_narrow = topic_generator.get_next_topic(curr_info.stream, curr_info.topic);
|
2017-04-21 23:49:33 +02:00
|
|
|
|
|
|
|
if (!next_narrow) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const filter_expr = [
|
2020-07-15 01:29:15 +02:00
|
|
|
{operator: "stream", operand: next_narrow.stream},
|
|
|
|
{operator: "topic", operand: next_narrow.topic},
|
2017-04-21 23:49:33 +02:00
|
|
|
];
|
|
|
|
|
2021-02-28 21:31:57 +01:00
|
|
|
activate(filter_expr, {});
|
|
|
|
}
|
2017-04-21 23:49:33 +02:00
|
|
|
|
2021-02-28 21:31:57 +01:00
|
|
|
export function narrow_to_next_pm_string() {
|
2019-11-02 00:06:25 +01:00
|
|
|
const curr_pm = narrow_state.pm_string();
|
2018-02-09 23:04:20 +01:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const next_pm = topic_generator.get_next_unread_pm_string(curr_pm);
|
2018-02-09 23:04:20 +01:00
|
|
|
|
|
|
|
if (!next_pm) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Hopefully someday we can narrow by user_ids_string instead of
|
|
|
|
// mapping back to emails.
|
2019-11-02 00:06:25 +01:00
|
|
|
const pm_with = people.user_ids_string_to_emails_string(next_pm);
|
2018-02-09 23:04:20 +01:00
|
|
|
|
2020-07-15 00:34:28 +02:00
|
|
|
const filter_expr = [{operator: "pm-with", operand: pm_with}];
|
2018-02-09 23:04:20 +01:00
|
|
|
|
2018-02-16 15:56:25 +01:00
|
|
|
// force_close parameter is true to not auto open compose_box
|
2019-11-02 00:06:25 +01:00
|
|
|
const opts = {
|
2018-02-16 15:56:25 +01:00
|
|
|
force_close: true,
|
2018-02-09 23:04:20 +01:00
|
|
|
};
|
|
|
|
|
2021-02-28 21:31:57 +01:00
|
|
|
activate(filter_expr, opts);
|
|
|
|
}
|
2018-02-09 23:04:20 +01:00
|
|
|
|
2013-01-02 19:21:39 +01:00
|
|
|
// Activate narrowing with a single operator.
|
|
|
|
// This is just for syntactic convenience.
|
2021-02-28 21:31:57 +01:00
|
|
|
export function by(operator, operand, opts) {
|
|
|
|
activate([{operator, operand}], opts);
|
|
|
|
}
|
2013-02-09 08:27:20 +01:00
|
|
|
|
2021-02-28 21:31:57 +01:00
|
|
|
export function by_topic(target_id, opts) {
|
2021-03-30 02:21:21 +02:00
|
|
|
// don't use message_lists.current as it won't work for muted messages or for out-of-narrow links
|
2019-11-02 00:06:25 +01:00
|
|
|
const original = message_store.get(target_id);
|
2020-07-15 01:29:15 +02:00
|
|
|
if (original.type !== "stream") {
|
2016-10-28 19:07:25 +02:00
|
|
|
// Only stream messages have topics, but the
|
2012-10-24 05:04:42 +02:00
|
|
|
// user wants us to narrow in some way.
|
2021-02-28 21:31:57 +01:00
|
|
|
by_recipient(target_id, opts);
|
2012-10-03 20:49:58 +02:00
|
|
|
return;
|
2012-10-24 05:04:42 +02:00
|
|
|
}
|
2019-07-10 02:03:41 +02:00
|
|
|
|
|
|
|
// We don't check msg_list.can_mark_messages_read here only because
|
|
|
|
// the target msg_list isn't initialized yet; in any case, the
|
|
|
|
// message is about to be marked read in the new view.
|
2018-04-04 21:32:45 +02:00
|
|
|
unread_ops.notify_server_message_read(original);
|
2019-07-10 02:03:41 +02:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const search_terms = [
|
2020-07-15 01:29:15 +02:00
|
|
|
{operator: "stream", operand: original.stream},
|
|
|
|
{operator: "topic", operand: original.topic},
|
2014-02-10 20:53:38 +01:00
|
|
|
];
|
2020-07-16 22:40:18 +02:00
|
|
|
opts = {then_select_id: target_id, ...opts};
|
2021-02-28 21:31:57 +01:00
|
|
|
activate(search_terms, opts);
|
|
|
|
}
|
2012-11-15 16:57:59 +01:00
|
|
|
|
2012-10-10 23:24:11 +02:00
|
|
|
// Called for the 'narrow by stream' hotkey.
|
2021-02-28 21:31:57 +01:00
|
|
|
export function by_recipient(target_id, opts) {
|
2020-07-16 22:40:18 +02:00
|
|
|
opts = {then_select_id: target_id, ...opts};
|
2021-03-30 02:21:21 +02:00
|
|
|
// don't use message_lists.current as it won't work for muted messages or for out-of-narrow links
|
2019-11-02 00:06:25 +01:00
|
|
|
const message = message_store.get(target_id);
|
2019-07-10 02:03:41 +02:00
|
|
|
|
|
|
|
// We don't check msg_list.can_mark_messages_read here only because
|
|
|
|
// the target msg_list isn't initialized yet; in any case, the
|
|
|
|
// message is about to be marked read in the new view.
|
2018-04-04 21:32:45 +02:00
|
|
|
unread_ops.notify_server_message_read(message);
|
2019-07-10 02:03:41 +02:00
|
|
|
|
2012-10-19 19:00:46 +02:00
|
|
|
switch (message.type) {
|
2020-07-15 02:14:03 +02:00
|
|
|
case "private":
|
2021-02-28 21:31:57 +01:00
|
|
|
by("pm-with", message.reply_to, opts);
|
2020-07-15 02:14:03 +02:00
|
|
|
break;
|
2012-10-19 17:11:31 +02:00
|
|
|
|
2020-07-15 02:14:03 +02:00
|
|
|
case "stream":
|
2021-02-28 21:31:57 +01:00
|
|
|
by("stream", message.stream, opts);
|
2020-07-15 02:14:03 +02:00
|
|
|
break;
|
2012-10-03 20:49:58 +02:00
|
|
|
}
|
2021-02-28 21:31:57 +01:00
|
|
|
}
|
2012-10-03 20:49:58 +02:00
|
|
|
|
2018-11-30 00:20:10 +01:00
|
|
|
// Called by the narrow_to_compose_target hotkey.
|
2021-02-28 21:31:57 +01:00
|
|
|
export function to_compose_target() {
|
2018-11-30 00:20:10 +01:00
|
|
|
if (!compose_state.composing()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const opts = {
|
2020-07-15 01:29:15 +02:00
|
|
|
trigger: "narrow_to_compose_target",
|
2018-11-30 00:20:10 +01:00
|
|
|
};
|
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
if (compose_state.get_message_type() === "stream") {
|
2019-11-02 00:06:25 +01:00
|
|
|
const stream_name = compose_state.stream_name();
|
|
|
|
const stream_id = stream_data.get_stream_id(stream_name);
|
2018-11-30 00:20:10 +01:00
|
|
|
if (!stream_id) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// If we are composing to a new topic, we narrow to the stream but
|
|
|
|
// grey-out the message view instead of narrowing to an empty view.
|
2020-03-22 18:40:05 +01:00
|
|
|
const topics = stream_topic_history.get_recent_topic_names(stream_id);
|
2020-07-15 01:29:15 +02:00
|
|
|
const operators = [{operator: "stream", operand: stream_name}];
|
2019-11-02 00:06:25 +01:00
|
|
|
const topic = compose_state.topic();
|
js: Convert a.indexOf(…) !== -1 to a.includes(…).
Babel polyfills this for us for Internet Explorer.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import K from "ast-types/gen/kinds";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
recast.visit(ast, {
visitBinaryExpression(path) {
const { operator, left, right } = path.node;
if (
n.CallExpression.check(left) &&
n.MemberExpression.check(left.callee) &&
!left.callee.computed &&
n.Identifier.check(left.callee.property) &&
left.callee.property.name === "indexOf" &&
left.arguments.length === 1 &&
checkExpression(left.arguments[0]) &&
((["===", "!==", "==", "!=", ">", "<="].includes(operator) &&
n.UnaryExpression.check(right) &&
right.operator == "-" &&
n.Literal.check(right.argument) &&
right.argument.value === 1) ||
([">=", "<"].includes(operator) &&
n.Literal.check(right) &&
right.value === 0))
) {
const test = b.callExpression(
b.memberExpression(left.callee.object, b.identifier("includes")),
[left.arguments[0]]
);
path.replace(
["!==", "!=", ">", ">="].includes(operator)
? test
: b.unaryExpression("!", test)
);
changed = true;
}
this.traverse(path);
},
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-08 04:55:06 +01:00
|
|
|
if (topics.includes(topic)) {
|
2020-07-15 01:29:15 +02:00
|
|
|
operators.push({operator: "topic", operand: topic});
|
2018-11-30 00:20:10 +01:00
|
|
|
}
|
2021-02-28 21:31:57 +01:00
|
|
|
activate(operators, opts);
|
2018-11-30 00:20:10 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
if (compose_state.get_message_type() === "private") {
|
2019-12-02 17:53:55 +01:00
|
|
|
const recipient_string = compose_state.private_message_recipient();
|
2019-11-02 00:06:25 +01:00
|
|
|
const emails = util.extract_pm_recipients(recipient_string);
|
2020-07-02 01:39:34 +02:00
|
|
|
const invalid = emails.filter((email) => !people.is_valid_email_for_compose(email));
|
2018-11-30 00:20:10 +01:00
|
|
|
// If there are no recipients or any recipient is
|
|
|
|
// invalid, narrow to all PMs.
|
|
|
|
if (emails.length === 0 || invalid.length > 0) {
|
2021-02-28 21:31:57 +01:00
|
|
|
by("is", "private", opts);
|
2018-11-30 00:20:10 +01:00
|
|
|
return;
|
|
|
|
}
|
2021-02-28 21:31:57 +01:00
|
|
|
by("pm-with", util.normalize_recipients(recipient_string), opts);
|
2018-11-30 00:20:10 +01:00
|
|
|
}
|
2021-02-28 21:31:57 +01:00
|
|
|
}
|
2018-11-30 00:20:10 +01:00
|
|
|
|
2018-08-06 17:43:24 +02:00
|
|
|
function handle_post_narrow_deactivate_processes() {
|
|
|
|
compose_fade.update_message_list();
|
|
|
|
|
|
|
|
// clear existing search pills
|
|
|
|
if (page_params.search_pills_enabled) {
|
|
|
|
search_pill_widget.widget.clear(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
top_left_corner.handle_narrow_deactivated();
|
|
|
|
stream_list.handle_narrow_deactivated();
|
2019-03-06 23:46:53 +01:00
|
|
|
compose.update_closed_compose_buttons_for_stream();
|
2018-08-06 18:01:04 +02:00
|
|
|
message_edit.handle_narrow_deactivated();
|
|
|
|
widgetize.set_widgets_for_list();
|
2018-08-06 18:09:51 +02:00
|
|
|
typing_events.render_notifications_for_narrow();
|
2020-07-08 23:44:01 +02:00
|
|
|
message_view_header.initialize();
|
2021-02-28 21:31:57 +01:00
|
|
|
narrow_title = "All messages";
|
2018-08-06 17:43:24 +02:00
|
|
|
notifications.redraw_title();
|
2020-06-14 12:33:12 +02:00
|
|
|
message_scroll.hide_top_of_narrow_notices();
|
2021-03-30 02:21:21 +02:00
|
|
|
message_scroll.update_top_of_narrow_notices(message_lists.home);
|
2018-08-06 17:43:24 +02:00
|
|
|
}
|
|
|
|
|
2021-02-28 21:31:57 +01:00
|
|
|
export function deactivate(coming_from_recent_topics = false) {
|
2020-07-03 10:38:24 +02:00
|
|
|
// NOTE: Never call this function independently,
|
2021-03-22 16:09:12 +01:00
|
|
|
// always use browser_history.go_to_location("#all_messages") to
|
2020-07-03 10:38:24 +02:00
|
|
|
// activate All message narrow.
|
2020-05-19 19:45:36 +02:00
|
|
|
/*
|
2021-03-30 02:21:21 +02:00
|
|
|
Switches message_lists.current from narrowed_msg_list to
|
|
|
|
message_lists.home ("All messages"), ending the current narrow. This
|
|
|
|
is a very fast operation, because we keep message_lists.home's data
|
2020-05-19 19:45:36 +02:00
|
|
|
cached and updated in the DOM at all times, making it suitable
|
|
|
|
for rapid access via keyboard shortcuts.
|
|
|
|
|
2021-03-30 02:21:21 +02:00
|
|
|
Long-term, we will likely want to make `message_lists.home` not
|
2020-05-19 19:45:36 +02:00
|
|
|
special in any way, and instead just have a generic
|
|
|
|
message_list_data structure caching system that happens to have
|
2021-03-30 02:21:21 +02:00
|
|
|
message_lists.home in it.
|
2020-05-19 19:45:36 +02:00
|
|
|
*/
|
Simplify narrow/search interactions.
Before this change, if you hit ESC, then hotkey
code would call search.clear_search, which would
call narrow.deactivate(), which would then use
`$('#search_query')` to clear a value, but then
let search.clear_search blur the input and
disable the exit button. It was all confusing.
Things are a bit more organized now.
Now the code works like this:
hotkey.process_escape_key
Just call narrow.deactivate.
$('#search_exit').on('click', ...):
Just call narrow.deactivate.
narrow.deactivate:
Just call search.clear_search_form
search.clear_search_form:
Just do simple jquery stuff. Don't
change the entire user's narrow, not
even indirectly!
There's still a two-way interaction between
the narrow.js module and the search.js module,
but in each direction it's a one-liner.
The guiding principle here is that we only
want one top-level API, which is narrow.deactivate,
and that does the whole "kitchen sink" of
clearing searches, closing popovers, switching
in views, etc. And then all the functions it
calls out to tend to have much smaller jobs to
do.
This commit can mostly be considered a refactoring, but the
order of operations changes slightly. Basically, as
soon as you hit ESC or click on the search "X", we
clear the search widget. Most users won't notice
any difference, because we don't have to hit the
server to populate the home view. And it's arguably
an improvement to give more immediate feedback.
2018-09-10 19:36:58 +02:00
|
|
|
search.clear_search_form();
|
2021-05-10 07:02:14 +02:00
|
|
|
// Both All messages and Recent topics have `undefined` filter.
|
2020-07-05 12:19:09 +02:00
|
|
|
// Return if already in the All message narrow.
|
|
|
|
if (narrow_state.filter() === undefined && !coming_from_recent_topics) {
|
2012-10-03 20:49:58 +02:00
|
|
|
return;
|
|
|
|
}
|
2014-02-13 18:49:44 +01:00
|
|
|
unnarrow_times = {start_time: new Date()};
|
2013-10-30 18:38:16 +01:00
|
|
|
blueslip.debug("Unnarrowed");
|
2013-04-24 21:40:08 +02:00
|
|
|
|
2020-09-23 02:27:16 +02:00
|
|
|
if (message_scroll.is_actively_scrolling()) {
|
2013-07-16 20:00:47 +02:00
|
|
|
// There is no way to intercept in-flight scroll events, and they will
|
|
|
|
// cause you to end up in the wrong place if you are actively scrolling
|
|
|
|
// on an unnarrow. Wait a bit and try again once the scrolling is over.
|
2021-02-28 21:31:57 +01:00
|
|
|
setTimeout(deactivate, 50);
|
2013-07-16 20:00:47 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-03-18 18:10:37 +01:00
|
|
|
if (!compose_state.has_message_content()) {
|
2017-03-18 17:55:11 +01:00
|
|
|
compose_actions.cancel();
|
2013-10-10 16:43:49 +02:00
|
|
|
}
|
2013-10-10 15:08:17 +02:00
|
|
|
|
2017-04-25 15:25:31 +02:00
|
|
|
narrow_state.reset_current_filter();
|
2012-10-03 20:49:58 +02:00
|
|
|
|
2021-03-26 15:21:47 +01:00
|
|
|
narrow_banner.hide_empty_narrow_message();
|
2013-02-23 19:38:25 +01:00
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
$("body").removeClass("narrowed_view");
|
|
|
|
$("#zfilt").removeClass("focused_table");
|
|
|
|
$("#zhome").addClass("focused_table");
|
2021-03-30 02:21:21 +02:00
|
|
|
message_lists.set_current(message_lists.home);
|
2018-03-27 18:52:45 +02:00
|
|
|
condense.condense_and_collapse($("#zhome div.message_row"));
|
2013-01-09 23:46:32 +01:00
|
|
|
|
2018-03-09 22:12:38 +01:00
|
|
|
message_scroll.hide_indicators();
|
2014-01-15 17:48:40 +01:00
|
|
|
hashchange.save_narrow();
|
2013-02-14 23:48:37 +01:00
|
|
|
|
2021-03-30 02:21:21 +02:00
|
|
|
if (message_lists.current.selected_id() !== -1) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const preserve_pre_narrowing_screen_position =
|
2021-03-30 02:21:21 +02:00
|
|
|
message_lists.current.selected_row().length > 0 &&
|
|
|
|
message_lists.current.pre_narrow_offset !== undefined;
|
2019-11-02 00:06:25 +01:00
|
|
|
let message_id_to_select;
|
|
|
|
const select_opts = {
|
2014-02-04 22:13:34 +01:00
|
|
|
then_scroll: true,
|
|
|
|
use_closest: true,
|
2017-01-12 00:17:43 +01:00
|
|
|
empty_ok: true,
|
2014-02-04 22:13:34 +01:00
|
|
|
};
|
2013-12-02 19:16:37 +01:00
|
|
|
|
2014-02-04 22:13:34 +01:00
|
|
|
// We fall back to the closest selected id, if the user has removed a
|
|
|
|
// stream from the home view since leaving it the old selected id might
|
|
|
|
// no longer be there
|
|
|
|
// Additionally, we pass empty_ok as the user may have removed **all** streams
|
2017-07-05 11:43:14 +02:00
|
|
|
// from their home view
|
2016-04-03 16:45:07 +02:00
|
|
|
if (unread.messages_read_in_narrow) {
|
2013-11-26 19:06:21 +01:00
|
|
|
// We read some unread messages in a narrow. Instead of going back to
|
|
|
|
// where we were before the narrow, go to our first unread message (or
|
|
|
|
// the bottom of the feed, if there are no unread messages).
|
2021-03-30 02:21:21 +02:00
|
|
|
message_id_to_select = message_lists.current.first_unread_message_id();
|
2013-11-26 19:06:21 +01:00
|
|
|
} else {
|
|
|
|
// We narrowed, but only backwards in time (ie no unread were read). Try
|
|
|
|
// to go back to exactly where we were before narrowing.
|
2014-01-09 22:57:53 +01:00
|
|
|
if (preserve_pre_narrowing_screen_position) {
|
|
|
|
// We scroll the user back to exactly the offset from the selected
|
2017-07-05 11:43:14 +02:00
|
|
|
// message that they were at the time that they narrowed.
|
2014-01-09 22:57:53 +01:00
|
|
|
// TODO: Make this correctly handle the case of resizing while narrowed.
|
2021-03-30 02:21:21 +02:00
|
|
|
select_opts.target_scroll_offset = message_lists.current.pre_narrow_offset;
|
2014-01-09 22:57:53 +01:00
|
|
|
}
|
2021-03-30 02:21:21 +02:00
|
|
|
message_id_to_select = message_lists.current.selected_id();
|
2013-12-02 19:16:37 +01:00
|
|
|
}
|
2021-03-30 02:21:21 +02:00
|
|
|
message_lists.current.select_id(message_id_to_select, select_opts);
|
2013-07-03 21:39:41 +02:00
|
|
|
}
|
2013-01-11 16:57:17 +01:00
|
|
|
|
2018-08-06 17:43:24 +02:00
|
|
|
handle_post_narrow_deactivate_processes();
|
2015-12-10 20:37:01 +01:00
|
|
|
|
2014-02-13 18:49:44 +01:00
|
|
|
unnarrow_times.initial_core_time = new Date();
|
2020-07-02 01:45:54 +02:00
|
|
|
setTimeout(() => {
|
2020-04-13 18:53:07 +02:00
|
|
|
resize.resize_stream_filters_container();
|
2014-02-13 18:49:44 +01:00
|
|
|
unnarrow_times.initial_free_time = new Date();
|
|
|
|
report_unnarrow_time();
|
|
|
|
});
|
2021-02-28 21:31:57 +01:00
|
|
|
}
|