diff --git a/.eslintrc.json b/.eslintrc.json index 293047dd7c..3fc7b0476e 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -98,6 +98,7 @@ "stream_sort": false, "stream_list": false, "stream_popover": false, + "narrow_state": false, "narrow": false, "narrow_state": false, "admin_sections": false, diff --git a/frontend_tests/node_tests/narrow.js b/frontend_tests/node_tests/narrow.js index 7c092d6b3a..d95cdec147 100644 --- a/frontend_tests/node_tests/narrow.js +++ b/frontend_tests/node_tests/narrow.js @@ -3,12 +3,16 @@ global.stub_out_jquery(); add_dependencies({ hash_util: 'js/hash_util.js', hashchange: 'js/hashchange.js', + narrow_state: 'js/narrow_state.js', people: 'js/people.js', stream_data: 'js/stream_data.js', Filter: 'js/filter.js', }); var narrow = require('js/narrow.js'); + +var narrow_state = global.narrow_state; + var Filter = global.Filter; var stream_data = global.stream_data; var _ = global._; @@ -17,68 +21,68 @@ function set_filter(operators) { operators = _.map(operators, function (op) { return {operator: op[0], operand: op[1]}; }); - narrow._set_current_filter(new Filter(operators)); + narrow_state.set_current_filter(new Filter(operators)); } (function test_stream() { var test_stream = {name: 'Test', stream_id: 15}; stream_data.add_sub('Test', test_stream); - assert(!narrow.is_for_stream_id(test_stream.stream_id)); + assert(!narrow_state.is_for_stream_id(test_stream.stream_id)); set_filter([['stream', 'Test'], ['topic', 'Bar'], ['search', 'yo']]); - assert.equal(narrow.stream(), 'Test'); - assert.equal(narrow.topic(), 'Bar'); - assert(narrow.is_for_stream_id(test_stream.stream_id)); + assert.equal(narrow_state.stream(), 'Test'); + assert.equal(narrow_state.topic(), 'Bar'); + assert(narrow_state.is_for_stream_id(test_stream.stream_id)); }()); (function test_narrowed() { - narrow._set_current_filter(undefined); // not narrowed, basically - assert(!narrow.narrowed_to_pms()); - assert(!narrow.narrowed_by_reply()); - assert(!narrow.narrowed_by_pm_reply()); - assert(!narrow.narrowed_by_topic_reply()); - assert(!narrow.narrowed_to_search()); - assert(!narrow.narrowed_to_topic()); + narrow_state.reset_current_filter(); // not narrowed, basically + assert(!narrow_state.narrowed_to_pms()); + assert(!narrow_state.narrowed_by_reply()); + assert(!narrow_state.narrowed_by_pm_reply()); + assert(!narrow_state.narrowed_by_topic_reply()); + assert(!narrow_state.narrowed_to_search()); + assert(!narrow_state.narrowed_to_topic()); set_filter([['stream', 'Foo']]); - assert(!narrow.narrowed_to_pms()); - assert(!narrow.narrowed_by_reply()); - assert(!narrow.narrowed_by_pm_reply()); - assert(!narrow.narrowed_by_topic_reply()); - assert(!narrow.narrowed_to_search()); - assert(!narrow.narrowed_to_topic()); + assert(!narrow_state.narrowed_to_pms()); + assert(!narrow_state.narrowed_by_reply()); + assert(!narrow_state.narrowed_by_pm_reply()); + assert(!narrow_state.narrowed_by_topic_reply()); + assert(!narrow_state.narrowed_to_search()); + assert(!narrow_state.narrowed_to_topic()); set_filter([['pm-with', 'steve@zulip.com']]); - assert(narrow.narrowed_to_pms()); - assert(narrow.narrowed_by_reply()); - assert(narrow.narrowed_by_pm_reply()); - assert(!narrow.narrowed_by_topic_reply()); - assert(!narrow.narrowed_to_search()); - assert(!narrow.narrowed_to_topic()); + assert(narrow_state.narrowed_to_pms()); + assert(narrow_state.narrowed_by_reply()); + assert(narrow_state.narrowed_by_pm_reply()); + assert(!narrow_state.narrowed_by_topic_reply()); + assert(!narrow_state.narrowed_to_search()); + assert(!narrow_state.narrowed_to_topic()); set_filter([['stream', 'Foo'], ['topic', 'bar']]); - assert(!narrow.narrowed_to_pms()); - assert(narrow.narrowed_by_reply()); - assert(!narrow.narrowed_by_pm_reply()); - assert(narrow.narrowed_by_topic_reply()); - assert(!narrow.narrowed_to_search()); - assert(narrow.narrowed_to_topic()); + assert(!narrow_state.narrowed_to_pms()); + assert(narrow_state.narrowed_by_reply()); + assert(!narrow_state.narrowed_by_pm_reply()); + assert(narrow_state.narrowed_by_topic_reply()); + assert(!narrow_state.narrowed_to_search()); + assert(narrow_state.narrowed_to_topic()); set_filter([['search', 'grail']]); - assert(!narrow.narrowed_to_pms()); - assert(!narrow.narrowed_by_reply()); - assert(!narrow.narrowed_by_pm_reply()); - assert(!narrow.narrowed_by_topic_reply()); - assert(narrow.narrowed_to_search()); - assert(!narrow.narrowed_to_topic()); + assert(!narrow_state.narrowed_to_pms()); + assert(!narrow_state.narrowed_by_reply()); + assert(!narrow_state.narrowed_by_pm_reply()); + assert(!narrow_state.narrowed_by_topic_reply()); + assert(narrow_state.narrowed_to_search()); + assert(!narrow_state.narrowed_to_topic()); }()); (function test_operators() { set_filter([['stream', 'Foo'], ['topic', 'Bar'], ['search', 'Yo']]); - var result = narrow.operators(); + var result = narrow_state.operators(); assert.equal(result.length, 3); assert.equal(result[0].operator, 'stream'); assert.equal(result[0].operand, 'Foo'); @@ -123,19 +127,19 @@ function set_filter(operators) { (function test_muting_enabled() { set_filter([['stream', 'devel']]); - assert(narrow.muting_enabled()); + assert(narrow_state.muting_enabled()); - narrow._set_current_filter(undefined); // not narrowed, basically - assert(narrow.muting_enabled()); + narrow_state.reset_current_filter(); // not narrowed, basically + assert(narrow_state.muting_enabled()); set_filter([['stream', 'devel'], ['topic', 'mac']]); - assert(!narrow.muting_enabled()); + assert(!narrow_state.muting_enabled()); set_filter([['search', 'whatever']]); - assert(!narrow.muting_enabled()); + assert(!narrow_state.muting_enabled()); set_filter([['is', 'private']]); - assert(!narrow.muting_enabled()); + assert(!narrow_state.muting_enabled()); }()); @@ -143,7 +147,7 @@ function set_filter(operators) { set_filter([['stream', 'Foo'], ['topic', 'Bar']]); var opts = {}; - narrow.set_compose_defaults(opts); + narrow_state.set_compose_defaults(opts); assert.equal(opts.stream, 'Foo'); assert.equal(opts.subject, 'Bar'); @@ -151,7 +155,7 @@ function set_filter(operators) { set_filter([['stream', 'rome']]); opts = {}; - narrow.set_compose_defaults(opts); + narrow_state.set_compose_defaults(opts); assert.equal(opts.stream, 'ROME'); }()); @@ -191,7 +195,7 @@ function set_filter(operators) { return {hide: function () {hide_id = id;}, show: function () {show_id = id;}}; }; - narrow._set_current_filter(undefined); + narrow_state.reset_current_filter(); narrow.show_empty_narrow_message(); assert.equal(hide_id,'.empty_feed_notice'); assert.equal(show_id, '#empty_narrow_message'); @@ -262,8 +266,8 @@ function set_filter(operators) { ['sender', 'steve@foo.com'], ['stream', 'steve@foo.com'], // try to be tricky ]); - narrow.update_email(steve.user_id, 'showell@foo.com'); - var filter = narrow.filter(); + narrow_state.update_email(steve.user_id, 'showell@foo.com'); + var filter = narrow_state.filter(); assert.deepEqual(filter.operands('pm-with'), ['showell@foo.com']); assert.deepEqual(filter.operands('sender'), ['showell@foo.com']); assert.deepEqual(filter.operands('stream'), ['steve@foo.com']); diff --git a/frontend_tests/node_tests/search_suggestion.js b/frontend_tests/node_tests/search_suggestion.js index 065436445e..ada2ad3773 100644 --- a/frontend_tests/node_tests/search_suggestion.js +++ b/frontend_tests/node_tests/search_suggestion.js @@ -14,7 +14,7 @@ add_dependencies({ typeahead_helper: 'js/typeahead_helper.js', people: 'js/people.js', stream_data: 'js/stream_data.js', - narrow: 'js/narrow.js', + narrow_state: 'js/narrow_state.js', }); var people = global.people; @@ -46,7 +46,7 @@ global.stream_data.populate_stream_topics_for_tests({}); return []; }; - global.narrow.stream = function () { + global.narrow_state.stream = function () { return 'office'; }; @@ -65,7 +65,7 @@ global.stream_data.populate_stream_topics_for_tests({}); return []; }; - global.narrow.stream = function () { + global.narrow_state.stream = function () { return undefined; }; @@ -85,7 +85,7 @@ global.stream_data.populate_stream_topics_for_tests({}); return []; }; - global.narrow.stream = function () { + global.narrow_state.stream = function () { return undefined; }; @@ -220,7 +220,7 @@ global.stream_data.populate_stream_topics_for_tests({}); return []; }; - global.narrow.stream = function () { + global.narrow_state.stream = function () { return undefined; }; @@ -332,7 +332,7 @@ init(); return ['devel', 'office']; }; - global.narrow.stream = function () { + global.narrow_state.stream = function () { return undefined; }; @@ -369,7 +369,7 @@ init(); return []; }; - global.narrow.stream = function () { + global.narrow_state.stream = function () { return undefined; }; @@ -428,7 +428,7 @@ init(); return ['office']; }; - global.narrow.stream = function () { + global.narrow_state.stream = function () { return 'office'; }; @@ -496,7 +496,7 @@ init(); return ['office']; }; - global.narrow.stream = function () { + global.narrow_state.stream = function () { return; }; @@ -518,7 +518,7 @@ init(); return ['office']; }; - global.narrow.stream = function () { + global.narrow_state.stream = function () { return; }; @@ -541,7 +541,7 @@ init(); return []; }; - global.narrow.stream = function () { + global.narrow_state.stream = function () { return; }; diff --git a/frontend_tests/node_tests/unread.js b/frontend_tests/node_tests/unread.js index bd93001b73..49815612f2 100644 --- a/frontend_tests/node_tests/unread.js +++ b/frontend_tests/node_tests/unread.js @@ -1,6 +1,6 @@ // Unit test the unread.js module, which depends on these global variables: // -// _, narrow, current_msg_list, home_msg_list, subs +// _, narrow_state, current_msg_list, home_msg_list, subs // // These tests are framework-free and run sequentially; they are invoked // immediately after being defined. The contract here is that tests should @@ -29,8 +29,8 @@ var people = global.people; var unread = require('js/unread.js'); -var narrow = {}; -global.narrow = narrow; +var narrow_state = {}; +global.narrow_state = narrow_state; var current_msg_list = {}; global.current_msg_list = current_msg_list; @@ -57,7 +57,7 @@ var zero_counts = { }; (function test_empty_counts_while_narrowed() { - narrow.active = function () { + narrow_state.active = function () { return true; }; current_msg_list.all_messages = function () { @@ -69,7 +69,7 @@ var zero_counts = { }()); (function test_empty_counts_while_home() { - narrow.active = function () { + narrow_state.active = function () { return false; }; current_msg_list.all_messages = function () { @@ -219,7 +219,7 @@ var zero_counts = { (function test_home_messages() { - narrow.active = function () { + narrow_state.active = function () { return false; }; stream_data.is_subscribed = function () { @@ -279,7 +279,7 @@ var zero_counts = { }()); (function test_private_messages() { - narrow.active = function () { + narrow_state.active = function () { return false; }; stream_data.is_subscribed = function () { @@ -351,7 +351,7 @@ var zero_counts = { (function test_mentions() { - narrow.active = function () { + narrow_state.active = function () { return false; }; stream_data.is_subscribed = function () { diff --git a/static/js/compose_actions.js b/static/js/compose_actions.js index a8d22219c7..e0f156ac40 100644 --- a/static/js/compose_actions.js +++ b/static/js/compose_actions.js @@ -304,7 +304,7 @@ exports.on_topic_narrow = function () { return; } - if (compose_state.stream_name() !== narrow.stream()) { + if (compose_state.stream_name() !== narrow_state.stream()) { // If we changed streams, then we only leave the // compose box open if there is content. if (compose_state.has_message_content()) { @@ -333,12 +333,12 @@ exports.on_topic_narrow = function () { // stream filled in, and we just need to update the topic. // See #3300 for context--a couple users specifically asked // for this convenience. - compose_state.subject(narrow.topic()); + compose_state.subject(narrow_state.topic()); $('#new_message_content').focus().select(); }; exports.on_narrow = function () { - if (narrow.narrowed_by_topic_reply()) { + if (narrow_state.narrowed_by_topic_reply()) { exports.on_topic_narrow(); return; } @@ -348,7 +348,7 @@ exports.on_narrow = function () { return; } - if (narrow.narrowed_by_pm_reply()) { + if (narrow_state.narrowed_by_pm_reply()) { exports.start('private'); return; } diff --git a/static/js/echo.js b/static/js/echo.js index d8919fe654..8f04fb6cf5 100644 --- a/static/js/echo.js +++ b/static/js/echo.js @@ -211,7 +211,7 @@ exports.try_deliver_locally = function try_deliver_locally(message_request) { return undefined; } - if (narrow.active() && !narrow.filter().can_apply_locally()) { + if (narrow_state.active() && !narrow_state.filter().can_apply_locally()) { return undefined; } diff --git a/static/js/message_events.js b/static/js/message_events.js index 2240b396b9..0a25340332 100644 --- a/static/js/message_events.js +++ b/static/js/message_events.js @@ -12,7 +12,7 @@ function maybe_add_narrowed_messages(messages, msg_list, messages_are_new, local url: '/json/messages_in_narrow', idempotent: true, data: {msg_ids: JSON.stringify(ids), - narrow: JSON.stringify(narrow.public_operators())}, + narrow: JSON.stringify(narrow_state.public_operators())}, timeout: 5000, success: function (data) { if (msg_list !== current_msg_list) { @@ -63,8 +63,8 @@ exports.insert_new_messages = function insert_new_messages(messages, local_id) { message_util.add_messages(messages, home_msg_list, {messages_are_new: true}); message_util.add_messages(messages, message_list.all, {messages_are_new: true}); - if (narrow.active()) { - if (narrow.filter().can_apply_locally()) { + if (narrow_state.active()) { + if (narrow_state.filter().can_apply_locally()) { message_util.add_messages(messages, message_list.narrowed, {messages_are_new: true}); notifications.possibly_notify_new_messages_outside_viewport(messages, local_id); } else { @@ -78,7 +78,7 @@ exports.insert_new_messages = function insert_new_messages(messages, local_id) { activity.process_loaded_messages(messages); message_util.do_unread_count_updates(messages); - if (narrow.narrowed_by_reply()) { + if (narrow_state.narrowed_by_reply()) { // If you send a message when narrowed to a recipient, move the // pointer to it. @@ -159,7 +159,7 @@ exports.update_messages = function update_messages(events) { var selection_changed_topic = _.indexOf(event.message_ids, current_id) >= 0; if (selection_changed_topic) { - var current_filter = narrow.filter(); + var current_filter = narrow_state.filter(); if (current_filter && stream_name) { if (current_filter.has_topic(stream_name, event.orig_subject)) { var new_filter = current_filter.filter_with_new_topic(event.subject); diff --git a/static/js/message_fetch.js b/static/js/message_fetch.js index e9c5d265da..2ac22bffbe 100644 --- a/static/js/message_fetch.js +++ b/static/js/message_fetch.js @@ -82,8 +82,8 @@ exports.load_old_messages = function load_old_messages(opts) { num_before: opts.num_before, num_after: opts.num_after}; - if (opts.msg_list.narrowed && narrow.active()) { - var operators = narrow.public_operators(); + if (opts.msg_list.narrowed && narrow_state.active()) { + var operators = narrow_state.public_operators(); if (page_params.narrow !== undefined) { operators = operators.concat(page_params.narrow); } diff --git a/static/js/message_list.js b/static/js/message_list.js index b3540d6d79..dd27b34180 100644 --- a/static/js/message_list.js +++ b/static/js/message_list.js @@ -362,7 +362,7 @@ exports.MessageList.prototype = { if (!this.narrowed) { return; } - var stream = narrow.stream(); + var stream = narrow_state.stream(); if (stream === undefined) { return; } diff --git a/static/js/message_list_view.js b/static/js/message_list_view.js index 8a083e6162..149f185bab 100644 --- a/static/js/message_list_view.js +++ b/static/js/message_list_view.js @@ -577,7 +577,7 @@ MessageListView.prototype = { _.last(last_message_group.message_containers).msg.historical; } - var stream_name = narrow.stream(); + var stream_name = narrow_state.stream(); if (stream_name !== undefined) { // If user narrows to a stream, doesn't update // trailing bookend if user is subscribed. diff --git a/static/js/narrow.js b/static/js/narrow.js index 183aa2deea..3615c1d3fe 100644 --- a/static/js/narrow.js +++ b/static/js/narrow.js @@ -2,139 +2,8 @@ var narrow = (function () { var exports = {}; -var current_filter; var unnarrow_times; -// A small concession to unit testing follows: -exports._set_current_filter = function (filter) { - current_filter = filter; -}; - -exports.active = function () { - return current_filter !== undefined; -}; - -exports.filter = function () { - return current_filter; -}; - -exports.predicate = function () { - if (current_filter === undefined) { - return function () { return true; }; - } - return current_filter.predicate(); -}; - -exports.operators = function () { - if (current_filter === undefined) { - return new Filter(page_params.narrow).operators(); - } - return current_filter.operators(); -}; - -exports.update_email = function (user_id, new_email) { - if (current_filter !== undefined) { - current_filter.update_email(user_id, new_email); - } -}; - - -/* Operators we should send to the server. */ -exports.public_operators = function () { - if (current_filter === undefined) { - return undefined; - } - return current_filter.public_operators(); -}; - -exports.search_string = function () { - return Filter.unparse(exports.operators()); -}; - -// Collect operators which appear only once into an object, -// and discard those which appear more than once. -function collect_single(operators) { - var seen = new Dict(); - var result = new Dict(); - _.each(operators, function (elem) { - var key = elem.operator; - if (seen.has(key)) { - result.del(key); - } else { - result.set(key, elem.operand); - seen.set(key, true); - } - }); - return result; -} - -// Modify default compose parameters (stream etc.) based on -// the current narrowed view. -// -// This logic is here and not in the 'compose' module because -// it will get more complicated as we add things to the narrow -// operator language. -exports.set_compose_defaults = function (opts) { - var single = collect_single(exports.operators()); - - // Set the stream, subject, and/or PM recipient if they are - // uniquely specified in the narrow view. - - if (single.has('stream')) { - opts.stream = stream_data.get_name(single.get('stream')); - } - - if (single.has('topic')) { - opts.subject = single.get('topic'); - } - - if (single.has('pm-with')) { - opts.private_message_recipient = single.get('pm-with'); - } -}; - -exports.stream = function () { - if (current_filter === undefined) { - return undefined; - } - var stream_operands = current_filter.operands("stream"); - if (stream_operands.length === 1) { - return stream_operands[0]; - } - return undefined; -}; - -exports.is_for_stream_id = function (stream_id) { - // This is not perfect, since we still track narrows by - // name, not id, but at least the interface is good going - // forward. - var sub = stream_data.get_sub_by_id(stream_id); - - if (sub === undefined) { - blueslip.error('Bad stream id ' + stream_id); - return false; - } - - var narrow_stream_name = exports.stream(); - - if (narrow_stream_name === undefined) { - return false; - } - - return (sub.name === narrow_stream_name); -}; - -exports.topic = function () { - if (current_filter === undefined) { - return undefined; - } - var operands = current_filter.operands("topic"); - if (operands.length === 1) { - return operands[0]; - } - return undefined; -}; - function report_narrow_time(initial_core_time, initial_free_time, network_time) { channel.post({ url: '/json/report_narrow_time', @@ -178,7 +47,7 @@ function report_unnarrow_time() { exports.narrow_title = "home"; exports.activate = function (raw_operators, opts) { var start_time = new Date(); - var was_narrowed_already = exports.active(); + var 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(); @@ -248,8 +117,8 @@ exports.activate = function (raw_operators, opts) { // For legacy reasons, we need to set current_filter before calling // muting_enabled. - current_filter = filter; - var muting_enabled = exports.muting_enabled(); + narrow_state.set_current_filter(filter); + var muting_enabled = narrow_state.muting_enabled(); // Save how far from the pointer the top of the message list was. if (current_msg_list.selected_id() !== -1) { @@ -274,10 +143,17 @@ exports.activate = function (raw_operators, opts) { home_msg_list.pre_narrow_offset = page_params.initial_offset; } - var msg_list = new message_list.MessageList('zfilt', current_filter, { - collapse_messages: ! current_filter.is_search(), + var msg_list_opts = { + collapse_messages: ! narrow_state.get_current_filter().is_search(), muting_enabled: muting_enabled, - }); + }; + + var msg_list = new message_list.MessageList( + 'zfilt', + narrow_state.get_current_filter(), + msg_list_opts + ); + msg_list.start_time = start_time; // Show the new set of messages. It is important to set current_msg_list to @@ -326,9 +202,11 @@ exports.activate = function (raw_operators, opts) { // Don't bother populating a message list when it won't contain // the message we want anyway or if the filter can't be applied // locally. - if (message_list.all.get(then_select_id) !== undefined && current_filter.can_apply_locally()) { - message_util.add_messages(message_list.all.all_messages(), message_list.narrowed, - {delay_render: true}); + if (message_list.all.get(then_select_id) !== undefined) { + if (narrow_state.get_current_filter().can_apply_locally()) { + message_util.add_messages(message_list.all.all_messages(), message_list.narrowed, + {delay_render: true}); + } } var defer_selecting_closest = message_list.narrowed.empty(); @@ -369,6 +247,7 @@ exports.activate = function (raw_operators, opts) { compose_actions.on_narrow(); + var current_filter = narrow_state.get_current_filter(); $(document).trigger($.Event('narrow_activated.zulip', {msg_list: message_list.narrowed, filter: current_filter, trigger: opts.trigger})); @@ -395,8 +274,8 @@ exports.stream_topic = function () { // We may be in an empty narrow. In that case we use // our narrow parameters to return the stream/topic. return { - stream: exports.stream(), - topic: exports.topic(), + stream: narrow_state.stream(), + topic: narrow_state.topic(), }; }; @@ -491,7 +370,7 @@ exports.by_conversation_and_time = function (target_id, opts) { }; exports.deactivate = function () { - if (current_filter === undefined) { + if (narrow_state.get_current_filter() === undefined) { return; } unnarrow_times = {start_time: new Date()}; @@ -509,7 +388,7 @@ exports.deactivate = function () { compose_actions.cancel(); } - current_filter = undefined; + narrow_state.reset_current_filter(); exports.hide_empty_narrow_message(); @@ -590,6 +469,9 @@ exports.restore_home_state = function () { function pick_empty_narrow_banner() { var default_banner = $('#empty_narrow_message'); + + var current_filter = narrow_state.get_current_filter(); + if (current_filter === undefined) { return default_banner; } @@ -614,7 +496,7 @@ function pick_empty_narrow_banner() { } } else if ((first_operator === "stream") && !stream_data.is_subscribed(first_operand)) { // You are narrowed to a stream to which you aren't subscribed. - if (!stream_data.get_sub(narrow.stream())) { + if (!stream_data.get_sub(narrow_state.stream())) { return $("#nonsubbed_private_nonexistent_stream_narrow_message"); } return $("#nonsubbed_stream_narrow_message"); @@ -692,58 +574,6 @@ exports.by_conversation_and_time_uri = function (message) { "/near/" + hash_util.encodeHashComponent(message.id); }; -// Are we narrowed to PMs: all PMs or PMs with particular people. -exports.narrowed_to_pms = function () { - if (current_filter === undefined) { - return false; - } - return (current_filter.has_operator("pm-with") || - current_filter.has_operand("is", "private")); -}; - -// We auto-reply under certain conditions, namely when you're narrowed -// to a PM (or huddle), and when you're narrowed to some stream/subject pair -exports.narrowed_by_reply = function () { - return (exports.narrowed_by_pm_reply() || - exports.narrowed_by_topic_reply()); -}; - -exports.narrowed_by_pm_reply = function () { - if (current_filter === undefined) { - return false; - } - var operators = current_filter.operators(); - return (operators.length === 1 && - current_filter.has_operator('pm-with')); -}; - -exports.narrowed_by_topic_reply = function () { - if (current_filter === undefined) { - return false; - } - var operators = current_filter.operators(); - return (operators.length === 2 && - current_filter.operands("stream").length === 1 && - current_filter.operands("topic").length === 1); -}; - -exports.narrowed_to_topic = function () { - if (current_filter === undefined) { - return false; - } - return (current_filter.has_operator("stream") && - current_filter.has_operator("topic")); -}; - -exports.narrowed_to_search = function () { - return (current_filter !== undefined) && current_filter.is_search(); -}; - -exports.muting_enabled = function () { - return (!exports.narrowed_to_topic() && !exports.narrowed_to_search() && - !exports.narrowed_to_pms()); -}; - return exports; }()); diff --git a/static/js/narrow_state.js b/static/js/narrow_state.js new file mode 100644 index 0000000000..1538dddb91 --- /dev/null +++ b/static/js/narrow_state.js @@ -0,0 +1,194 @@ +var narrow_state = (function () { + +var exports = {}; + +var current_filter; + +exports.reset_current_filter = function () { + current_filter = undefined; +}; + +exports.set_current_filter = function (filter) { + current_filter = filter; +}; + +exports.get_current_filter = function () { + return current_filter; +}; + +exports.active = function () { + return current_filter !== undefined; +}; + +exports.filter = function () { + return current_filter; +}; + +exports.operators = function () { + if (current_filter === undefined) { + return new Filter(page_params.narrow).operators(); + } + return current_filter.operators(); +}; + +exports.update_email = function (user_id, new_email) { + if (current_filter !== undefined) { + current_filter.update_email(user_id, new_email); + } +}; + + +/* Operators we should send to the server. */ +exports.public_operators = function () { + if (current_filter === undefined) { + return undefined; + } + return current_filter.public_operators(); +}; + +exports.search_string = function () { + return Filter.unparse(exports.operators()); +}; + +// Collect operators which appear only once into an object, +// and discard those which appear more than once. +function collect_single(operators) { + var seen = new Dict(); + var result = new Dict(); + _.each(operators, function (elem) { + var key = elem.operator; + if (seen.has(key)) { + result.del(key); + } else { + result.set(key, elem.operand); + seen.set(key, true); + } + }); + return result; +} + +// Modify default compose parameters (stream etc.) based on +// the current narrowed view. +// +// This logic is here and not in the 'compose' module because +// it will get more complicated as we add things to the narrow +// operator language. +exports.set_compose_defaults = function (opts) { + var single = collect_single(exports.operators()); + + // Set the stream, subject, and/or PM recipient if they are + // uniquely specified in the narrow view. + + if (single.has('stream')) { + opts.stream = stream_data.get_name(single.get('stream')); + } + + if (single.has('topic')) { + opts.subject = single.get('topic'); + } + + if (single.has('pm-with')) { + opts.private_message_recipient = single.get('pm-with'); + } +}; + +exports.stream = function () { + if (current_filter === undefined) { + return undefined; + } + var stream_operands = current_filter.operands("stream"); + if (stream_operands.length === 1) { + return stream_operands[0]; + } + return undefined; +}; + +exports.topic = function () { + if (current_filter === undefined) { + return undefined; + } + var operands = current_filter.operands("topic"); + if (operands.length === 1) { + return operands[0]; + } + return undefined; +}; + +// Are we narrowed to PMs: all PMs or PMs with particular people. +exports.narrowed_to_pms = function () { + if (current_filter === undefined) { + return false; + } + return (current_filter.has_operator("pm-with") || + current_filter.has_operand("is", "private")); +}; + +exports.narrowed_by_pm_reply = function () { + if (current_filter === undefined) { + return false; + } + var operators = current_filter.operators(); + return (operators.length === 1 && + current_filter.has_operator('pm-with')); +}; + +exports.narrowed_by_topic_reply = function () { + if (current_filter === undefined) { + return false; + } + var operators = current_filter.operators(); + return (operators.length === 2 && + current_filter.operands("stream").length === 1 && + current_filter.operands("topic").length === 1); +}; + +// We auto-reply under certain conditions, namely when you're narrowed +// to a PM (or huddle), and when you're narrowed to some stream/subject pair +exports.narrowed_by_reply = function () { + return (exports.narrowed_by_pm_reply() || + exports.narrowed_by_topic_reply()); +}; + +exports.narrowed_to_topic = function () { + if (current_filter === undefined) { + return false; + } + return (current_filter.has_operator("stream") && + current_filter.has_operator("topic")); +}; + +exports.narrowed_to_search = function () { + return (current_filter !== undefined) && current_filter.is_search(); +}; + +exports.muting_enabled = function () { + return (!exports.narrowed_to_topic() && !exports.narrowed_to_search() && + !exports.narrowed_to_pms()); +}; + +exports.is_for_stream_id = function (stream_id) { + // This is not perfect, since we still track narrows by + // name, not id, but at least the interface is good going + // forward. + var sub = stream_data.get_sub_by_id(stream_id); + + if (sub === undefined) { + blueslip.error('Bad stream id ' + stream_id); + return false; + } + + var narrow_stream_name = exports.stream(); + + if (narrow_stream_name === undefined) { + return false; + } + + return (sub.name === narrow_stream_name); +}; + +return exports; + +}()); +if (typeof module !== 'undefined') { + module.exports = narrow_state; +} diff --git a/static/js/navigate.js b/static/js/navigate.js index 49e8694a64..af1186ab59 100644 --- a/static/js/navigate.js +++ b/static/js/navigate.js @@ -118,12 +118,12 @@ exports.page_down = function () { exports.cycle_stream = function (direction) { var currentStream; var nextStream; - if (narrow.stream() !== undefined) { - currentStream = stream_list.get_stream_li(narrow.stream()); + if (narrow_state.stream() !== undefined) { + currentStream = stream_list.get_stream_li(narrow_state.stream()); } switch (direction) { case 'forward': - if (narrow.stream() === undefined) { + if (narrow_state.stream() === undefined) { nextStream = $("#stream_filters").children('.narrow-filter').first(); } else { nextStream = currentStream.next('.narrow-filter'); @@ -133,7 +133,7 @@ exports.cycle_stream = function (direction) { } break; case 'backward': - if (narrow.stream() === undefined) { + if (narrow_state.stream() === undefined) { nextStream = $("#stream_filters").children('.narrow-filter').last(); } else { nextStream = currentStream.prev('.narrow-filter'); diff --git a/static/js/pm_list.js b/static/js/pm_list.js index 08c733f505..d952ff2bd1 100644 --- a/static/js/pm_list.js +++ b/static/js/pm_list.js @@ -152,12 +152,12 @@ exports.rebuild_recent = function (active_conversation) { exports.update_private_messages = function () { exports._build_private_messages_list(); - if (! narrow.active()) { + if (! narrow_state.active()) { return; } - var is_pm_filter = _.contains(narrow.filter().operands('is'), "private"); - var conversation = narrow.filter().operands('pm-with'); + var is_pm_filter = _.contains(narrow_state.filter().operands('is'), "private"); + var conversation = narrow_state.filter().operands('pm-with'); if (conversation.length === 1) { exports.rebuild_recent(conversation[0]); } else if (conversation.length !== 0) { diff --git a/static/js/popovers.js b/static/js/popovers.js index 2ee6b7d42e..2e05d2f8a5 100644 --- a/static/js/popovers.js +++ b/static/js/popovers.js @@ -77,7 +77,7 @@ function show_message_info_popover(element, id) { user_time: people.get_user_time(message.sender_id), pm_with_uri: narrow.pm_with_uri(sender_email), sent_by_uri: narrow.by_sender_uri(sender_email), - narrowed: narrow.active(), + narrowed: narrow_state.active(), historical: message.historical, private_message_class: "respond_personal_button", }; @@ -275,7 +275,7 @@ exports.toggle_actions_popover = function (element, id) { should_display_add_reaction_option: message.sent_by_me, should_display_edit_history_option: should_display_edit_history_option, conversation_time_uri: narrow.by_conversation_and_time_uri(message), - narrowed: narrow.active(), + narrowed: narrow_state.active(), }; var ypos = elt.offset().top; diff --git a/static/js/reload.js b/static/js/reload.js index 86e0411e47..f79d56f659 100644 --- a/static/js/reload.js +++ b/static/js/reload.js @@ -46,7 +46,7 @@ function preserve_state(send_after_reload, save_pointer, save_narrow, save_compo if (save_narrow) { var row = home_msg_list.selected_row(); - if (!narrow.active()) { + if (!narrow_state.active()) { if (row.length > 0) { url += "+offset=" + row.offset().top; } diff --git a/static/js/search.js b/static/js/search.js index 3e9cd6c6cf..eaa41327f6 100644 --- a/static/js/search.js +++ b/static/js/search.js @@ -25,7 +25,7 @@ function update_buttons_with_focus(focused) { // or we are narrowed. if (focused || search_query.val() - || narrow.active()) { + || narrow_state.active()) { $('.search_button').removeAttr('disabled'); } else { $('.search_button').attr('disabled', 'disabled'); @@ -121,7 +121,7 @@ exports.initialize = function () { // really it would be OK if they did). setTimeout(function () { - var search_string = narrow.search_string(); + var search_string = narrow_state.search_string(); query.val(search_string); exports.update_button_visibility(); }, 100); diff --git a/static/js/search_suggestion.js b/static/js/search_suggestion.js index fca8626157..562342dc57 100644 --- a/static/js/search_suggestion.js +++ b/static/js/search_suggestion.js @@ -311,7 +311,7 @@ function get_topic_suggestions(query_operators) { if (filter.has_operator('stream')) { stream = filter.operands('stream')[0]; } else { - stream = narrow.stream(); + stream = narrow_state.stream(); query_operators.push({operator: 'stream', operand: stream}); } break; diff --git a/static/js/shim.js b/static/js/shim.js deleted file mode 100644 index 8324b5b9b3..0000000000 --- a/static/js/shim.js +++ /dev/null @@ -1,11 +0,0 @@ -/* - -This module has shims to help us break circular dependencies. -We eventually want to to move the actual implementations into -new modules. When we do this, you may need to fix node tests -that still refer to the old name. -*/ - -var narrow_state = {}; // global, should be made into module -narrow_state.set_compose_defaults = narrow.set_compose_defaults; - diff --git a/static/js/stream_edit.js b/static/js/stream_edit.js index 4942e849c8..2a6edf491e 100644 --- a/static/js/stream_edit.js +++ b/static/js/stream_edit.js @@ -402,7 +402,7 @@ $(function () { e.preventDefault(); e.stopPropagation(); - var stream_name = narrow.stream(); + var stream_name = narrow_state.stream(); if (stream_name === undefined) { return; } diff --git a/static/js/stream_events.js b/static/js/stream_events.js index ae9d0f24a7..5fb7a477e7 100644 --- a/static/js/stream_events.js +++ b/static/js/stream_events.js @@ -99,7 +99,7 @@ exports.mark_subscribed = function (sub, subscribers, color) { subs.update_settings_for_subscribed(sub); - if (narrow.is_for_stream_id(sub.stream_id)) { + if (narrow_state.is_for_stream_id(sub.stream_id)) { current_msg_list.update_trailing_bookend(); } @@ -124,7 +124,7 @@ exports.mark_unsubscribed = function (sub) { return; } - if (narrow.is_for_stream_id(sub.stream_id)) { + if (narrow_state.is_for_stream_id(sub.stream_id)) { current_msg_list.update_trailing_bookend(); } diff --git a/static/js/stream_list.js b/static/js/stream_list.js index c6cd99c3ed..9e29b7ee5d 100644 --- a/static/js/stream_list.js +++ b/static/js/stream_list.js @@ -144,7 +144,7 @@ function zoom_in() { popovers.hide_all(); topic_list.zoom_in(); $("#streams_list").expectOne().removeClass("zoom-out").addClass("zoom-in"); - zoomed_stream = narrow.stream(); + zoomed_stream = narrow_state.stream(); // Hide stream list titles and pinned stream splitter $(".stream-filters-label").each(function () { @@ -296,11 +296,11 @@ function rebuild_recent_topics(stream) { exports.update_streams_sidebar = function () { exports.build_stream_list(); - if (! narrow.active()) { + if (! narrow_state.active()) { return; } - var op_stream = narrow.filter().operands('stream'); + var op_stream = narrow_state.filter().operands('stream'); if (op_stream.length !== 0) { if (stream_data.is_subscribed(op_stream[0])) { rebuild_recent_topics(op_stream[0]); @@ -369,7 +369,7 @@ $(function () { $(document).on('narrow_activated.zulip', function (event) { deselect_top_left_corner_items(); - reset_to_unnarrowed(narrow.stream() === zoomed_stream); + reset_to_unnarrowed(narrow_state.stream() === zoomed_stream); // TODO: handle confused filters like "in:all stream:foo" var op_in = event.filter.operands('in'); diff --git a/static/js/subs.js b/static/js/subs.js index 98a5e0f29f..d01c9c20a0 100644 --- a/static/js/subs.js +++ b/static/js/subs.js @@ -644,7 +644,7 @@ $(function () { e.preventDefault(); $('#subscription-status').hide(); - var stream_name = narrow.stream(); + var stream_name = narrow_state.stream(); if (stream_name === undefined) { return; } @@ -709,7 +709,7 @@ $(function () { }); function focus_on_narrowed_stream() { - var stream_name = narrow.stream(); + var stream_name = narrow_state.stream(); if (stream_name === undefined) { return; } diff --git a/static/js/tab_bar.js b/static/js/tab_bar.js index 76a2372d18..b875eb70f9 100644 --- a/static/js/tab_bar.js +++ b/static/js/tab_bar.js @@ -13,7 +13,7 @@ function make_tab(title, hash, data, extra_class, home) { function make_tab_data() { var tabs = []; - var filter = narrow.filter(); + var filter = narrow_state.filter(); // Root breadcrumb item: Either Home or All Messages if (filter !== undefined && @@ -34,9 +34,9 @@ function make_tab_data() { tabs.push(make_tab('Home', "#", "home", "root", true)); } - if (narrow.active() && narrow.operators().length > 0) { + if (narrow_state.active() && narrow_state.operators().length > 0) { var stream; - var ops = narrow.operators(); + var ops = narrow_state.operators(); // Second breadcrumb item var hashed = hashchange.operators_to_hash(ops.slice(0, 1)); if (filter.has_operator("stream")) { diff --git a/static/js/topic_list.js b/static/js/topic_list.js index 80a37817fd..89bfc95ff0 100644 --- a/static/js/topic_list.js +++ b/static/js/topic_list.js @@ -157,7 +157,7 @@ exports.build_widget = function (parent_elem, stream, active_topic, max_topics) exports.rebuild = function (stream_li, stream) { var max_topics = 5; - var active_topic = narrow.topic(); + var active_topic = narrow_state.topic(); exports.remove_expanded_topics(); active_widget = exports.build_widget(stream_li, stream, active_topic, max_topics); }; diff --git a/static/js/typing_events.js b/static/js/typing_events.js index bd051c12ca..6e133f1df1 100644 --- a/static/js/typing_events.js +++ b/static/js/typing_events.js @@ -15,13 +15,15 @@ var TYPING_STARTED_EXPIRY_PERIOD = 15000; // 15s // that make typing indicators work. function get_users_typing_for_narrow() { - if (!narrow.narrowed_to_pms()) { + if (!narrow_state.narrowed_to_pms()) { // Narrow is neither pm-with nor is: private return []; } - if (narrow.operators()[0].operator === 'pm-with') { + + var first_term = narrow_state.operators()[0]; + if (first_term.operator === 'pm-with') { // Get list of users typing in this conversation - var narrow_emails_string = narrow.operators()[0].operand; + var narrow_emails_string = first_term.operand; // TODO: Create people.emails_strings_to_user_ids. var narrow_user_ids_string = people.emails_strings_to_user_ids_string(narrow_emails_string); var narrow_user_ids = narrow_user_ids_string.split(',').map(function (user_id_string) { diff --git a/static/js/unread.js b/static/js/unread.js index 2e6cd3cc7f..5488f29bb1 100644 --- a/static/js/unread.js +++ b/static/js/unread.js @@ -251,7 +251,7 @@ exports.get_counts = function () { res.private_message_count = pm_count; res.home_unread_messages += pm_count; - if (narrow.active()) { + if (narrow_state.active()) { res.unread_in_current_view = exports.num_unread_current_messages(); } else { res.unread_in_current_view = res.home_unread_messages; diff --git a/static/js/user_events.js b/static/js/user_events.js index dd292f00df..f943e34013 100644 --- a/static/js/user_events.js +++ b/static/js/user_events.js @@ -20,7 +20,7 @@ exports.update_person = function update(person) { var user_id = person.user_id; var new_email = person.new_email; - narrow.update_email(user_id, new_email); + narrow_state.update_email(user_id, new_email); compose.update_email(user_id, new_email); if (people.is_my_user_id(person.user_id)) { diff --git a/tools/js-dep-visualizer.py b/tools/js-dep-visualizer.py index c08763da69..6555d20c39 100644 --- a/tools/js-dep-visualizer.py +++ b/tools/js-dep-visualizer.py @@ -140,6 +140,7 @@ def find_edges_to_remove(graph, methods): ('settings_notifications', 'stream_edit'), ('compose', 'stream_edit'), ('subs', 'stream_edit'), + ('narrow_state', 'stream_data'), ] # type: List[Edge] def is_exempt(edge): diff --git a/zproject/settings.py b/zproject/settings.py index d569066446..c0f216607b 100644 --- a/zproject/settings.py +++ b/zproject/settings.py @@ -854,6 +854,7 @@ JS_SPECS = { 'js/message_list_view.js', 'js/message_list.js', 'js/message_live_update.js', + 'js/narrow_state.js', 'js/narrow.js', 'js/reload.js', 'js/compose_fade.js', @@ -947,7 +948,6 @@ JS_SPECS = { 'js/typing_data.js', 'js/typing_events.js', 'js/ui_init.js', - 'js/shim.js', # JS bundled by webpack is also included here if PIPELINE_ENABLED setting is true ], 'output_filename': 'min/app.js'