diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js
index c04bec4b35..6cbf708252 100644
--- a/src/components/structures/MessagePanel.js
+++ b/src/components/structures/MessagePanel.js
@@ -281,7 +281,6 @@ module.exports = React.createClass({
var isMembershipChange = (e) =>
e.getType() === 'm.room.member'
- && ['join', 'leave'].indexOf(e.getContent().membership) !== -1
&& (!e.getPrevContent() || e.getContent().membership !== e.getPrevContent().membership);
for (i = 0; i < this.props.events.length; i++) {
diff --git a/src/components/views/elements/MemberEventListSummary.js b/src/components/views/elements/MemberEventListSummary.js
index 518439b1c7..ab4a89eb69 100644
--- a/src/components/views/elements/MemberEventListSummary.js
+++ b/src/components/views/elements/MemberEventListSummary.js
@@ -24,7 +24,7 @@ module.exports = React.createClass({
events: React.PropTypes.array.isRequired,
// An array of EventTiles to render when expanded
children: React.PropTypes.array.isRequired,
- // The maximum number of names to show in either the join or leave summaries
+ // The maximum number of names to show in either each summary e.g. 2 would result "A, B and 234 others left"
summaryLength: React.PropTypes.number,
// The maximum number of avatars to display in the summary
avatarsMaxLength: React.PropTypes.number,
@@ -40,7 +40,7 @@ module.exports = React.createClass({
getDefaultProps: function() {
return {
- summaryLength: 3,
+ summaryLength: 1,
threshold: 3,
avatarsMaxLength: 5,
};
@@ -52,88 +52,122 @@ module.exports = React.createClass({
});
},
- _getEventSenderName: function(ev) {
- if (!ev) {
- return 'undefined';
- }
- return ev.sender.name || ev.event.content.displayname || ev.getSender();
- },
-
- _renderNameList: function(events) {
- if (events.length === 0) {
+ _renderNameList: function(users) {
+ if (users.length === 0) {
return null;
}
- let originalNumber = events.length;
- events = events.slice(0, this.props.summaryLength);
- let lastEvent = events.pop();
+ let originalNumber = users.length;
- let names = events.map((ev) => {
- return this._getEventSenderName(ev);
- }).join(', ');
-
- let lastName = this._getEventSenderName(lastEvent);
- if (names.length === 0) {
- // special-case for a single event
- return lastName;
- }
+ users = users.slice(0, this.props.summaryLength);
let remaining = originalNumber - this.props.summaryLength;
- if (remaining > 0) {
- // name1, name2, name3, and 100 others
- return names + ', ' + lastName + ', and ' + remaining + ' others';
+ if (remaining < 0) {
+ remaining = 0;
+ }
+ let other = " other" + (remaining > 1 ? "s" : "");
+
+ return this._renderCommaSeparatedList(users, remaining) + (remaining ? ' and ' + remaining + other : '');
+ },
+
+ // Test whether the first n items repeat for the duration
+ // e.g. [1,2,3,4,1,2,3] would resolve true for n = 4
+ _isRepeatedSequence: function(transitions, n) {
+ let count = 0;
+ for (let i = 0; i < transitions.length; i++) {
+ if (transitions[i % n] !== transitions[i]) {
+ return null;
+ }
+ }
+ return true;
+ },
+
+ _renderCommaSeparatedList(items, disableAnd) {
+ if (disableAnd) {
+ return items.join(', ');
+ }
+ if (items.length === 0) {
+ return "";
+ } else if (items.length === 1) {
+ return items[0];
} else {
- // name1, name2 and name3
- return names + ' and ' + lastName;
+ let last = items.pop();
+ return items.join(', ') + ' and ' + last;
}
},
- _renderSummary: function(joinEvents, leaveEvents) {
- let joiners = this._renderNameList(joinEvents);
- let leavers = this._renderNameList(leaveEvents);
+ _getDescriptionForTransition(t, plural) {
+ let beConjugated = plural ? "were" : "was";
+ let invitation = plural ? "invitations" : "an invitation";
- let joinSummary = null;
- if (joiners) {
- joinSummary = (
-
- {joiners} joined the room
-
- );
- }
- let leaveSummary = null;
- if (leavers) {
- leaveSummary = (
-
- {leavers} left the room
-
- );
+ switch (t) {
+ case 'joined': return "joined";
+ case 'left': return "left";
+ case 'invite_reject': return "rejected " + invitation;
+ case 'invite_withdrawal': return "withdrew " + invitation;
+ case 'invited': return beConjugated + " invited";
+ case 'banned': return beConjugated + " banned";
+ case 'unbanned': return beConjugated + " unbanned";
+ case 'kicked': return beConjugated + " kicked";
}
- // The joinEvents and leaveEvents are representative of the net movement
- // per-user, and so it is possible that the total net movement is nil,
- // whilst there are some events in the expanded list. If the total net
- // movement is nil, then neither joinSummary nor leaveSummary will be
- // truthy, so return null.
- if (!joinSummary && !leaveSummary) {
+ return null;
+ },
+
+ _renderSummary: function(eventAggregates) {
+ let summaries = Object.keys(eventAggregates).map((transitions) => {
+ let nameList = this._renderNameList(eventAggregates[transitions]);
+
+ let repeats = 1;
+ let repeatExtra = 0;
+
+ let splitTransitions = transitions.split(',');
+ let describedTransitions = splitTransitions;
+ let plural = eventAggregates[transitions].length > 1;
+
+ for (let modulus = 1; modulus <= 2; modulus++) {
+ // Sequences that are repeating through modulus transitions will be truncated
+ if (this._isRepeatedSequence(describedTransitions, modulus)) {
+ // Extra repeating sequence on the end that should be treated separately
+ // so as to avoid j,l,j,l,j => "... joined and left 2.5 times"
+ repeatExtra = describedTransitions.length % modulus;
+
+ repeats = (describedTransitions.length - repeatExtra) / modulus;
+ describedTransitions = describedTransitions.slice(0, modulus);
+ break;
+ }
+ }
+
+ let numberOfTimes = repeats > 1 ? " " + repeats + " times" : "";
+
+ let descs = describedTransitions.map((t) => {
+ return this._getDescriptionForTransition(t, plural);
+ });
+
+ let afterRepeatDescs = splitTransitions.slice(splitTransitions.length - repeatExtra).map((t) => {
+ return this._getDescriptionForTransition(t, plural);
+ });
+
+ let desc = this._renderCommaSeparatedList(descs);
+ let afterRepeatDesc = this._renderCommaSeparatedList(afterRepeatDescs);
+
+ return nameList + " " + desc + numberOfTimes + (afterRepeatDesc ? " and then " + afterRepeatDesc : "");
+ });
+
+ if (!summaries) {
return null;
}
return (
- {joinSummary}{joinSummary && leaveSummary?'; ':''}
- {leaveSummary}.
+ {summaries.join(", ")}
);
},
- _renderAvatars: function(events) {
- let avatars = events.slice(0, this.props.avatarsMaxLength).map((e) => {
+ _renderAvatars: function(roomMembers) {
+ let avatars = roomMembers.slice(0, this.props.avatarsMaxLength).map((m) => {
return (
-