Merge pull request #1389 from turt2live/travis/ignored_users
Add ignore user API support
This commit is contained in:
commit
6a53b7b149
12 changed files with 217 additions and 2 deletions
|
@ -240,6 +240,59 @@ const commands = {
|
||||||
return reject(this.getUsage());
|
return reject(this.getUsage());
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
ignore: new Command("ignore", "<userId>", function(roomId, args) {
|
||||||
|
if (args) {
|
||||||
|
const matches = args.match(/^(\S+)$/);
|
||||||
|
if (matches) {
|
||||||
|
const userId = matches[1];
|
||||||
|
const ignoredUsers = MatrixClientPeg.get().getIgnoredUsers();
|
||||||
|
ignoredUsers.push(userId); // de-duped internally in the js-sdk
|
||||||
|
return success(
|
||||||
|
MatrixClientPeg.get().setIgnoredUsers(ignoredUsers).then(() => {
|
||||||
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
|
Modal.createTrackedDialog('Slash Commands', 'User ignored', QuestionDialog, {
|
||||||
|
title: _t("Ignored user"),
|
||||||
|
description: (
|
||||||
|
<div>
|
||||||
|
<p>{_t("You are now ignoring %(userId)s", {userId: userId})}</p>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
hasCancelButton: false,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reject(this.getUsage());
|
||||||
|
}),
|
||||||
|
|
||||||
|
unignore: new Command("unignore", "<userId>", function(roomId, args) {
|
||||||
|
if (args) {
|
||||||
|
const matches = args.match(/^(\S+)$/);
|
||||||
|
if (matches) {
|
||||||
|
const userId = matches[1];
|
||||||
|
const ignoredUsers = MatrixClientPeg.get().getIgnoredUsers();
|
||||||
|
const index = ignoredUsers.indexOf(userId);
|
||||||
|
if (index !== -1) ignoredUsers.splice(index, 1);
|
||||||
|
return success(
|
||||||
|
MatrixClientPeg.get().setIgnoredUsers(ignoredUsers).then(() => {
|
||||||
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
|
Modal.createTrackedDialog('Slash Commands', 'User unignored', QuestionDialog, {
|
||||||
|
title: _t("Unignored user"),
|
||||||
|
description: (
|
||||||
|
<div>
|
||||||
|
<p>{_t("You are no longer ignoring %(userId)s", {userId: userId})}</p>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
hasCancelButton: false,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reject(this.getUsage());
|
||||||
|
}),
|
||||||
|
|
||||||
// Define the power level of a user
|
// Define the power level of a user
|
||||||
op: new Command("op", "<userId> [<power level>]", function(roomId, args) {
|
op: new Command("op", "<userId> [<power level>]", function(roomId, args) {
|
||||||
if (args) {
|
if (args) {
|
||||||
|
|
|
@ -18,6 +18,12 @@ var MatrixClientPeg = require("./MatrixClientPeg");
|
||||||
import { _t } from './languageHandler';
|
import { _t } from './languageHandler';
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
usersTypingApartFromMeAndIgnored: function(room) {
|
||||||
|
return this.usersTyping(
|
||||||
|
room, [MatrixClientPeg.get().credentials.userId].concat(MatrixClientPeg.get().getIgnoredUsers())
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
usersTypingApartFromMe: function(room) {
|
usersTypingApartFromMe: function(room) {
|
||||||
return this.usersTyping(
|
return this.usersTyping(
|
||||||
room, [MatrixClientPeg.get().credentials.userId]
|
room, [MatrixClientPeg.get().credentials.userId]
|
||||||
|
|
|
@ -94,6 +94,16 @@ const COMMANDS = [
|
||||||
args: '<user-id> <device-id> <device-signing-key>',
|
args: '<user-id> <device-id> <device-signing-key>',
|
||||||
description: 'Verifies a user, device, and pubkey tuple',
|
description: 'Verifies a user, device, and pubkey tuple',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
command: '/ignore',
|
||||||
|
args: '<user-id>',
|
||||||
|
description: 'Ignores a user, hiding their messages from you',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: '/unignore',
|
||||||
|
args: '<user-id>',
|
||||||
|
description: 'Stops ignoring a user, showing their messages going forward',
|
||||||
|
},
|
||||||
// Omitting `/markdown` as it only seems to apply to OldComposer
|
// Omitting `/markdown` as it only seems to apply to OldComposer
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -131,6 +131,9 @@ export default React.createClass({
|
||||||
useCompactLayout: event.getContent().useCompactLayout,
|
useCompactLayout: event.getContent().useCompactLayout,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (event.getType() === "m.ignored_user_list") {
|
||||||
|
dis.dispatch({action: "ignore_state_changed"});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_onKeyDown: function(ev) {
|
_onKeyDown: function(ev) {
|
||||||
|
|
|
@ -241,6 +241,10 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
// TODO: Implement granular (per-room) hide options
|
// TODO: Implement granular (per-room) hide options
|
||||||
_shouldShowEvent: function(mxEv) {
|
_shouldShowEvent: function(mxEv) {
|
||||||
|
if (mxEv.sender && MatrixClientPeg.get().isUserIgnored(mxEv.sender.userId)) {
|
||||||
|
return false; // ignored = no show (only happens if the ignore happens after an event was received)
|
||||||
|
}
|
||||||
|
|
||||||
const EventTile = sdk.getComponent('rooms.EventTile');
|
const EventTile = sdk.getComponent('rooms.EventTile');
|
||||||
if (!EventTile.haveTileForEvent(mxEv)) {
|
if (!EventTile.haveTileForEvent(mxEv)) {
|
||||||
return false; // no tile = no show
|
return false; // no tile = no show
|
||||||
|
@ -549,6 +553,9 @@ module.exports = React.createClass({
|
||||||
if (!r.userId || r.type !== "m.read" || r.userId === myUserId) {
|
if (!r.userId || r.type !== "m.read" || r.userId === myUserId) {
|
||||||
return; // ignore non-read receipts and receipts from self.
|
return; // ignore non-read receipts and receipts from self.
|
||||||
}
|
}
|
||||||
|
if (MatrixClientPeg.get().isUserIgnored(r.userId)) {
|
||||||
|
return; // ignore ignored users
|
||||||
|
}
|
||||||
let member = room.getMember(r.userId);
|
let member = room.getMember(r.userId);
|
||||||
if (!member) {
|
if (!member) {
|
||||||
return; // ignore unknown user IDs
|
return; // ignore unknown user IDs
|
||||||
|
|
|
@ -121,7 +121,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
onRoomMemberTyping: function(ev, member) {
|
onRoomMemberTyping: function(ev, member) {
|
||||||
this.setState({
|
this.setState({
|
||||||
usersTyping: WhoIsTyping.usersTypingApartFromMe(this.props.room),
|
usersTyping: WhoIsTyping.usersTypingApartFromMeAndIgnored(this.props.room),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -384,6 +384,9 @@ var TimelinePanel = React.createClass({
|
||||||
this.sendReadReceipt();
|
this.sendReadReceipt();
|
||||||
this.updateReadMarker();
|
this.updateReadMarker();
|
||||||
break;
|
break;
|
||||||
|
case 'ignore_state_changed':
|
||||||
|
this.forceUpdate();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -176,6 +176,34 @@ const THEMES = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const IgnoredUser = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
userId: React.PropTypes.string.isRequired,
|
||||||
|
onUnignored: React.PropTypes.func.isRequired,
|
||||||
|
},
|
||||||
|
|
||||||
|
_onUnignoreClick: function() {
|
||||||
|
const ignoredUsers = MatrixClientPeg.get().getIgnoredUsers();
|
||||||
|
const index = ignoredUsers.indexOf(this.props.userId);
|
||||||
|
if (index !== -1) {
|
||||||
|
ignoredUsers.splice(index, 1);
|
||||||
|
MatrixClientPeg.get().setIgnoredUsers(ignoredUsers)
|
||||||
|
.then(() => this.props.onUnignored(this.props.userId));
|
||||||
|
} else this.props.onUnignored(this.props.userId);
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
return (
|
||||||
|
<li>
|
||||||
|
<AccessibleButton onClick={this._onUnignoreClick} className="mx_UserSettings_button mx_UserSettings_buttonSmall">
|
||||||
|
{ _t("Unignore") }
|
||||||
|
</AccessibleButton>
|
||||||
|
{ this.props.userId }
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'UserSettings',
|
displayName: 'UserSettings',
|
||||||
|
|
||||||
|
@ -211,6 +239,7 @@ module.exports = React.createClass({
|
||||||
vectorVersion: undefined,
|
vectorVersion: undefined,
|
||||||
rejectingInvites: false,
|
rejectingInvites: false,
|
||||||
mediaDevices: null,
|
mediaDevices: null,
|
||||||
|
ignoredUsers: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -232,6 +261,7 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
this._refreshMediaDevices();
|
this._refreshMediaDevices();
|
||||||
|
this._refreshIgnoredUsers();
|
||||||
|
|
||||||
// Bulk rejecting invites:
|
// Bulk rejecting invites:
|
||||||
// /sync won't have had time to return when UserSettings re-renders from state changes, so getRooms()
|
// /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({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_refreshIgnoredUsers: function(userIdUnignored=null) {
|
||||||
|
const users = MatrixClientPeg.get().getIgnoredUsers();
|
||||||
|
if (userIdUnignored) {
|
||||||
|
const index = users.indexOf(userIdUnignored);
|
||||||
|
if (index !== -1) users.splice(index, 1);
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
ignoredUsers: users,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
onAction: function(payload) {
|
onAction: function(payload) {
|
||||||
if (payload.action === "notifier_enabled") {
|
if (payload.action === "notifier_enabled") {
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
|
} else if (payload.action === "ignore_state_changed") {
|
||||||
|
this._refreshIgnoredUsers();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -800,6 +843,26 @@ module.exports = React.createClass({
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_renderIgnoredUsers: function() {
|
||||||
|
if (this.state.ignoredUsers.length > 0) {
|
||||||
|
const updateHandler = this._refreshIgnoredUsers;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3>{ _t("Ignored Users") }</h3>
|
||||||
|
<div className="mx_UserSettings_section mx_UserSettings_ignoredUsersSection">
|
||||||
|
<ul>
|
||||||
|
{this.state.ignoredUsers.map(function(userId) {
|
||||||
|
return (<IgnoredUser key={userId}
|
||||||
|
userId={userId}
|
||||||
|
onUnignored={updateHandler}></IgnoredUser>);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else return (<div />);
|
||||||
|
},
|
||||||
|
|
||||||
_renderLocalSetting: function(setting) {
|
_renderLocalSetting: function(setting) {
|
||||||
// TODO: this ought to be a separate component so that we don't need
|
// TODO: this ought to be a separate component so that we don't need
|
||||||
// to rebind the onChange each time we render
|
// to rebind the onChange each time we render
|
||||||
|
@ -1306,6 +1369,7 @@ module.exports = React.createClass({
|
||||||
{this._renderWebRtcSettings()}
|
{this._renderWebRtcSettings()}
|
||||||
{this._renderDevicesPanel()}
|
{this._renderDevicesPanel()}
|
||||||
{this._renderCryptoInfo()}
|
{this._renderCryptoInfo()}
|
||||||
|
{this._renderIgnoredUsers()}
|
||||||
{this._renderBulkOptions()}
|
{this._renderBulkOptions()}
|
||||||
{this._renderBugReport()}
|
{this._renderBugReport()}
|
||||||
|
|
||||||
|
|
|
@ -62,6 +62,7 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
updating: 0,
|
updating: 0,
|
||||||
devicesLoading: true,
|
devicesLoading: true,
|
||||||
devices: null,
|
devices: null,
|
||||||
|
isIgnoring: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -81,6 +82,8 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
cli.on("RoomState.events", this.onRoomStateEvents);
|
cli.on("RoomState.events", this.onRoomStateEvents);
|
||||||
cli.on("RoomMember.name", this.onRoomMemberName);
|
cli.on("RoomMember.name", this.onRoomMemberName);
|
||||||
cli.on("accountData", this.onAccountData);
|
cli.on("accountData", this.onAccountData);
|
||||||
|
|
||||||
|
this._checkIgnoreState();
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
|
@ -111,6 +114,11 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_checkIgnoreState: function() {
|
||||||
|
const isIgnoring = this.props.matrixClient.isUserIgnored(this.props.member.userId);
|
||||||
|
this.setState({isIgnoring: isIgnoring});
|
||||||
|
},
|
||||||
|
|
||||||
_disambiguateDevices: function(devices) {
|
_disambiguateDevices: function(devices) {
|
||||||
var names = Object.create(null);
|
var names = Object.create(null);
|
||||||
for (var i = 0; i < devices.length; i++) {
|
for (var i = 0; i < devices.length; i++) {
|
||||||
|
@ -225,6 +233,18 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onIgnoreToggle: function() {
|
||||||
|
const ignoredUsers = this.props.matrixClient.getIgnoredUsers();
|
||||||
|
if (this.state.isIgnoring) {
|
||||||
|
const index = ignoredUsers.indexOf(this.props.member.userId);
|
||||||
|
if (index !== -1) ignoredUsers.splice(index, 1);
|
||||||
|
} else {
|
||||||
|
ignoredUsers.push(this.props.member.userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.matrixClient.setIgnoredUsers(ignoredUsers).then(() => this.setState({isIgnoring: !this.state.isIgnoring}));
|
||||||
|
},
|
||||||
|
|
||||||
onKick: function() {
|
onKick: function() {
|
||||||
const membership = this.props.member.membership;
|
const membership = this.props.member.membership;
|
||||||
const kickLabel = membership === "invite" ? _t("Disinvite") : _t("Kick");
|
const kickLabel = membership === "invite" ? _t("Disinvite") : _t("Kick");
|
||||||
|
@ -607,6 +627,29 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_renderUserOptions: function() {
|
||||||
|
// Only allow the user to ignore the user if its not ourselves
|
||||||
|
let ignoreButton = null;
|
||||||
|
if (this.props.member.userId !== this.props.matrixClient.getUserId()) {
|
||||||
|
ignoreButton = (
|
||||||
|
<AccessibleButton onClick={this.onIgnoreToggle} className="mx_MemberInfo_field">
|
||||||
|
{this.state.isIgnoring ? _t("Unignore") : _t("Ignore")}
|
||||||
|
</AccessibleButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ignoreButton) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3>{ _t("User Options") }</h3>
|
||||||
|
<div className="mx_MemberInfo_buttons">
|
||||||
|
{ignoreButton}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var startChat, kickButton, banButton, muteButton, giveModButton, spinner;
|
var startChat, kickButton, banButton, muteButton, giveModButton, spinner;
|
||||||
if (this.props.member.userId !== this.props.matrixClient.credentials.userId) {
|
if (this.props.member.userId !== this.props.matrixClient.credentials.userId) {
|
||||||
|
@ -756,6 +799,8 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{ this._renderUserOptions() }
|
||||||
|
|
||||||
{ adminTools }
|
{ adminTools }
|
||||||
|
|
||||||
{ startChat }
|
{ startChat }
|
||||||
|
|
|
@ -260,6 +260,16 @@
|
||||||
"Kick": "Kick",
|
"Kick": "Kick",
|
||||||
"Kicks user with given id": "Kicks user with given id",
|
"Kicks user with given id": "Kicks user with given id",
|
||||||
"Labs": "Labs",
|
"Labs": "Labs",
|
||||||
|
"Ignored Users": "Ignored Users",
|
||||||
|
"Ignore": "Ignore",
|
||||||
|
"Unignore": "Unignore",
|
||||||
|
"User Options": "User Options",
|
||||||
|
"You are now ignoring %(userId)s": "You are now ignoring %(userId)s",
|
||||||
|
"You are no longer ignoring %(userId)s": "You are no longer ignoring %(userId)s",
|
||||||
|
"Unignored user": "Unignored user",
|
||||||
|
"Ignored user": "Ignored user",
|
||||||
|
"Stops ignoring a user, showing their messages going forward": "Stops ignoring a user, showing their messages going forward",
|
||||||
|
"Ignores a user, hiding their messages from you": "Ignores a user, hiding their messages from you",
|
||||||
"Last seen": "Last seen",
|
"Last seen": "Last seen",
|
||||||
"Leave room": "Leave room",
|
"Leave room": "Leave room",
|
||||||
"left and rejoined": "left and rejoined",
|
"left and rejoined": "left and rejoined",
|
||||||
|
|
|
@ -227,6 +227,16 @@
|
||||||
"Kick": "Kick",
|
"Kick": "Kick",
|
||||||
"Kicks user with given id": "Kicks user with given id",
|
"Kicks user with given id": "Kicks user with given id",
|
||||||
"Labs": "Labs",
|
"Labs": "Labs",
|
||||||
|
"Ignored Users": "Ignored Users",
|
||||||
|
"Ignore": "Ignore",
|
||||||
|
"Unignore": "Unignore",
|
||||||
|
"User Options": "User Options",
|
||||||
|
"You are now ignoring %(userId)s": "You are now ignoring %(userId)s",
|
||||||
|
"You are no longer ignoring %(userId)s": "You are no longer ignoring %(userId)s",
|
||||||
|
"Unignored user": "Unignored user",
|
||||||
|
"Ignored user": "Ignored user",
|
||||||
|
"Stops ignoring a user, showing their messages going forward": "Stops ignoring a user, showing their messages going forward",
|
||||||
|
"Ignores a user, hiding their messages from you": "Ignores a user, hiding their messages from you",
|
||||||
"Leave room": "Leave room",
|
"Leave room": "Leave room",
|
||||||
"left and rejoined": "left and rejoined",
|
"left and rejoined": "left and rejoined",
|
||||||
"left": "left",
|
"left": "left",
|
||||||
|
|
|
@ -24,6 +24,7 @@ var sdk = require('matrix-react-sdk');
|
||||||
|
|
||||||
var MessagePanel = sdk.getComponent('structures.MessagePanel');
|
var MessagePanel = sdk.getComponent('structures.MessagePanel');
|
||||||
import UserSettingsStore from '../../../src/UserSettingsStore';
|
import UserSettingsStore from '../../../src/UserSettingsStore';
|
||||||
|
import MatrixClientPeg from '../../../src/MatrixClientPeg';
|
||||||
|
|
||||||
var test_utils = require('test-utils');
|
var test_utils = require('test-utils');
|
||||||
var mockclock = require('mock-clock');
|
var mockclock = require('mock-clock');
|
||||||
|
@ -51,16 +52,19 @@ describe('MessagePanel', function () {
|
||||||
var clock = mockclock.clock();
|
var clock = mockclock.clock();
|
||||||
var realSetTimeout = window.setTimeout;
|
var realSetTimeout = window.setTimeout;
|
||||||
var events = mkEvents();
|
var events = mkEvents();
|
||||||
|
var sandbox = null;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
test_utils.beforeEach(this);
|
test_utils.beforeEach(this);
|
||||||
client = test_utils.createTestClient();
|
sandbox = test_utils.stubClient();
|
||||||
|
client = MatrixClientPeg.get();
|
||||||
client.credentials = {userId: '@me:here'};
|
client.credentials = {userId: '@me:here'};
|
||||||
UserSettingsStore.getSyncedSettings = sinon.stub().returns({});
|
UserSettingsStore.getSyncedSettings = sinon.stub().returns({});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
clock.uninstall();
|
clock.uninstall();
|
||||||
|
sandbox.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
function mkEvents() {
|
function mkEvents() {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue