diff --git a/static/js/message_list.js b/static/js/message_list.js index 9d482045b7..e9438fce33 100644 --- a/static/js/message_list.js +++ b/static/js/message_list.js @@ -10,7 +10,24 @@ import {page_params} from "./page_params"; import * as stream_data from "./stream_data"; export class MessageList { + // A MessageList is the main interface for a message feed that is + // rendered in the DOM. Code outside the message feed rendering + // internals will directly call this module in order to manipulate + // a message feed. + // + // Each MessageList has an associated MessageListData, which + // manages the messages, and a MessageListView, which manages the + // the templates/HTML rendering as well as invisible pagination. + // + // TODO: The abstraction boundary between this and MessageListView + // is not particularly well-defined; it could be nice to figure + // out a good rule. constructor(opts) { + // The MessageListData keeps track of the actual sequence of + // messages displayed by this MessageList. Most + // configuration/logic questions in this module will be + // answered by calling a function from the MessageListData, + // its Filter, or its FetchStatus object. if (opts.data) { this.data = opts.data; } else { @@ -22,12 +39,39 @@ export class MessageList { }); } - const collapse_messages = this.data.filter.supports_collapsing_recipients(); + // The table_name is the outer HTML element for this message + // list in the DOM. const table_name = opts.table_name; - this.view = new MessageListView(this, table_name, collapse_messages); this.table_name = table_name; + + // TODO: This property should likely just be inlined into + // having the MessageListView code that needs to access it + // query .data.filter directly. + const collapse_messages = this.data.filter.supports_collapsing_recipients(); + + // The MessageListView object that is responsible for + // maintaining this message feed's HTML representation in the + // DOM. + this.view = new MessageListView(this, table_name, collapse_messages); + + // Whether this is a narrowed message list. The only message + // list that is not is the home_msg_list global. + // + // TODO: It would probably be more readable to replace this + // with another property with an inverted meaning, since + // home_msg_list is the message list that is special/unique. this.narrowed = this.table_name === "zfilt"; + + // TODO: This appears to be unused and can be deleted. this.num_appends = 0; + + // Keeps track of whether the user has done a UI interaction, + // such as "Mark as unread", that should disable marking + // messages as read until prevent_reading is called again. + // + // Distinct from filter.can_mark_messages_read(), which is a + // property of the type of narrow, regardless of actions by + // the user. Possibly this can be unified in some nice way. this.reading_prevented = false; return this; diff --git a/static/js/message_list_data.js b/static/js/message_list_data.js index 9713928839..3242851b2a 100644 --- a/static/js/message_list_data.js +++ b/static/js/message_list_data.js @@ -8,16 +8,51 @@ import * as user_topics from "./user_topics"; import * as util from "./util"; export class MessageListData { + // MessageListData is a core data structure for keeping track of a + // contiguous block of messages matching a given narrow that can + // be displayed in a Zulip message feed. + // + // See also MessageList and MessageListView, which are important + // to actually display a message list. + constructor({excludes_muted_topics, filter = new Filter()}) { - this.excludes_muted_topics = excludes_muted_topics; + // The Filter object defines which messages match the narrow, + // and defines most of the configuration for the MessageListData. + this.filter = filter; + + // The FetchStatus object keeps track of our understanding of + // to what extent this MessageListData has all the messages + // that the server possesses matching this narrow, and whether + // we're in the progress of fetching more. + this.fetch_status = new FetchStatus(); + + // _all_items is a sorted list of all message objects that + // match this.filter, regardless of muting. + // + // Most code will instead use _items, which contains contains + // only messages that should be displayed after excluding + // muted topics and messages sent by muted users. this._all_items = []; this._items = []; - this._hash = new Map(); - this._local_only = new Set(); - this._selected_id = -1; - this.filter = filter; - this.fetch_status = new FetchStatus(); + // _hash contains the same messages as _all_items, mapped by + // message ID. It's used to efficiently query if a given + // message is present. + this._hash = new Map(); + + // Some views exclude muted topics. + // + // TODO: Refactor this to be a property of Filter, rather than + // a parameter that needs to be passed into the constructor.. + this.excludes_muted_topics = excludes_muted_topics; + + // Tracks any locally echoed messages, which we know aren't present on the server. + this._local_only = new Set(); + + // The currently selected message ID. The special value -1 + // there is no selected message. A common situation is when + // there are no messages matching the current filter. + this._selected_id = -1; } all_messages() { diff --git a/static/js/message_list_view.js b/static/js/message_list_view.js index cb35e6d089..01248d0d90 100644 --- a/static/js/message_list_view.js +++ b/static/js/message_list_view.js @@ -224,18 +224,51 @@ function populate_group_from_message_container(group, message_container) { } export class MessageListView { + // MessageListView is the module responsible for rendering a + // MessageList into the DOM, and maintaining it over time. + // + // Logic to compute context, render templates, insert them into + // the DOM, and generally + constructor(list, table_name, collapse_messages) { + // The MessageList that this MessageListView is responsible for rendering. this.list = list; + + // TODO: Access this via .list.data. this.collapse_messages = collapse_messages; + + // These three data structures keep track of groups of messages in the DOM. + // + // The message_groups are blocks of messages rendered into the + // DOM that will share a common recipent bar heading. + // + // A message_container an object containing a Message object + // plus additional computed metadata needed for rendering it + // in the DOM. + // + // _rows contains jQuery objects for the `message_row` + // elements rendered by single_message.hbs. + // + // TODO: Consider renaming _message_groups to something like _recipient_groups. + // TODO: Consider renaming _rows to something like $rows. this._rows = new Map(); this.message_containers = new Map(); + this._message_groups = []; + + // TODO: Should this be just accessing .list.table_name? this.table_name = table_name; if (this.table_name) { this.clear_table(); } - this._message_groups = []; - // Half-open interval of the indices that define the current render window + // For performance reasons, this module renders at most + // _RENDER_WINDOW_SIZE messages into the DOM at a time, and + // will transparently adjust which messages are rendered + // whenever the user scrolls within _RENDER_THRESHOLD of the + // edge of the rendered window. + // + // These two values are a half-open interval keeping track of + // what range of messages is currently rendered in the dOM. this._render_win_start = 0; this._render_win_end = 0; }