Merge pull request #1243 from matrix-org/t3chguy/hide-join-part-2
T3chguy/hide join part (attempt) 2
This commit is contained in:
commit
c4f049effe
7 changed files with 119 additions and 55 deletions
|
@ -15,6 +15,8 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var MatrixClientPeg = require('./MatrixClientPeg');
|
var MatrixClientPeg = require('./MatrixClientPeg');
|
||||||
|
import UserSettingsStore from './UserSettingsStore';
|
||||||
|
import shouldHideEvent from './shouldHideEvent';
|
||||||
var sdk = require('./index');
|
var sdk = require('./index');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@ -63,6 +65,7 @@ module.exports = {
|
||||||
// we have and the read receipt. We could fetch more history to try & find out,
|
// we have and the read receipt. We could fetch more history to try & find out,
|
||||||
// but currently we just guess.
|
// but currently we just guess.
|
||||||
|
|
||||||
|
const syncedSettings = UserSettingsStore.getSyncedSettings();
|
||||||
// Loop through messages, starting with the most recent...
|
// Loop through messages, starting with the most recent...
|
||||||
for (var i = room.timeline.length - 1; i >= 0; --i) {
|
for (var i = room.timeline.length - 1; i >= 0; --i) {
|
||||||
var ev = room.timeline[i];
|
var ev = room.timeline[i];
|
||||||
|
@ -72,7 +75,7 @@ module.exports = {
|
||||||
// that counts and we can stop looking because the user's read
|
// that counts and we can stop looking because the user's read
|
||||||
// this and everything before.
|
// this and everything before.
|
||||||
return false;
|
return false;
|
||||||
} else if (this.eventTriggersUnreadCount(ev)) {
|
} else if (!shouldHideEvent(ev, syncedSettings) && this.eventTriggersUnreadCount(ev)) {
|
||||||
// We've found a message that counts before we hit
|
// We've found a message that counts before we hit
|
||||||
// the read marker, so this room is definitely unread.
|
// the read marker, so this room is definitely unread.
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -14,12 +14,14 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var React = require('react');
|
import React from 'react';
|
||||||
var ReactDOM = require("react-dom");
|
import ReactDOM from 'react-dom';
|
||||||
var dis = require("../../dispatcher");
|
import UserSettingsStore from '../../UserSettingsStore';
|
||||||
var sdk = require('../../index');
|
import shouldHideEvent from '../../shouldHideEvent';
|
||||||
|
import dis from "../../dispatcher";
|
||||||
|
import sdk from '../../index';
|
||||||
|
|
||||||
var MatrixClientPeg = require('../../MatrixClientPeg');
|
import MatrixClientPeg from '../../MatrixClientPeg';
|
||||||
|
|
||||||
const MILLIS_IN_DAY = 86400000;
|
const MILLIS_IN_DAY = 86400000;
|
||||||
|
|
||||||
|
@ -90,9 +92,6 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
// show timestamps always
|
// show timestamps always
|
||||||
alwaysShowTimestamps: React.PropTypes.bool,
|
alwaysShowTimestamps: React.PropTypes.bool,
|
||||||
|
|
||||||
// hide redacted events as per old behaviour
|
|
||||||
hideRedactions: React.PropTypes.bool,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
|
@ -113,6 +112,8 @@ module.exports = React.createClass({
|
||||||
// Velocity requires
|
// Velocity requires
|
||||||
this._readMarkerGhostNode = null;
|
this._readMarkerGhostNode = null;
|
||||||
|
|
||||||
|
this._syncedSettings = UserSettingsStore.getSyncedSettings();
|
||||||
|
|
||||||
this._isMounted = true;
|
this._isMounted = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -238,8 +239,20 @@ module.exports = React.createClass({
|
||||||
return !this._isMounted;
|
return !this._isMounted;
|
||||||
},
|
},
|
||||||
|
|
||||||
_getEventTiles: function() {
|
// TODO: Implement granular (per-room) hide options
|
||||||
|
_shouldShowEvent: function(mxEv) {
|
||||||
const EventTile = sdk.getComponent('rooms.EventTile');
|
const EventTile = sdk.getComponent('rooms.EventTile');
|
||||||
|
if (!EventTile.haveTileForEvent(mxEv)) {
|
||||||
|
return false; // no tile = no show
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always show highlighted event
|
||||||
|
if (this.props.highlightedEventId === mxEv.getId()) return true;
|
||||||
|
|
||||||
|
return !shouldHideEvent(mxEv, this._syncedSettings);
|
||||||
|
},
|
||||||
|
|
||||||
|
_getEventTiles: function() {
|
||||||
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
||||||
const MemberEventListSummary = sdk.getComponent('views.elements.MemberEventListSummary');
|
const MemberEventListSummary = sdk.getComponent('views.elements.MemberEventListSummary');
|
||||||
|
|
||||||
|
@ -249,20 +262,21 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
// first figure out which is the last event in the list which we're
|
// first figure out which is the last event in the list which we're
|
||||||
// actually going to show; this allows us to behave slightly
|
// actually going to show; this allows us to behave slightly
|
||||||
// differently for the last event in the list.
|
// differently for the last event in the list. (eg show timestamp)
|
||||||
//
|
//
|
||||||
// we also need to figure out which is the last event we show which isn't
|
// we also need to figure out which is the last event we show which isn't
|
||||||
// a local echo, to manage the read-marker.
|
// a local echo, to manage the read-marker.
|
||||||
var lastShownEventIndex = -1;
|
let lastShownEvent;
|
||||||
|
|
||||||
var lastShownNonLocalEchoIndex = -1;
|
var lastShownNonLocalEchoIndex = -1;
|
||||||
for (i = this.props.events.length-1; i >= 0; i--) {
|
for (i = this.props.events.length-1; i >= 0; i--) {
|
||||||
var mxEv = this.props.events[i];
|
var mxEv = this.props.events[i];
|
||||||
if (!EventTile.haveTileForEvent(mxEv)) {
|
if (!this._shouldShowEvent(mxEv)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lastShownEventIndex < 0) {
|
if (lastShownEvent === undefined) {
|
||||||
lastShownEventIndex = i;
|
lastShownEvent = mxEv;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mxEv.status) {
|
if (mxEv.status) {
|
||||||
|
@ -288,25 +302,18 @@ module.exports = React.createClass({
|
||||||
this.currentGhostEventId = null;
|
this.currentGhostEventId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var isMembershipChange = (e) => e.getType() === 'm.room.member';
|
const isMembershipChange = (e) => e.getType() === 'm.room.member';
|
||||||
|
|
||||||
for (i = 0; i < this.props.events.length; i++) {
|
for (i = 0; i < this.props.events.length; i++) {
|
||||||
let mxEv = this.props.events[i];
|
let mxEv = this.props.events[i];
|
||||||
let wantTile = true;
|
|
||||||
let eventId = mxEv.getId();
|
let eventId = mxEv.getId();
|
||||||
let readMarkerInMels = false;
|
let readMarkerInMels = false;
|
||||||
|
let last = (mxEv === lastShownEvent);
|
||||||
|
|
||||||
if (!EventTile.haveTileForEvent(mxEv)) {
|
const wantTile = this._shouldShowEvent(mxEv);
|
||||||
wantTile = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let last = (i == lastShownEventIndex);
|
|
||||||
|
|
||||||
// Wrap consecutive member events in a ListSummary, ignore if redacted
|
// Wrap consecutive member events in a ListSummary, ignore if redacted
|
||||||
if (isMembershipChange(mxEv) &&
|
if (isMembershipChange(mxEv) && wantTile) {
|
||||||
EventTile.haveTileForEvent(mxEv) &&
|
|
||||||
!mxEv.isRedacted()
|
|
||||||
) {
|
|
||||||
let ts1 = mxEv.getTs();
|
let ts1 = mxEv.getTs();
|
||||||
// Ensure that the key of the MemberEventListSummary does not change with new
|
// Ensure that the key of the MemberEventListSummary does not change with new
|
||||||
// member events. This will prevent it from being re-created unnecessarily, and
|
// member events. This will prevent it from being re-created unnecessarily, and
|
||||||
|
@ -325,35 +332,36 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
let summarisedEvents = [mxEv];
|
let summarisedEvents = [mxEv];
|
||||||
for (;i + 1 < this.props.events.length; i++) {
|
for (;i + 1 < this.props.events.length; i++) {
|
||||||
let collapsedMxEv = this.props.events[i + 1];
|
const collapsedMxEv = this.props.events[i + 1];
|
||||||
|
|
||||||
// Ignore redacted member events
|
|
||||||
if (!EventTile.haveTileForEvent(collapsedMxEv)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isMembershipChange(collapsedMxEv) ||
|
if (!isMembershipChange(collapsedMxEv) ||
|
||||||
this._wantsDateSeparator(this.props.events[i], collapsedMxEv.getDate())) {
|
this._wantsDateSeparator(this.props.events[i], collapsedMxEv.getDate())) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If RM event is in MELS mark it as such and the RM will be appended after MELS.
|
||||||
|
if (collapsedMxEv.getId() === this.props.readMarkerEventId) {
|
||||||
|
readMarkerInMels = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore redacted/hidden member events
|
||||||
|
if (!this._shouldShowEvent(collapsedMxEv)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
summarisedEvents.push(collapsedMxEv);
|
summarisedEvents.push(collapsedMxEv);
|
||||||
}
|
}
|
||||||
// At this point, i = the index of the last event in the summary sequence
|
|
||||||
|
|
||||||
let eventTiles = summarisedEvents.map(
|
// At this point, i = the index of the last event in the summary sequence
|
||||||
(e) => {
|
let eventTiles = summarisedEvents.map((e) => {
|
||||||
if (e.getId() === this.props.readMarkerEventId) {
|
// In order to prevent DateSeparators from appearing in the expanded form
|
||||||
readMarkerInMels = true;
|
// of MemberEventListSummary, render each member event as if the previous
|
||||||
}
|
// one was itself. This way, the timestamp of the previous event === the
|
||||||
// In order to prevent DateSeparators from appearing in the expanded form
|
// timestamp of the current event, and no DateSeperator is inserted.
|
||||||
// of MemberEventListSummary, render each member event as if the previous
|
const ret = this._getTilesForEvent(e, e, e === lastShownEvent);
|
||||||
// one was itself. This way, the timestamp of the previous event === the
|
prevEvent = e;
|
||||||
// timestamp of the current event, and no DateSeperator is inserted.
|
return ret;
|
||||||
let ret = this._getTilesForEvent(e, e);
|
}).reduce((a, b) => a.concat(b));
|
||||||
prevEvent = e;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
).reduce((a, b) => a.concat(b));
|
|
||||||
|
|
||||||
if (eventTiles.length === 0) {
|
if (eventTiles.length === 0) {
|
||||||
eventTiles = null;
|
eventTiles = null;
|
||||||
|
@ -466,8 +474,6 @@ module.exports = React.createClass({
|
||||||
continuation = false;
|
continuation = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mxEv.isRedacted() && this.props.hideRedactions) return ret;
|
|
||||||
|
|
||||||
var eventId = mxEv.getId();
|
var eventId = mxEv.getId();
|
||||||
var highlight = (eventId == this.props.highlightedEventId);
|
var highlight = (eventId == this.props.highlightedEventId);
|
||||||
|
|
||||||
|
|
|
@ -181,9 +181,6 @@ var TimelinePanel = React.createClass({
|
||||||
|
|
||||||
// always show timestamps on event tiles?
|
// always show timestamps on event tiles?
|
||||||
alwaysShowTimestamps: syncedSettings.alwaysShowTimestamps,
|
alwaysShowTimestamps: syncedSettings.alwaysShowTimestamps,
|
||||||
|
|
||||||
// hide redacted events as per old behaviour
|
|
||||||
hideRedactions: syncedSettings.hideRedactions,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1122,7 +1119,6 @@ var TimelinePanel = React.createClass({
|
||||||
return (
|
return (
|
||||||
<MessagePanel ref="messagePanel"
|
<MessagePanel ref="messagePanel"
|
||||||
hidden={ this.props.hidden }
|
hidden={ this.props.hidden }
|
||||||
hideRedactions={ this.state.hideRedactions }
|
|
||||||
backPaginating={ this.state.backPaginating }
|
backPaginating={ this.state.backPaginating }
|
||||||
forwardPaginating={ forwardPaginating }
|
forwardPaginating={ forwardPaginating }
|
||||||
events={ this.state.events }
|
events={ this.state.events }
|
||||||
|
|
|
@ -81,6 +81,10 @@ const SETTINGS_LABELS = [
|
||||||
id: 'showTwelveHourTimestamps',
|
id: 'showTwelveHourTimestamps',
|
||||||
label: 'Show timestamps in 12 hour format (e.g. 2:30pm)',
|
label: 'Show timestamps in 12 hour format (e.g. 2:30pm)',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'hideJoinLeaves',
|
||||||
|
label: 'Hide join/leave messages (invites/kicks/bans unaffected)',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'useCompactLayout',
|
id: 'useCompactLayout',
|
||||||
label: 'Use compact timeline layout',
|
label: 'Use compact timeline layout',
|
||||||
|
@ -1136,7 +1140,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
const threepidsSection = this.state.threepids.map((val, pidIndex) => {
|
const threepidsSection = this.state.threepids.map((val, pidIndex) => {
|
||||||
const id = "3pid-" + val.address;
|
const id = "3pid-" + val.address;
|
||||||
// TODO; make a separate component to avoid having to rebind onClick
|
// TODO: make a separate component to avoid having to rebind onClick
|
||||||
// each time we render
|
// each time we render
|
||||||
const onRemoveClick = (e) => this.onRemoveThreepidClicked(val);
|
const onRemoveClick = (e) => this.onRemoveThreepidClicked(val);
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -342,6 +342,7 @@
|
||||||
"had": "had",
|
"had": "had",
|
||||||
"Hangup": "Hangup",
|
"Hangup": "Hangup",
|
||||||
"Hide Apps": "Hide Apps",
|
"Hide Apps": "Hide Apps",
|
||||||
|
"Hide join/leave messages (invites/kicks/bans unaffected)": "Hide join/leave messages (invites/kicks/bans unaffected)",
|
||||||
"Hide read receipts": "Hide read receipts",
|
"Hide read receipts": "Hide read receipts",
|
||||||
"Hide Text Formatting Toolbar": "Hide Text Formatting Toolbar",
|
"Hide Text Formatting Toolbar": "Hide Text Formatting Toolbar",
|
||||||
"Historical": "Historical",
|
"Historical": "Historical",
|
||||||
|
|
51
src/shouldHideEvent.js
Normal file
51
src/shouldHideEvent.js
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 Michael Telatynski <7t3chguy@gmail.com>
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function _isLeaveOrJoin(ev) {
|
||||||
|
const isMemberEvent = ev.getType() === 'm.room.member' && ev.getStateKey() !== undefined;
|
||||||
|
if (!isMemberEvent) {
|
||||||
|
return false; // bail early: all the checks below concern member events only
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: These checks are done to make sure we're dealing with membership transitions not avatar changes / dupe joins
|
||||||
|
// These checks are also being done in TextForEvent and should really reside in the JS SDK as a helper function
|
||||||
|
const membership = ev.getContent().membership;
|
||||||
|
const prevMembership = ev.getPrevContent().membership;
|
||||||
|
if (membership === prevMembership && membership === 'join') {
|
||||||
|
// join -> join : This happens when display names change / avatars are set / genuine dupe joins with no changes.
|
||||||
|
// Find out which we're dealing with.
|
||||||
|
if (ev.getPrevContent().displayname !== ev.getContent().displayname) {
|
||||||
|
return false; // display name changed
|
||||||
|
}
|
||||||
|
if (ev.getPrevContent().avatar_url !== ev.getContent().avatar_url) {
|
||||||
|
return false; // avatar url changed
|
||||||
|
}
|
||||||
|
// dupe join event, fall through to hide rules
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// this only applies to joins/invited joins/leaves not invites/kicks/bans
|
||||||
|
const isJoin = membership === 'join' && prevMembership !== 'ban';
|
||||||
|
const isLeave = membership === 'leave' && ev.getStateKey() === ev.getSender();
|
||||||
|
return isJoin || isLeave;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function(ev, syncedSettings) {
|
||||||
|
// Hide redacted events
|
||||||
|
if (syncedSettings['hideRedactions'] && ev.isRedacted()) return true;
|
||||||
|
if (syncedSettings['hideJoinLeaves'] && _isLeaveOrJoin(ev)) return true;
|
||||||
|
return false;
|
||||||
|
}
|
|
@ -18,10 +18,12 @@ var React = require('react');
|
||||||
var ReactDOM = require("react-dom");
|
var ReactDOM = require("react-dom");
|
||||||
var TestUtils = require('react-addons-test-utils');
|
var TestUtils = require('react-addons-test-utils');
|
||||||
var expect = require('expect');
|
var expect = require('expect');
|
||||||
|
import sinon from 'sinon';
|
||||||
|
|
||||||
var sdk = require('matrix-react-sdk');
|
var sdk = require('matrix-react-sdk');
|
||||||
|
|
||||||
var MessagePanel = sdk.getComponent('structures.MessagePanel');
|
var MessagePanel = sdk.getComponent('structures.MessagePanel');
|
||||||
|
import UserSettingsStore from '../../../src/UserSettingsStore';
|
||||||
|
|
||||||
var test_utils = require('test-utils');
|
var test_utils = require('test-utils');
|
||||||
var mockclock = require('mock-clock');
|
var mockclock = require('mock-clock');
|
||||||
|
@ -54,9 +56,10 @@ describe('MessagePanel', function () {
|
||||||
test_utils.beforeEach(this);
|
test_utils.beforeEach(this);
|
||||||
client = test_utils.createTestClient();
|
client = test_utils.createTestClient();
|
||||||
client.credentials = {userId: '@me:here'};
|
client.credentials = {userId: '@me:here'};
|
||||||
|
UserSettingsStore.getSyncedSettings = sinon.stub().returns({});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function() {
|
||||||
clock.uninstall();
|
clock.uninstall();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue