Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 6a53b7b

Browse files
authored
Merge pull request #1389 from turt2live/travis/ignored_users
Add ignore user API support
2 parents bc565ba + 2605004 commit 6a53b7b

File tree

12 files changed

+217
-2
lines changed

12 files changed

+217
-2
lines changed

src/SlashCommands.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,59 @@ const commands = {
240240
return reject(this.getUsage());
241241
}),
242242

243+
ignore: new Command("ignore", "<userId>", function(roomId, args) {
244+
if (args) {
245+
const matches = args.match(/^(\S+)$/);
246+
if (matches) {
247+
const userId = matches[1];
248+
const ignoredUsers = MatrixClientPeg.get().getIgnoredUsers();
249+
ignoredUsers.push(userId); // de-duped internally in the js-sdk
250+
return success(
251+
MatrixClientPeg.get().setIgnoredUsers(ignoredUsers).then(() => {
252+
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
253+
Modal.createTrackedDialog('Slash Commands', 'User ignored', QuestionDialog, {
254+
title: _t("Ignored user"),
255+
description: (
256+
<div>
257+
<p>{_t("You are now ignoring %(userId)s", {userId: userId})}</p>
258+
</div>
259+
),
260+
hasCancelButton: false,
261+
});
262+
}),
263+
);
264+
}
265+
}
266+
return reject(this.getUsage());
267+
}),
268+
269+
unignore: new Command("unignore", "<userId>", function(roomId, args) {
270+
if (args) {
271+
const matches = args.match(/^(\S+)$/);
272+
if (matches) {
273+
const userId = matches[1];
274+
const ignoredUsers = MatrixClientPeg.get().getIgnoredUsers();
275+
const index = ignoredUsers.indexOf(userId);
276+
if (index !== -1) ignoredUsers.splice(index, 1);
277+
return success(
278+
MatrixClientPeg.get().setIgnoredUsers(ignoredUsers).then(() => {
279+
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
280+
Modal.createTrackedDialog('Slash Commands', 'User unignored', QuestionDialog, {
281+
title: _t("Unignored user"),
282+
description: (
283+
<div>
284+
<p>{_t("You are no longer ignoring %(userId)s", {userId: userId})}</p>
285+
</div>
286+
),
287+
hasCancelButton: false,
288+
});
289+
}),
290+
);
291+
}
292+
}
293+
return reject(this.getUsage());
294+
}),
295+
243296
// Define the power level of a user
244297
op: new Command("op", "<userId> [<power level>]", function(roomId, args) {
245298
if (args) {

src/WhoIsTyping.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ var MatrixClientPeg = require("./MatrixClientPeg");
1818
import { _t } from './languageHandler';
1919

2020
module.exports = {
21+
usersTypingApartFromMeAndIgnored: function(room) {
22+
return this.usersTyping(
23+
room, [MatrixClientPeg.get().credentials.userId].concat(MatrixClientPeg.get().getIgnoredUsers())
24+
);
25+
},
26+
2127
usersTypingApartFromMe: function(room) {
2228
return this.usersTyping(
2329
room, [MatrixClientPeg.get().credentials.userId]

src/autocomplete/CommandProvider.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,16 @@ const COMMANDS = [
9494
args: '<user-id> <device-id> <device-signing-key>',
9595
description: 'Verifies a user, device, and pubkey tuple',
9696
},
97+
{
98+
command: '/ignore',
99+
args: '<user-id>',
100+
description: 'Ignores a user, hiding their messages from you',
101+
},
102+
{
103+
command: '/unignore',
104+
args: '<user-id>',
105+
description: 'Stops ignoring a user, showing their messages going forward',
106+
},
97107
// Omitting `/markdown` as it only seems to apply to OldComposer
98108
];
99109

src/components/structures/LoggedInView.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ export default React.createClass({
131131
useCompactLayout: event.getContent().useCompactLayout,
132132
});
133133
}
134+
if (event.getType() === "m.ignored_user_list") {
135+
dis.dispatch({action: "ignore_state_changed"});
136+
}
134137
},
135138

136139
_onKeyDown: function(ev) {

src/components/structures/MessagePanel.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,10 @@ module.exports = React.createClass({
241241

242242
// TODO: Implement granular (per-room) hide options
243243
_shouldShowEvent: function(mxEv) {
244+
if (mxEv.sender && MatrixClientPeg.get().isUserIgnored(mxEv.sender.userId)) {
245+
return false; // ignored = no show (only happens if the ignore happens after an event was received)
246+
}
247+
244248
const EventTile = sdk.getComponent('rooms.EventTile');
245249
if (!EventTile.haveTileForEvent(mxEv)) {
246250
return false; // no tile = no show
@@ -549,6 +553,9 @@ module.exports = React.createClass({
549553
if (!r.userId || r.type !== "m.read" || r.userId === myUserId) {
550554
return; // ignore non-read receipts and receipts from self.
551555
}
556+
if (MatrixClientPeg.get().isUserIgnored(r.userId)) {
557+
return; // ignore ignored users
558+
}
552559
let member = room.getMember(r.userId);
553560
if (!member) {
554561
return; // ignore unknown user IDs

src/components/structures/RoomStatusBar.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ module.exports = React.createClass({
121121

122122
onRoomMemberTyping: function(ev, member) {
123123
this.setState({
124-
usersTyping: WhoIsTyping.usersTypingApartFromMe(this.props.room),
124+
usersTyping: WhoIsTyping.usersTypingApartFromMeAndIgnored(this.props.room),
125125
});
126126
},
127127

src/components/structures/TimelinePanel.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,9 @@ var TimelinePanel = React.createClass({
384384
this.sendReadReceipt();
385385
this.updateReadMarker();
386386
break;
387+
case 'ignore_state_changed':
388+
this.forceUpdate();
389+
break;
387390
}
388391
},
389392

src/components/structures/UserSettings.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,34 @@ const THEMES = [
176176
},
177177
];
178178

179+
const IgnoredUser = React.createClass({
180+
propTypes: {
181+
userId: React.PropTypes.string.isRequired,
182+
onUnignored: React.PropTypes.func.isRequired,
183+
},
184+
185+
_onUnignoreClick: function() {
186+
const ignoredUsers = MatrixClientPeg.get().getIgnoredUsers();
187+
const index = ignoredUsers.indexOf(this.props.userId);
188+
if (index !== -1) {
189+
ignoredUsers.splice(index, 1);
190+
MatrixClientPeg.get().setIgnoredUsers(ignoredUsers)
191+
.then(() => this.props.onUnignored(this.props.userId));
192+
} else this.props.onUnignored(this.props.userId);
193+
},
194+
195+
render: function() {
196+
return (
197+
<li>
198+
<AccessibleButton onClick={this._onUnignoreClick} className="mx_UserSettings_button mx_UserSettings_buttonSmall">
199+
{ _t("Unignore") }
200+
</AccessibleButton>
201+
{ this.props.userId }
202+
</li>
203+
);
204+
},
205+
});
206+
179207
module.exports = React.createClass({
180208
displayName: 'UserSettings',
181209

@@ -211,6 +239,7 @@ module.exports = React.createClass({
211239
vectorVersion: undefined,
212240
rejectingInvites: false,
213241
mediaDevices: null,
242+
ignoredUsers: [],
214243
};
215244
},
216245

@@ -232,6 +261,7 @@ module.exports = React.createClass({
232261
}
233262

234263
this._refreshMediaDevices();
264+
this._refreshIgnoredUsers();
235265

236266
// Bulk rejecting invites:
237267
// /sync won't have had time to return when UserSettings re-renders from state changes, so getRooms()
@@ -350,9 +380,22 @@ module.exports = React.createClass({
350380
});
351381
},
352382

383+
_refreshIgnoredUsers: function(userIdUnignored=null) {
384+
const users = MatrixClientPeg.get().getIgnoredUsers();
385+
if (userIdUnignored) {
386+
const index = users.indexOf(userIdUnignored);
387+
if (index !== -1) users.splice(index, 1);
388+
}
389+
this.setState({
390+
ignoredUsers: users,
391+
});
392+
},
393+
353394
onAction: function(payload) {
354395
if (payload.action === "notifier_enabled") {
355396
this.forceUpdate();
397+
} else if (payload.action === "ignore_state_changed") {
398+
this._refreshIgnoredUsers();
356399
}
357400
},
358401

@@ -800,6 +843,26 @@ module.exports = React.createClass({
800843
);
801844
},
802845

846+
_renderIgnoredUsers: function() {
847+
if (this.state.ignoredUsers.length > 0) {
848+
const updateHandler = this._refreshIgnoredUsers;
849+
return (
850+
<div>
851+
<h3>{ _t("Ignored Users") }</h3>
852+
<div className="mx_UserSettings_section mx_UserSettings_ignoredUsersSection">
853+
<ul>
854+
{this.state.ignoredUsers.map(function(userId) {
855+
return (<IgnoredUser key={userId}
856+
userId={userId}
857+
onUnignored={updateHandler}></IgnoredUser>);
858+
})}
859+
</ul>
860+
</div>
861+
</div>
862+
);
863+
} else return (<div />);
864+
},
865+
803866
_renderLocalSetting: function(setting) {
804867
// TODO: this ought to be a separate component so that we don't need
805868
// to rebind the onChange each time we render
@@ -1306,6 +1369,7 @@ module.exports = React.createClass({
13061369
{this._renderWebRtcSettings()}
13071370
{this._renderDevicesPanel()}
13081371
{this._renderCryptoInfo()}
1372+
{this._renderIgnoredUsers()}
13091373
{this._renderBulkOptions()}
13101374
{this._renderBugReport()}
13111375

src/components/views/rooms/MemberInfo.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ module.exports = withMatrixClient(React.createClass({
6262
updating: 0,
6363
devicesLoading: true,
6464
devices: null,
65+
isIgnoring: false,
6566
};
6667
},
6768

@@ -81,6 +82,8 @@ module.exports = withMatrixClient(React.createClass({
8182
cli.on("RoomState.events", this.onRoomStateEvents);
8283
cli.on("RoomMember.name", this.onRoomMemberName);
8384
cli.on("accountData", this.onAccountData);
85+
86+
this._checkIgnoreState();
8487
},
8588

8689
componentDidMount: function() {
@@ -111,6 +114,11 @@ module.exports = withMatrixClient(React.createClass({
111114
}
112115
},
113116

117+
_checkIgnoreState: function() {
118+
const isIgnoring = this.props.matrixClient.isUserIgnored(this.props.member.userId);
119+
this.setState({isIgnoring: isIgnoring});
120+
},
121+
114122
_disambiguateDevices: function(devices) {
115123
var names = Object.create(null);
116124
for (var i = 0; i < devices.length; i++) {
@@ -225,6 +233,18 @@ module.exports = withMatrixClient(React.createClass({
225233
});
226234
},
227235

236+
onIgnoreToggle: function() {
237+
const ignoredUsers = this.props.matrixClient.getIgnoredUsers();
238+
if (this.state.isIgnoring) {
239+
const index = ignoredUsers.indexOf(this.props.member.userId);
240+
if (index !== -1) ignoredUsers.splice(index, 1);
241+
} else {
242+
ignoredUsers.push(this.props.member.userId);
243+
}
244+
245+
this.props.matrixClient.setIgnoredUsers(ignoredUsers).then(() => this.setState({isIgnoring: !this.state.isIgnoring}));
246+
},
247+
228248
onKick: function() {
229249
const membership = this.props.member.membership;
230250
const kickLabel = membership === "invite" ? _t("Disinvite") : _t("Kick");
@@ -607,6 +627,29 @@ module.exports = withMatrixClient(React.createClass({
607627
);
608628
},
609629

630+
_renderUserOptions: function() {
631+
// Only allow the user to ignore the user if its not ourselves
632+
let ignoreButton = null;
633+
if (this.props.member.userId !== this.props.matrixClient.getUserId()) {
634+
ignoreButton = (
635+
<AccessibleButton onClick={this.onIgnoreToggle} className="mx_MemberInfo_field">
636+
{this.state.isIgnoring ? _t("Unignore") : _t("Ignore")}
637+
</AccessibleButton>
638+
);
639+
}
640+
641+
if (!ignoreButton) return null;
642+
643+
return (
644+
<div>
645+
<h3>{ _t("User Options") }</h3>
646+
<div className="mx_MemberInfo_buttons">
647+
{ignoreButton}
648+
</div>
649+
</div>
650+
);
651+
},
652+
610653
render: function() {
611654
var startChat, kickButton, banButton, muteButton, giveModButton, spinner;
612655
if (this.props.member.userId !== this.props.matrixClient.credentials.userId) {
@@ -756,6 +799,8 @@ module.exports = withMatrixClient(React.createClass({
756799
</div>
757800
</div>
758801

802+
{ this._renderUserOptions() }
803+
759804
{ adminTools }
760805

761806
{ startChat }

src/i18n/strings/en_EN.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,16 @@
260260
"Kick": "Kick",
261261
"Kicks user with given id": "Kicks user with given id",
262262
"Labs": "Labs",
263+
"Ignored Users": "Ignored Users",
264+
"Ignore": "Ignore",
265+
"Unignore": "Unignore",
266+
"User Options": "User Options",
267+
"You are now ignoring %(userId)s": "You are now ignoring %(userId)s",
268+
"You are no longer ignoring %(userId)s": "You are no longer ignoring %(userId)s",
269+
"Unignored user": "Unignored user",
270+
"Ignored user": "Ignored user",
271+
"Stops ignoring a user, showing their messages going forward": "Stops ignoring a user, showing their messages going forward",
272+
"Ignores a user, hiding their messages from you": "Ignores a user, hiding their messages from you",
263273
"Last seen": "Last seen",
264274
"Leave room": "Leave room",
265275
"left and rejoined": "left and rejoined",

0 commit comments

Comments
 (0)