diff --git a/lib/filter-component.js b/lib/filter-component.js index 6c1e289f6..98f8fd7d7 100644 --- a/lib/filter-component.js +++ b/lib/filter-component.js @@ -18,6 +18,13 @@ limitations under the License. * @module filter-component */ +/** + * Checks if a value matches a given field value, which may be a * terminated + * wildcard pattern. + * @param {String} actual_value The value to be compared + * @param {String} filter_value The filter pattern to be compared + * @return {bool} true if the actual_value matches the filter_value + */ function _matches_wildcard(actual_value, filter_value) { if (filter_value.endsWith("*")) { var type_prefix = filter_value.slice(0, -1); @@ -29,10 +36,15 @@ function _matches_wildcard(actual_value, filter_value) { } /** - * A FilterComponent is a section of a Filter definition which defines the + * FilterComponent is a section of a Filter definition which defines the * types, rooms, senders filters etc to be applied to a particular type of resource. + * This is all ported over from synapse's Filter object. * - * This is all ported from synapse's Filter object. + * N.B. that synapse refers to these as 'Filters', and what js-sdk refers to as + * 'Filters' are referred to as 'FilterCollections'. + * + * @constructor + * @param {Object} the definition of this filter JSON, e.g. { 'contains_url': true } */ function FilterComponent(filter_json) { this.filter_json = filter_json; @@ -51,11 +63,11 @@ function FilterComponent(filter_json) { /** * Checks with the filter component matches the given event - * - * Takes a MatrixEvent object + * @param {MatrixEvent} event event to be checked against the filter + * @return {bool} true if the event matches the filter */ FilterComponent.prototype.check = function(event) { - return this.checkFields( + return this._checkFields( event.getRoomId(), event.getSender(), event.getType(), @@ -64,9 +76,14 @@ FilterComponent.prototype.check = function(event) { }; /** - * Checks whether the filter matches the given event fields. + * Checks whether the filter component matches the given event fields. + * @param {String} room_id the room_id for the event being checked + * @param {String} sender the sender of the event being checked + * @param {String} event_type the type of the event being checked + * @param {String} contains_url whether the event contains a content.url field + * @return {bool} true if the event fields match the filter */ -FilterComponent.prototype.checkFields = +FilterComponent.prototype._checkFields = function(room_id, sender, event_type, contains_url) { var literal_keys = { @@ -102,10 +119,20 @@ FilterComponent.prototype.checkFields = return true; }; +/** + * Filters a list of events down to those which match this filter component + * @param {MatrixEvent[]} events Events to be checked againt the filter component + * @return {MatrixEvent[]} events which matched the filter component + */ FilterComponent.prototype.filter = function(events) { return events.filter(this.check, this); }; +/** + * Returns the limit field for a given filter component, providing a default of + * 10 if none is otherwise specified. Cargo-culted from Synapse. + * @return {Number} the limit for this filter component. + */ FilterComponent.prototype.limit = function() { return this.filter_json.limit !== undefined ? this.filter_json.limit : 10; }; diff --git a/lib/filter.js b/lib/filter.js index d1d31a0bc..1aa892960 100644 --- a/lib/filter.js +++ b/lib/filter.js @@ -127,11 +127,16 @@ Filter.prototype.setDefinition = function(definition) { ); // don't bother porting this from synapse yet: - // this._room_state_filter = new FilterComponent(room_filter_json.state || {}); - // this._room_ephemeral_filter = new FilterComponent(room_filter_json.ephemeral || {}); - // this._room_account_data_filter = new FilterComponent(room_filter_json.account_data || {}); - // this._presence_filter = new FilterComponent(definition.presence || {}); - // this._account_data_filter = new FilterComponent(definition.account_data || {}); + // this._room_state_filter = + // new FilterComponent(room_filter_json.state || {}); + // this._room_ephemeral_filter = + // new FilterComponent(room_filter_json.ephemeral || {}); + // this._room_account_data_filter = + // new FilterComponent(room_filter_json.account_data || {}); + // this._presence_filter = + // new FilterComponent(definition.presence || {}); + // this._account_data_filter = + // new FilterComponent(definition.account_data || {}); }; /** @@ -145,6 +150,8 @@ Filter.prototype.getRoomTimelineFilterComponent = function() { /** * Filter the list of events based on whether they are allowed in a timeline * based on this filter + * @param {MatrixEvent[]} events the list of events being filtered + * @return {MatrixEvent[]} the list of events which match the filter */ Filter.prototype.filterRoomTimeline = function(events) { return this._room_timeline_filter.filter(this._room_filter.filter(events)); diff --git a/lib/models/event-timeline-set.js b/lib/models/event-timeline-set.js index 5cdec4c21..abc1d9386 100644 --- a/lib/models/event-timeline-set.js +++ b/lib/models/event-timeline-set.js @@ -41,8 +41,7 @@ if (DEBUG) { * be continuous. Each timeline lists a series of events, as well as tracking * the room state at the start and the end of the timeline (if appropriate). * It also tracks forward and backward pagination tokens, as well as containing - * links to the - * next timeline in the sequence. + * links to the next timeline in the sequence. * *

There is one special timeline - the 'live' timeline, which represents the * timeline to which events are being added in real-time as they are received @@ -52,6 +51,13 @@ if (DEBUG) { * *

In order that we can find events from their ids later, we also maintain a * map from event_id to timeline and index. + * + * @constructor + * @param {?String} roomId the roomId of this timelineSet's room, if any + * @param {?Room} room the optional room for this timelineSet + * @param {Object} opts hash of options inherited from Room. + * opts.timelineSupport gives whether timeline support is enabled + * opts.filter is the filter object, if any, for this timelineSet. */ function EventTimelineSet(roomId, room, opts) { this.roomId = roomId; @@ -69,15 +75,17 @@ function EventTimelineSet(roomId, room, opts) { utils.inherits(EventTimelineSet, EventEmitter); /** - * Get the filter object this timeline list is filtered on + * Get the filter object this timeline set is filtered on, if any + * @return {?Filter} the optional filter for this timelineSet */ EventTimelineSet.prototype.getFilter = function() { return this._filter; }; /** - * Set the filter object this timeline list is filtered on + * Set the filter object this timeline set is filtered on * (passed to the server when paginating via /messages). + * @param {Filter} filter the filter for this timelineSet */ EventTimelineSet.prototype.setFilter = function(filter) { this._filter = filter; @@ -93,7 +101,9 @@ EventTimelineSet.prototype.setFilter = function(filter) { * @throws If opts.pendingEventOrdering was not 'detached' */ EventTimelineSet.prototype.getPendingEvents = function() { - if (!this.room) return []; + if (!this.room) { + return []; + } if (this._filter) { return this._filter.filterRoomTimeline(this.room.getPendingEvents()); @@ -108,14 +118,25 @@ EventTimelineSet.prototype.getPendingEvents = function() { * * @return {module:models/event-timeline~EventTimeline} live timeline */ -EventTimelineSet.prototype.getLiveTimeline = function(filterId) { +EventTimelineSet.prototype.getLiveTimeline = function() { return this._liveTimeline; }; +/** + * Return the timeline (if any) this event is in. + * @param {String} eventId the eventId being sought + * @return {module:models/event-timeline~EventTimeline} timeline + */ EventTimelineSet.prototype.eventIdToTimeline = function(eventId) { return this._eventIdToTimeline[eventId]; }; +/** + * Track a new event as if it were in the same timeline as an old event, + * replacing it. + * @param {String} oldEventId event ID of the original event + * @param {String} newEventId event ID of the replacement event + */ EventTimelineSet.prototype.replaceEventId = function(oldEventId, newEventId) { var existingTimeline = this._eventIdToTimeline[oldEventId]; if (existingTimeline) { @@ -387,7 +408,10 @@ EventTimelineSet.prototype.addEventsToTimeline = function(events, toStartOfTimel }; /** - * Add event to the live timeline + * Add an event to the end of this live timeline. + * + * @param {MatrixEvent} event Event to be added + * @param {string?} duplicateStrategy 'ignore' or 'replace' */ EventTimelineSet.prototype.addLiveEvent = function(event, duplicateStrategy) { if (this._filter) { @@ -441,10 +465,9 @@ EventTimelineSet.prototype.addLiveEvent = function(event, duplicateStrategy) { * @param {boolean} toStartOfTimeline * * @fires module:client~MatrixClient#event:"Room.timeline" - * - * @private */ -EventTimelineSet.prototype.addEventToTimeline = function(event, timeline, toStartOfTimeline) { +EventTimelineSet.prototype.addEventToTimeline = function(event, timeline, + toStartOfTimeline) { var eventId = event.getId(); timeline.addEvent(event, toStartOfTimeline); this._eventIdToTimeline[eventId] = timeline; @@ -454,10 +477,23 @@ EventTimelineSet.prototype.addEventToTimeline = function(event, timeline, toStar liveEvent: !toStartOfTimeline && timeline == this._liveTimeline, timelineSet: this, }; - this.emit("Room.timeline", event, this.room, Boolean(toStartOfTimeline), false, data); + this.emit("Room.timeline", event, this.room, + Boolean(toStartOfTimeline), false, data); }; -EventTimelineSet.prototype.replaceOrAddEvent = function(localEvent, oldEventId, newEventId) { +/** + * Replaces event with ID oldEventId with one with newEventId, if oldEventId is + * recognised. Otherwise, add to the live timeline. + * + * @param {MatrixEvent} localEvent the new event to be added to the timeline + * @param {String} oldEventId the ID of the original event + * @param {boolean} newEventId the ID of the replacement event + * + * @fires module:client~MatrixClient#event:"Room.timeline" + */ +EventTimelineSet.prototype.replaceOrAddEvent = function(localEvent, oldEventId, + newEventId) { + // XXX: why don't we infer newEventId from localEvent? var existingTimeline = this._eventIdToTimeline[oldEventId]; if (existingTimeline) { delete this._eventIdToTimeline[oldEventId]; diff --git a/lib/models/event-timeline.js b/lib/models/event-timeline.js index 3a12f26db..b885e0469 100644 --- a/lib/models/event-timeline.js +++ b/lib/models/event-timeline.js @@ -94,7 +94,7 @@ EventTimeline.prototype.getRoomId = function() { /** * Get the filter for this timeline's timelineSet (if any) - * @return {Filter}} filter + * @return {Filter} filter */ EventTimeline.prototype.getFilter = function() { return this._eventTimelineSet.getFilter(); @@ -262,6 +262,10 @@ EventTimeline.prototype.addEvent = function(event, atStart) { /** * Static helper method to set sender and target properties + * + * @param {MatrixEvent} event the event whose metadata is to be set + * @param {RoomState} stateContext the room state to be queried + * @param {bool} toStartOfTimeline if true the event's forwardLooking flag is set false */ EventTimeline.setEventMetadata = function(event, stateContext, toStartOfTimeline) { // set sender and target properties diff --git a/lib/models/room.js b/lib/models/room.js index ee6b07433..467e26d83 100644 --- a/lib/models/room.js +++ b/lib/models/room.js @@ -151,8 +151,8 @@ function Room(roomId, opts) { // all our per-room timeline lists. the first one is the unfiltered ones; // the subsequent ones are the filtered ones in no particular order. - this._timelineSets = [ new EventTimelineSet(roomId, this, opts) ]; - reEmit(this, this._timelineSets[0], [ "Room.timeline" ]); + this._timelineSets = [new EventTimelineSet(roomId, this, opts)]; + reEmit(this, this._timelineSets[0], ["Room.timeline"]); this._fixUpLegacyTimelineFields(); @@ -193,7 +193,7 @@ Room.prototype.getPendingEvents = function() { * * @return {module:models/event-timeline~EventTimeline} live timeline */ -Room.prototype.getLiveTimeline = function(filterId) { +Room.prototype.getLiveTimeline = function() { return this._timelineSets[0].getLiveTimeline(); }; @@ -227,19 +227,23 @@ Room.prototype._fixUpLegacyTimelineFields = function() { // state at the start and end of that timeline. These are more // for backwards-compatibility than anything else. this.timeline = this._timelineSets[0].getLiveTimeline().getEvents(); - this.oldState = this._timelineSets[0].getLiveTimeline().getState(EventTimeline.BACKWARDS); - this.currentState = this._timelineSets[0].getLiveTimeline().getState(EventTimeline.FORWARDS); + this.oldState = this._timelineSets[0].getLiveTimeline() + .getState(EventTimeline.BACKWARDS); + this.currentState = this._timelineSets[0].getLiveTimeline() + .getState(EventTimeline.FORWARDS); }; /** - * Return the timeline sets for this room + * Return the timeline sets for this room. + * @return {EventTimelineSet[]} array of timeline sets for this room */ Room.prototype.getTimelineSets = function() { return this._timelineSets; }; /** - * Return the notification timeline set for this room + * Return the shared notification timeline set + * @return {EventTimelineSet} notification timeline set */ Room.prototype.getNotifTimelineSet = function() { return this._notifTimelineSet; @@ -252,7 +256,6 @@ Room.prototype.getNotifTimelineSet = function() { * @return {?module:models/event-timeline~EventTimeline} timeline containing * the given event, or null if unknown */ - Room.prototype.getTimelineForEvent = function(eventId) { return this._timelineSets[0].getTimelineForEvent(eventId); }; @@ -466,7 +469,7 @@ Room.prototype.getOrCreateFilteredTimelineSet = function(filter) { } var opts = Object.assign({ filter: filter }, this._opts); var timelineSet = new EventTimelineSet(this.roomId, this, opts); - reEmit(this, timelineSet, [ "Room.timeline" ]); + reEmit(this, timelineSet, ["Room.timeline"]); this._filteredTimelineSets[filter.filterId] = timelineSet; this._timelineSets.push(timelineSet); @@ -501,6 +504,8 @@ Room.prototype.getOrCreateFilteredTimelineSet = function(filter) { /** * Forget the timelineSet for this room with the given filter + * + * @param {Filter} filter the filter whose timelineSet is to be forgotten */ Room.prototype.removeFilteredTimelineSet = function(filter) { var timelineSet = this._filteredTimelineSets[filter.filterId]; @@ -634,14 +639,17 @@ Room.prototype.addPendingEvent = function(event, txnId) { var timelineSet = this._timelineSets[i]; if (timelineSet.getFilter()) { if (this._filter.filterRoomTimeline([event]).length) { - timelineSet.addEventToTimeline(event, timelineSet.getLiveTimeline(), false); + timelineSet.addEventToTimeline(event, + timelineSet.getLiveTimeline(), false); } } else { - timelineSet.addEventToTimeline(event, timelineSet.getLiveTimeline(), false); + timelineSet.addEventToTimeline(event, + timelineSet.getLiveTimeline(), false); } } - // notifications are receive-only, so we don't need to worry about this._notifTimelineSet. + // notifications are receive-only, so we don't need to worry + // about this._notifTimelineSet. } this.emit("Room.localEchoUpdated", event, this, null, null); @@ -823,13 +831,14 @@ Room.prototype.addLiveEvents = function(events, duplicateStrategy) { var liveTimeline = this._timelineSets[i].getLiveTimeline(); if (liveTimeline.getPaginationToken(EventTimeline.FORWARDS)) { throw new Error( - "live timeline "+i+" is no longer live - it has a pagination token (" + - liveTimeline.getPaginationToken(EventTimeline.FORWARDS) + ")" + "live timeline " + i + " is no longer live - it has a pagination token " + + "(" + liveTimeline.getPaginationToken(EventTimeline.FORWARDS) + ")" ); } if (liveTimeline.getNeighbouringTimeline(EventTimeline.FORWARDS)) { throw new Error( - "live timeline "+i+" is no longer live - it has a neighbouring timeline" + "live timeline " + i + " is no longer live - " + + "it has a neighbouring timeline" ); } } diff --git a/spec/unit/event-timeline.spec.js b/spec/unit/event-timeline.spec.js index 9a7aba2ae..25b748648 100644 --- a/spec/unit/event-timeline.spec.js +++ b/spec/unit/event-timeline.spec.js @@ -16,7 +16,7 @@ describe("EventTimeline", function() { beforeEach(function() { utils.beforeEach(this); - timeline = new EventTimeline({ roomId : roomId }); + timeline = new EventTimeline({ roomId: roomId }); }); describe("construction", function() { diff --git a/spec/unit/timeline-window.spec.js b/spec/unit/timeline-window.spec.js index f33e9451c..ce87f13c0 100644 --- a/spec/unit/timeline-window.spec.js +++ b/spec/unit/timeline-window.spec.js @@ -18,7 +18,7 @@ function createTimeline(numEvents, baseIndex) { if (numEvents === undefined) { numEvents = 3; } if (baseIndex === undefined) { baseIndex = 1; } - var timeline = new EventTimeline({ roomId : ROOM_ID }); + var timeline = new EventTimeline({ roomId: ROOM_ID }); // add the events after the baseIndex first addEventsToTimeline(timeline, numEvents - baseIndex, false);