mirror of
https://github.com/vector-im/element-web.git
synced 2024-11-16 21:24:59 +08:00
Merge branch 'develop' into t3chguy/remove_bluebird_1
This commit is contained in:
commit
79ebb9091c
@ -82,6 +82,7 @@
|
||||
"gemini-scrollbar": "github:matrix-org/gemini-scrollbar#91e1e566",
|
||||
"gfm.css": "^1.1.1",
|
||||
"glob": "^5.0.14",
|
||||
"glob-to-regexp": "^0.4.1",
|
||||
"highlight.js": "^9.15.8",
|
||||
"is-ip": "^2.0.0",
|
||||
"isomorphic-fetch": "^2.2.1",
|
||||
|
@ -124,6 +124,7 @@
|
||||
@import "./views/messages/_MTextBody.scss";
|
||||
@import "./views/messages/_MessageActionBar.scss";
|
||||
@import "./views/messages/_MessageTimestamp.scss";
|
||||
@import "./views/messages/_MjolnirBody.scss";
|
||||
@import "./views/messages/_ReactionsRow.scss";
|
||||
@import "./views/messages/_ReactionsRowButton.scss";
|
||||
@import "./views/messages/_ReactionsRowButtonTooltip.scss";
|
||||
@ -183,6 +184,7 @@
|
||||
@import "./views/settings/tabs/room/_SecurityRoomSettingsTab.scss";
|
||||
@import "./views/settings/tabs/user/_GeneralUserSettingsTab.scss";
|
||||
@import "./views/settings/tabs/user/_HelpUserSettingsTab.scss";
|
||||
@import "./views/settings/tabs/user/_MjolnirUserSettingsTab.scss";
|
||||
@import "./views/settings/tabs/user/_NotificationUserSettingsTab.scss";
|
||||
@import "./views/settings/tabs/user/_PreferencesUserSettingsTab.scss";
|
||||
@import "./views/settings/tabs/user/_SecurityUserSettingsTab.scss";
|
||||
|
@ -45,6 +45,10 @@ limitations under the License.
|
||||
mask-image: url('$(res)/img/feather-customised/flag.svg');
|
||||
}
|
||||
|
||||
.mx_UserSettingsDialog_mjolnirIcon::before {
|
||||
mask-image: url('$(res)/img/feather-customised/face.svg');
|
||||
}
|
||||
|
||||
.mx_UserSettingsDialog_flairIcon::before {
|
||||
mask-image: url('$(res)/img/feather-customised/flair.svg');
|
||||
}
|
||||
|
19
res/css/views/messages/_MjolnirBody.scss
Normal file
19
res/css/views/messages/_MjolnirBody.scss
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
.mx_MjolnirBody {
|
||||
opacity: 0.4;
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
.mx_MjolnirUserSettingsTab .mx_Field {
|
||||
@mixin mx_Settings_fullWidthField;
|
||||
}
|
||||
|
||||
.mx_MjolnirUserSettingsTab_listItem {
|
||||
margin-bottom: 2px;
|
||||
}
|
@ -36,6 +36,7 @@ import * as StorageManager from './utils/StorageManager';
|
||||
import SettingsStore from "./settings/SettingsStore";
|
||||
import TypingStore from "./stores/TypingStore";
|
||||
import {IntegrationManagers} from "./integrations/IntegrationManagers";
|
||||
import {Mjolnir} from "./mjolnir/Mjolnir";
|
||||
|
||||
/**
|
||||
* Called at startup, to attempt to build a logged-in Matrix session. It tries
|
||||
@ -581,6 +582,11 @@ async function startMatrixClient(startSyncing=true) {
|
||||
IntegrationManagers.sharedInstance().startWatching();
|
||||
ActiveWidgetStore.start();
|
||||
|
||||
// Start Mjolnir even though we haven't checked the feature flag yet. Starting
|
||||
// the thing just wastes CPU cycles, but should result in no actual functionality
|
||||
// being exposed to the user.
|
||||
Mjolnir.sharedInstance().start();
|
||||
|
||||
if (startSyncing) {
|
||||
await MatrixClientPeg.start();
|
||||
} else {
|
||||
@ -641,6 +647,7 @@ export function stopMatrixClient(unsetClient=true) {
|
||||
Presence.stop();
|
||||
ActiveWidgetStore.stop();
|
||||
IntegrationManagers.sharedInstance().stopWatching();
|
||||
Mjolnir.sharedInstance().stop();
|
||||
if (DMRoomMap.shared()) DMRoomMap.shared().stop();
|
||||
const cli = MatrixClientPeg.get();
|
||||
if (cli) {
|
||||
|
35
src/RoomAliasCache.js
Normal file
35
src/RoomAliasCache.js
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is meant to be a cache of room alias to room ID so that moving between
|
||||
* rooms happens smoothly (for example using browser back / forward buttons).
|
||||
*
|
||||
* For the moment, it's in memory only and so only applies for the current
|
||||
* session for simplicity, but could be extended further in the future.
|
||||
*
|
||||
* A similar thing could also be achieved via `pushState` with a state object,
|
||||
* but keeping it separate like this seems easier in case we do want to extend.
|
||||
*/
|
||||
const aliasToIDMap = new Map();
|
||||
|
||||
export function storeRoomAliasInCache(alias, id) {
|
||||
aliasToIDMap.set(alias, id);
|
||||
}
|
||||
|
||||
export function getCachedRoomIDForAlias(alias) {
|
||||
return aliasToIDMap.get(alias);
|
||||
}
|
@ -60,7 +60,8 @@ import AutoDiscoveryUtils from "../../utils/AutoDiscoveryUtils";
|
||||
import DMRoomMap from '../../utils/DMRoomMap';
|
||||
import { countRoomsWithNotif } from '../../RoomNotifs';
|
||||
import { setTheme } from "../../theme";
|
||||
import {defer} from "../../utils/promise";
|
||||
import { storeRoomAliasInCache } from '../../RoomAliasCache';
|
||||
import { defer } from "../../utils/promise";
|
||||
|
||||
// Disable warnings for now: we use deprecated bluebird functions
|
||||
// and need to migrate, but they spam the console with warnings.
|
||||
@ -867,7 +868,12 @@ export default createReactClass({
|
||||
const room = MatrixClientPeg.get().getRoom(roomInfo.room_id);
|
||||
if (room) {
|
||||
const theAlias = Rooms.getDisplayAliasForRoom(room);
|
||||
if (theAlias) presentedId = theAlias;
|
||||
if (theAlias) {
|
||||
presentedId = theAlias;
|
||||
// Store display alias of the presented room in cache to speed future
|
||||
// navigation.
|
||||
storeRoomAliasInCache(theAlias, room.roomId);
|
||||
}
|
||||
|
||||
// Store this as the ID of the last room accessed. This is so that we can
|
||||
// persist which room is being stored across refreshes and browser quits.
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright 2019 New Vector Ltd
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@ -29,12 +30,34 @@ import HelpUserSettingsTab from "../settings/tabs/user/HelpUserSettingsTab";
|
||||
import FlairUserSettingsTab from "../settings/tabs/user/FlairUserSettingsTab";
|
||||
import sdk from "../../../index";
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import MjolnirUserSettingsTab from "../settings/tabs/user/MjolnirUserSettingsTab";
|
||||
|
||||
export default class UserSettingsDialog extends React.Component {
|
||||
static propTypes = {
|
||||
onFinished: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
mjolnirEnabled: SettingsStore.isFeatureEnabled("feature_mjolnir"),
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
this._mjolnirWatcher = SettingsStore.watchSetting("feature_mjolnir", null, this._mjolnirChanged.bind(this));
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
SettingsStore.unwatchSetting(this._mjolnirWatcher);
|
||||
}
|
||||
|
||||
_mjolnirChanged(settingName, roomId, atLevel, newValue) {
|
||||
// We can cheat because we know what levels a feature is tracked at, and how it is tracked
|
||||
this.setState({mjolnirEnabled: newValue});
|
||||
}
|
||||
|
||||
_getTabs() {
|
||||
const tabs = [];
|
||||
|
||||
@ -75,6 +98,13 @@ export default class UserSettingsDialog extends React.Component {
|
||||
<LabsUserSettingsTab />,
|
||||
));
|
||||
}
|
||||
if (this.state.mjolnirEnabled) {
|
||||
tabs.push(new Tab(
|
||||
_td("Ignored users"),
|
||||
"mx_UserSettingsDialog_mjolnirIcon",
|
||||
<MjolnirUserSettingsTab />,
|
||||
));
|
||||
}
|
||||
tabs.push(new Tab(
|
||||
_td("Help & About"),
|
||||
"mx_UserSettingsDialog_helpIcon",
|
||||
|
@ -18,6 +18,8 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import createReactClass from 'create-react-class';
|
||||
import sdk from '../../../index';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import {Mjolnir} from "../../../mjolnir/Mjolnir";
|
||||
|
||||
module.exports = createReactClass({
|
||||
displayName: 'MessageEvent',
|
||||
@ -49,6 +51,10 @@ module.exports = createReactClass({
|
||||
return this.refs.body && this.refs.body.getEventTileOps ? this.refs.body.getEventTileOps() : null;
|
||||
},
|
||||
|
||||
onTileUpdate: function() {
|
||||
this.forceUpdate();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const UnknownBody = sdk.getComponent('messages.UnknownBody');
|
||||
|
||||
@ -81,6 +87,21 @@ module.exports = createReactClass({
|
||||
}
|
||||
}
|
||||
|
||||
if (SettingsStore.isFeatureEnabled("feature_mjolnir")) {
|
||||
const key = `mx_mjolnir_render_${this.props.mxEvent.getRoomId()}__${this.props.mxEvent.getId()}`;
|
||||
const allowRender = localStorage.getItem(key) === "true";
|
||||
|
||||
if (!allowRender) {
|
||||
const userDomain = this.props.mxEvent.getSender().split(':').slice(1).join(':');
|
||||
const userBanned = Mjolnir.sharedInstance().isUserBanned(this.props.mxEvent.getSender());
|
||||
const serverBanned = Mjolnir.sharedInstance().isServerBanned(userDomain);
|
||||
|
||||
if (userBanned || serverBanned) {
|
||||
BodyType = sdk.getComponent('messages.MjolnirBody');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return <BodyType
|
||||
ref="body" mxEvent={this.props.mxEvent}
|
||||
highlights={this.props.highlights}
|
||||
@ -90,6 +111,8 @@ module.exports = createReactClass({
|
||||
maxImageHeight={this.props.maxImageHeight}
|
||||
replacingEventId={this.props.replacingEventId}
|
||||
editState={this.props.editState}
|
||||
onHeightChanged={this.props.onHeightChanged} />;
|
||||
onHeightChanged={this.props.onHeightChanged}
|
||||
onMessageAllowed={this.onTileUpdate}
|
||||
/>;
|
||||
},
|
||||
});
|
||||
|
48
src/components/views/messages/MjolnirBody.js
Normal file
48
src/components/views/messages/MjolnirBody.js
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {_t} from '../../../languageHandler';
|
||||
|
||||
export default class MjolnirBody extends React.Component {
|
||||
static propTypes = {
|
||||
mxEvent: PropTypes.object.isRequired,
|
||||
onMessageAllowed: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
_onAllowClick = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const key = `mx_mjolnir_render_${this.props.mxEvent.getRoomId()}__${this.props.mxEvent.getId()}`;
|
||||
localStorage.setItem(key, "true");
|
||||
this.props.onMessageAllowed();
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className='mx_MjolnirBody'><i>{_t(
|
||||
"You have ignored this user, so their message is hidden. <a>Show anyways.</a>",
|
||||
{}, {a: (sub) => <a href="#" onClick={this._onAllowClick}>{sub}</a>},
|
||||
)}</i></div>
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,329 @@
|
||||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {_t} from "../../../../../languageHandler";
|
||||
import {Mjolnir} from "../../../../../mjolnir/Mjolnir";
|
||||
import {ListRule} from "../../../../../mjolnir/ListRule";
|
||||
import {BanList, RULE_SERVER, RULE_USER} from "../../../../../mjolnir/BanList";
|
||||
import Modal from "../../../../../Modal";
|
||||
import MatrixClientPeg from "../../../../../MatrixClientPeg";
|
||||
|
||||
const sdk = require("../../../../..");
|
||||
|
||||
export default class MjolnirUserSettingsTab extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
busy: false,
|
||||
newPersonalRule: "",
|
||||
newList: "",
|
||||
};
|
||||
}
|
||||
|
||||
_onPersonalRuleChanged = (e) => {
|
||||
this.setState({newPersonalRule: e.target.value});
|
||||
};
|
||||
|
||||
_onNewListChanged = (e) => {
|
||||
this.setState({newList: e.target.value});
|
||||
};
|
||||
|
||||
_onAddPersonalRule = async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
let kind = RULE_SERVER;
|
||||
if (this.state.newPersonalRule.startsWith("@")) {
|
||||
kind = RULE_USER;
|
||||
}
|
||||
|
||||
this.setState({busy: true});
|
||||
try {
|
||||
const list = await Mjolnir.sharedInstance().getOrCreatePersonalList();
|
||||
await list.banEntity(kind, this.state.newPersonalRule, _t("Ignored/Blocked"));
|
||||
this.setState({newPersonalRule: ""}); // this will also cause the new rule to be rendered
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createTrackedDialog('Failed to add Mjolnir rule', '', ErrorDialog, {
|
||||
title: _t('Error adding ignored user/server'),
|
||||
description: _t('Something went wrong. Please try again or view your console for hints.'),
|
||||
});
|
||||
} finally {
|
||||
this.setState({busy: false});
|
||||
}
|
||||
};
|
||||
|
||||
_onSubscribeList = async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
this.setState({busy: true});
|
||||
try {
|
||||
const room = await MatrixClientPeg.get().joinRoom(this.state.newList);
|
||||
await Mjolnir.sharedInstance().subscribeToList(room.roomId);
|
||||
this.setState({newList: ""}); // this will also cause the new rule to be rendered
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createTrackedDialog('Failed to subscribe to Mjolnir list', '', ErrorDialog, {
|
||||
title: _t('Error subscribing to list'),
|
||||
description: _t('Please verify the room ID or alias and try again.'),
|
||||
});
|
||||
} finally {
|
||||
this.setState({busy: false});
|
||||
}
|
||||
};
|
||||
|
||||
async _removePersonalRule(rule: ListRule) {
|
||||
this.setState({busy: true});
|
||||
try {
|
||||
const list = Mjolnir.sharedInstance().getPersonalList();
|
||||
await list.unbanEntity(rule.kind, rule.entity);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createTrackedDialog('Failed to remove Mjolnir rule', '', ErrorDialog, {
|
||||
title: _t('Error removing ignored user/server'),
|
||||
description: _t('Something went wrong. Please try again or view your console for hints.'),
|
||||
});
|
||||
} finally {
|
||||
this.setState({busy: false});
|
||||
}
|
||||
}
|
||||
|
||||
async _unsubscribeFromList(list: BanList) {
|
||||
this.setState({busy: true});
|
||||
try {
|
||||
await Mjolnir.sharedInstance().unsubscribeFromList(list.roomId);
|
||||
await MatrixClientPeg.get().leave(list.roomId);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createTrackedDialog('Failed to unsubscribe from Mjolnir list', '', ErrorDialog, {
|
||||
title: _t('Error unsubscribing from list'),
|
||||
description: _t('Please try again or view your console for hints.'),
|
||||
});
|
||||
} finally {
|
||||
this.setState({busy: false});
|
||||
}
|
||||
}
|
||||
|
||||
_viewListRules(list: BanList) {
|
||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
|
||||
const room = MatrixClientPeg.get().getRoom(list.roomId);
|
||||
const name = room ? room.name : list.roomId;
|
||||
|
||||
const renderRules = (rules: ListRule[]) => {
|
||||
if (rules.length === 0) return <i>{_t("None")}</i>;
|
||||
|
||||
const tiles = [];
|
||||
for (const rule of rules) {
|
||||
tiles.push(<li key={rule.kind + rule.entity}><code>{rule.entity}</code></li>);
|
||||
}
|
||||
return <ul>{tiles}</ul>;
|
||||
};
|
||||
|
||||
Modal.createTrackedDialog('View Mjolnir list rules', '', QuestionDialog, {
|
||||
title: _t("Ban list rules - %(roomName)s", {roomName: name}),
|
||||
description: (
|
||||
<div>
|
||||
<h3>{_t("Server rules")}</h3>
|
||||
{renderRules(list.serverRules)}
|
||||
<h3>{_t("User rules")}</h3>
|
||||
{renderRules(list.userRules)}
|
||||
</div>
|
||||
),
|
||||
button: _t("Close"),
|
||||
hasCancelButton: false,
|
||||
});
|
||||
}
|
||||
|
||||
_renderPersonalBanListRules() {
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
|
||||
const list = Mjolnir.sharedInstance().getPersonalList();
|
||||
const rules = list ? [...list.userRules, ...list.serverRules] : [];
|
||||
if (!list || rules.length <= 0) return <i>{_t("You have not ignored anyone.")}</i>;
|
||||
|
||||
const tiles = [];
|
||||
for (const rule of rules) {
|
||||
tiles.push(
|
||||
<li key={rule.entity} className="mx_MjolnirUserSettingsTab_listItem">
|
||||
<AccessibleButton
|
||||
kind="danger_sm"
|
||||
onClick={() => this._removePersonalRule(rule)}
|
||||
disabled={this.state.busy}
|
||||
>
|
||||
{_t("Remove")}
|
||||
</AccessibleButton>
|
||||
<code>{rule.entity}</code>
|
||||
</li>,
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>{_t("You are currently ignoring:")}</p>
|
||||
<ul>{tiles}</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
_renderSubscribedBanLists() {
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
|
||||
const personalList = Mjolnir.sharedInstance().getPersonalList();
|
||||
const lists = Mjolnir.sharedInstance().lists.filter(b => {
|
||||
return personalList? personalList.roomId !== b.roomId : true;
|
||||
});
|
||||
if (!lists || lists.length <= 0) return <i>{_t("You are not subscribed to any lists")}</i>;
|
||||
|
||||
const tiles = [];
|
||||
for (const list of lists) {
|
||||
const room = MatrixClientPeg.get().getRoom(list.roomId);
|
||||
const name = room ? <span>{room.name} (<code>{list.roomId}</code>)</span> : <code>list.roomId</code>;
|
||||
tiles.push(
|
||||
<li key={list.roomId} className="mx_MjolnirUserSettingsTab_listItem">
|
||||
<AccessibleButton
|
||||
kind="danger_sm"
|
||||
onClick={() => this._unsubscribeFromList(list)}
|
||||
disabled={this.state.busy}
|
||||
>
|
||||
{_t("Unsubscribe")}
|
||||
</AccessibleButton>
|
||||
<AccessibleButton
|
||||
kind="primary_sm"
|
||||
onClick={() => this._viewListRules(list)}
|
||||
disabled={this.state.busy}
|
||||
>
|
||||
{_t("View rules")}
|
||||
</AccessibleButton>
|
||||
{name}
|
||||
</li>,
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>{_t("You are currently subscribed to:")}</p>
|
||||
<ul>{tiles}</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const Field = sdk.getComponent('elements.Field');
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
|
||||
return (
|
||||
<div className="mx_SettingsTab mx_MjolnirUserSettingsTab">
|
||||
<div className="mx_SettingsTab_heading">{_t("Ignored users")}</div>
|
||||
<div className="mx_SettingsTab_section">
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
<span className='warning'>{_t("⚠ These settings are meant for advanced users.")}</span><br />
|
||||
<br />
|
||||
{_t(
|
||||
"Add users and servers you want to ignore here. Use asterisks " +
|
||||
"to have Riot match any characters. For example, <code>@bot:*</code> " +
|
||||
"would ignore all users that have the name 'bot' on any server.",
|
||||
{}, {code: (s) => <code>{s}</code>},
|
||||
)}<br />
|
||||
<br />
|
||||
{_t(
|
||||
"Ignoring people is done through ban lists which contain rules for " +
|
||||
"who to ban. Subscribing to a ban list means the users/servers blocked by " +
|
||||
"that list will be hidden from you.",
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Personal ban list")}</span>
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
{_t(
|
||||
"Your personal ban list holds all the users/servers you personally don't " +
|
||||
"want to see messages from. After ignoring your first user/server, a new room " +
|
||||
"will show up in your room list named 'My Ban List' - stay in this room to keep " +
|
||||
"the ban list in effect.",
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
{this._renderPersonalBanListRules()}
|
||||
</div>
|
||||
<div>
|
||||
<form onSubmit={this._onAddPersonalRule} autoComplete="off">
|
||||
<Field
|
||||
id="mx_MjolnirUserSettingsTab_personalAdd"
|
||||
type="text"
|
||||
label={_t("Server or user ID to ignore")}
|
||||
placeholder={_t("eg: @bot:* or example.org")}
|
||||
value={this.state.newPersonalRule}
|
||||
onChange={this._onPersonalRuleChanged}
|
||||
/>
|
||||
<AccessibleButton
|
||||
type="submit"
|
||||
kind="primary"
|
||||
onClick={this._onAddPersonalRule}
|
||||
disabled={this.state.busy}
|
||||
>
|
||||
{_t("Ignore")}
|
||||
</AccessibleButton>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Subscribed lists")}</span>
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
<span className='warning'>{_t("Subscribing to a ban list will cause you to join it!")}</span>
|
||||
|
||||
<span>{_t(
|
||||
"If this isn't what you want, please use a different tool to ignore users.",
|
||||
)}</span>
|
||||
</div>
|
||||
<div>
|
||||
{this._renderSubscribedBanLists()}
|
||||
</div>
|
||||
<div>
|
||||
<form onSubmit={this._onSubscribeList} autoComplete="off">
|
||||
<Field
|
||||
id="mx_MjolnirUserSettingsTab_subscriptionAdd"
|
||||
type="text"
|
||||
label={_t("Room ID or alias of ban list")}
|
||||
value={this.state.newList}
|
||||
onChange={this._onNewListChanged}
|
||||
/>
|
||||
<AccessibleButton
|
||||
type="submit"
|
||||
kind="primary"
|
||||
onClick={this._onSubscribeList}
|
||||
disabled={this.state.busy}
|
||||
>
|
||||
{_t("Subscribe")}
|
||||
</AccessibleButton>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -340,6 +340,7 @@
|
||||
"Render simple counters in room header": "Render simple counters in room header",
|
||||
"Multiple integration managers": "Multiple integration managers",
|
||||
"Use the new, consistent UserInfo panel for Room Members and Group Members": "Use the new, consistent UserInfo panel for Room Members and Group Members",
|
||||
"Try out new ways to ignore people (experimental)": "Try out new ways to ignore people (experimental)",
|
||||
"Send verification requests in direct message": "Send verification requests in direct message",
|
||||
"Use the new, faster, composer for writing messages": "Use the new, faster, composer for writing messages",
|
||||
"Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing",
|
||||
@ -394,6 +395,8 @@
|
||||
"Call invitation": "Call invitation",
|
||||
"Messages sent by bot": "Messages sent by bot",
|
||||
"When rooms are upgraded": "When rooms are upgraded",
|
||||
"My Ban List": "My Ban List",
|
||||
"This is your list of users/servers you have blocked - don't leave the room!": "This is your list of users/servers you have blocked - don't leave the room!",
|
||||
"Active call (%(roomName)s)": "Active call (%(roomName)s)",
|
||||
"unknown caller": "unknown caller",
|
||||
"Incoming voice call from %(name)s": "Incoming voice call from %(name)s",
|
||||
@ -641,6 +644,40 @@
|
||||
"Access Token:": "Access Token:",
|
||||
"click to reveal": "click to reveal",
|
||||
"Labs": "Labs",
|
||||
"Ignored/Blocked": "Ignored/Blocked",
|
||||
"Error adding ignored user/server": "Error adding ignored user/server",
|
||||
"Something went wrong. Please try again or view your console for hints.": "Something went wrong. Please try again or view your console for hints.",
|
||||
"Error subscribing to list": "Error subscribing to list",
|
||||
"Please verify the room ID or alias and try again.": "Please verify the room ID or alias and try again.",
|
||||
"Error removing ignored user/server": "Error removing ignored user/server",
|
||||
"Error unsubscribing from list": "Error unsubscribing from list",
|
||||
"Please try again or view your console for hints.": "Please try again or view your console for hints.",
|
||||
"None": "None",
|
||||
"Ban list rules - %(roomName)s": "Ban list rules - %(roomName)s",
|
||||
"Server rules": "Server rules",
|
||||
"User rules": "User rules",
|
||||
"Close": "Close",
|
||||
"You have not ignored anyone.": "You have not ignored anyone.",
|
||||
"Remove": "Remove",
|
||||
"You are currently ignoring:": "You are currently ignoring:",
|
||||
"You are not subscribed to any lists": "You are not subscribed to any lists",
|
||||
"Unsubscribe": "Unsubscribe",
|
||||
"View rules": "View rules",
|
||||
"You are currently subscribed to:": "You are currently subscribed to:",
|
||||
"Ignored users": "Ignored users",
|
||||
"⚠ These settings are meant for advanced users.": "⚠ These settings are meant for advanced users.",
|
||||
"Add users and servers you want to ignore here. Use asterisks to have Riot match any characters. For example, <code>@bot:*</code> would ignore all users that have the name 'bot' on any server.": "Add users and servers you want to ignore here. Use asterisks to have Riot match any characters. For example, <code>@bot:*</code> would ignore all users that have the name 'bot' on any server.",
|
||||
"Ignoring people is done through ban lists which contain rules for who to ban. Subscribing to a ban list means the users/servers blocked by that list will be hidden from you.": "Ignoring people is done through ban lists which contain rules for who to ban. Subscribing to a ban list means the users/servers blocked by that list will be hidden from you.",
|
||||
"Personal ban list": "Personal ban list",
|
||||
"Your personal ban list holds all the users/servers you personally don't want to see messages from. After ignoring your first user/server, a new room will show up in your room list named 'My Ban List' - stay in this room to keep the ban list in effect.": "Your personal ban list holds all the users/servers you personally don't want to see messages from. After ignoring your first user/server, a new room will show up in your room list named 'My Ban List' - stay in this room to keep the ban list in effect.",
|
||||
"Server or user ID to ignore": "Server or user ID to ignore",
|
||||
"eg: @bot:* or example.org": "eg: @bot:* or example.org",
|
||||
"Ignore": "Ignore",
|
||||
"Subscribed lists": "Subscribed lists",
|
||||
"Subscribing to a ban list will cause you to join it!": "Subscribing to a ban list will cause you to join it!",
|
||||
"If this isn't what you want, please use a different tool to ignore users.": "If this isn't what you want, please use a different tool to ignore users.",
|
||||
"Room ID or alias of ban list": "Room ID or alias of ban list",
|
||||
"Subscribe": "Subscribe",
|
||||
"Notifications": "Notifications",
|
||||
"Start automatically after system login": "Start automatically after system login",
|
||||
"Always show the window menu bar": "Always show the window menu bar",
|
||||
@ -658,7 +695,6 @@
|
||||
"Cryptography": "Cryptography",
|
||||
"Device ID:": "Device ID:",
|
||||
"Device key:": "Device key:",
|
||||
"Ignored users": "Ignored users",
|
||||
"Bulk options": "Bulk options",
|
||||
"Accept all %(invitedRooms)s invites": "Accept all %(invitedRooms)s invites",
|
||||
"Reject all %(invitedRooms)s invites": "Reject all %(invitedRooms)s invites",
|
||||
@ -769,7 +805,6 @@
|
||||
"Discovery options will appear once you have added a phone number above.": "Discovery options will appear once you have added a phone number above.",
|
||||
"Unable to remove contact information": "Unable to remove contact information",
|
||||
"Remove %(email)s?": "Remove %(email)s?",
|
||||
"Remove": "Remove",
|
||||
"Invalid Email Address": "Invalid Email Address",
|
||||
"This doesn't appear to be a valid email address": "This doesn't appear to be a valid email address",
|
||||
"Unable to add email address": "Unable to add email address",
|
||||
@ -836,7 +871,6 @@
|
||||
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.",
|
||||
"Are you sure?": "Are you sure?",
|
||||
"No devices with registered encryption keys": "No devices with registered encryption keys",
|
||||
"Ignore": "Ignore",
|
||||
"Jump to read receipt": "Jump to read receipt",
|
||||
"Mention": "Mention",
|
||||
"Invite": "Invite",
|
||||
@ -849,7 +883,6 @@
|
||||
"Revoke Moderator": "Revoke Moderator",
|
||||
"Make Moderator": "Make Moderator",
|
||||
"Admin Tools": "Admin Tools",
|
||||
"Close": "Close",
|
||||
"and %(count)s others...|other": "and %(count)s others...",
|
||||
"and %(count)s others...|one": "and one other...",
|
||||
"Invite to this room": "Invite to this room",
|
||||
@ -1066,6 +1099,7 @@
|
||||
"Invalid file%(extra)s": "Invalid file%(extra)s",
|
||||
"Error decrypting image": "Error decrypting image",
|
||||
"Show image": "Show image",
|
||||
"You have ignored this user, so their message is hidden. <a>Show anyways.</a>": "You have ignored this user, so their message is hidden. <a>Show anyways.</a>",
|
||||
"You verified %(name)s": "You verified %(name)s",
|
||||
"You cancelled verifying %(name)s": "You cancelled verifying %(name)s",
|
||||
"%(name)s cancelled verifying": "%(name)s cancelled verifying",
|
||||
|
110
src/mjolnir/BanList.js
Normal file
110
src/mjolnir/BanList.js
Normal file
@ -0,0 +1,110 @@
|
||||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
// Inspiration largely taken from Mjolnir itself
|
||||
|
||||
import {ListRule, RECOMMENDATION_BAN, recommendationToStable} from "./ListRule";
|
||||
import MatrixClientPeg from "../MatrixClientPeg";
|
||||
|
||||
export const RULE_USER = "m.room.rule.user";
|
||||
export const RULE_ROOM = "m.room.rule.room";
|
||||
export const RULE_SERVER = "m.room.rule.server";
|
||||
|
||||
export const USER_RULE_TYPES = [RULE_USER, "org.matrix.mjolnir.rule.user"];
|
||||
export const ROOM_RULE_TYPES = [RULE_ROOM, "org.matrix.mjolnir.rule.room"];
|
||||
export const SERVER_RULE_TYPES = [RULE_SERVER, "org.matrix.mjolnir.rule.server"];
|
||||
export const ALL_RULE_TYPES = [...USER_RULE_TYPES, ...ROOM_RULE_TYPES, ...SERVER_RULE_TYPES];
|
||||
|
||||
export function ruleTypeToStable(rule: string, unstable = true): string {
|
||||
if (USER_RULE_TYPES.includes(rule)) {
|
||||
return unstable ? USER_RULE_TYPES[USER_RULE_TYPES.length - 1] : RULE_USER;
|
||||
}
|
||||
if (ROOM_RULE_TYPES.includes(rule)) {
|
||||
return unstable ? ROOM_RULE_TYPES[ROOM_RULE_TYPES.length - 1] : RULE_ROOM;
|
||||
}
|
||||
if (SERVER_RULE_TYPES.includes(rule)) {
|
||||
return unstable ? SERVER_RULE_TYPES[SERVER_RULE_TYPES.length - 1] : RULE_SERVER;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export class BanList {
|
||||
_rules: ListRule[] = [];
|
||||
_roomId: string;
|
||||
|
||||
constructor(roomId: string) {
|
||||
this._roomId = roomId;
|
||||
this.updateList();
|
||||
}
|
||||
|
||||
get roomId(): string {
|
||||
return this._roomId;
|
||||
}
|
||||
|
||||
get serverRules(): ListRule[] {
|
||||
return this._rules.filter(r => r.kind === RULE_SERVER);
|
||||
}
|
||||
|
||||
get userRules(): ListRule[] {
|
||||
return this._rules.filter(r => r.kind === RULE_USER);
|
||||
}
|
||||
|
||||
get roomRules(): ListRule[] {
|
||||
return this._rules.filter(r => r.kind === RULE_ROOM);
|
||||
}
|
||||
|
||||
async banEntity(kind: string, entity: string, reason: string): Promise<any> {
|
||||
await MatrixClientPeg.get().sendStateEvent(this._roomId, ruleTypeToStable(kind, true), {
|
||||
entity: entity,
|
||||
reason: reason,
|
||||
recommendation: recommendationToStable(RECOMMENDATION_BAN, true),
|
||||
}, "rule:" + entity);
|
||||
this._rules.push(new ListRule(entity, RECOMMENDATION_BAN, reason, ruleTypeToStable(kind, false)));
|
||||
}
|
||||
|
||||
async unbanEntity(kind: string, entity: string): Promise<any> {
|
||||
// Empty state event is effectively deleting it.
|
||||
await MatrixClientPeg.get().sendStateEvent(this._roomId, ruleTypeToStable(kind, true), {}, "rule:" + entity);
|
||||
this._rules = this._rules.filter(r => {
|
||||
if (r.kind !== ruleTypeToStable(kind, false)) return true;
|
||||
if (r.entity !== entity) return true;
|
||||
return false; // we just deleted this rule
|
||||
});
|
||||
}
|
||||
|
||||
updateList() {
|
||||
this._rules = [];
|
||||
|
||||
const room = MatrixClientPeg.get().getRoom(this._roomId);
|
||||
if (!room) return;
|
||||
|
||||
for (const eventType of ALL_RULE_TYPES) {
|
||||
const events = room.currentState.getStateEvents(eventType, undefined);
|
||||
for (const ev of events) {
|
||||
if (!ev.getStateKey()) continue;
|
||||
|
||||
const kind = ruleTypeToStable(eventType, false);
|
||||
|
||||
const entity = ev.getContent()['entity'];
|
||||
const recommendation = ev.getContent()['recommendation'];
|
||||
const reason = ev.getContent()['reason'];
|
||||
if (!entity || !recommendation || !reason) continue;
|
||||
|
||||
this._rules.push(new ListRule(entity, recommendation, reason, kind));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
65
src/mjolnir/ListRule.js
Normal file
65
src/mjolnir/ListRule.js
Normal file
@ -0,0 +1,65 @@
|
||||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
import {MatrixGlob} from "../utils/MatrixGlob";
|
||||
|
||||
// Inspiration largely taken from Mjolnir itself
|
||||
|
||||
export const RECOMMENDATION_BAN = "m.ban";
|
||||
export const RECOMMENDATION_BAN_TYPES = [RECOMMENDATION_BAN, "org.matrix.mjolnir.ban"];
|
||||
|
||||
export function recommendationToStable(recommendation: string, unstable = true): string {
|
||||
if (RECOMMENDATION_BAN_TYPES.includes(recommendation)) {
|
||||
return unstable ? RECOMMENDATION_BAN_TYPES[RECOMMENDATION_BAN_TYPES.length - 1] : RECOMMENDATION_BAN;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export class ListRule {
|
||||
_glob: MatrixGlob;
|
||||
_entity: string;
|
||||
_action: string;
|
||||
_reason: string;
|
||||
_kind: string;
|
||||
|
||||
constructor(entity: string, action: string, reason: string, kind: string) {
|
||||
this._glob = new MatrixGlob(entity);
|
||||
this._entity = entity;
|
||||
this._action = recommendationToStable(action, false);
|
||||
this._reason = reason;
|
||||
this._kind = kind;
|
||||
}
|
||||
|
||||
get entity(): string {
|
||||
return this._entity;
|
||||
}
|
||||
|
||||
get reason(): string {
|
||||
return this._reason;
|
||||
}
|
||||
|
||||
get kind(): string {
|
||||
return this._kind;
|
||||
}
|
||||
|
||||
get recommendation(): string {
|
||||
return this._action;
|
||||
}
|
||||
|
||||
isMatch(entity: string): boolean {
|
||||
return this._glob.test(entity);
|
||||
}
|
||||
}
|
189
src/mjolnir/Mjolnir.js
Normal file
189
src/mjolnir/Mjolnir.js
Normal file
@ -0,0 +1,189 @@
|
||||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
import MatrixClientPeg from "../MatrixClientPeg";
|
||||
import {ALL_RULE_TYPES, BanList} from "./BanList";
|
||||
import SettingsStore, {SettingLevel} from "../settings/SettingsStore";
|
||||
import {_t} from "../languageHandler";
|
||||
import dis from "../dispatcher";
|
||||
|
||||
// TODO: Move this and related files to the js-sdk or something once finalized.
|
||||
|
||||
export class Mjolnir {
|
||||
static _instance: Mjolnir = null;
|
||||
|
||||
_lists: BanList[] = [];
|
||||
_roomIds: string[] = [];
|
||||
_mjolnirWatchRef = null;
|
||||
_dispatcherRef = null;
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
get roomIds(): string[] {
|
||||
return this._roomIds;
|
||||
}
|
||||
|
||||
get lists(): BanList[] {
|
||||
return this._lists;
|
||||
}
|
||||
|
||||
start() {
|
||||
this._mjolnirWatchRef = SettingsStore.watchSetting("mjolnirRooms", null, this._onListsChanged.bind(this));
|
||||
|
||||
this._dispatcherRef = dis.register(this._onAction);
|
||||
dis.dispatch({
|
||||
action: 'do_after_sync_prepared',
|
||||
deferred_action: {action: 'setup_mjolnir'},
|
||||
});
|
||||
}
|
||||
|
||||
_onAction = (payload) => {
|
||||
if (payload['action'] === 'setup_mjolnir') {
|
||||
console.log("Setting up Mjolnir: after sync");
|
||||
this.setup();
|
||||
}
|
||||
};
|
||||
|
||||
setup() {
|
||||
if (!MatrixClientPeg.get()) return;
|
||||
this._updateLists(SettingsStore.getValue("mjolnirRooms"));
|
||||
MatrixClientPeg.get().on("RoomState.events", this._onEvent.bind(this));
|
||||
}
|
||||
|
||||
stop() {
|
||||
SettingsStore.unwatchSetting(this._mjolnirWatchRef);
|
||||
|
||||
try {
|
||||
if (this._dispatcherRef) dis.unregister(this._dispatcherRef);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
// Only the tests cause problems with this particular block of code. We should
|
||||
// never be here in production.
|
||||
}
|
||||
|
||||
if (!MatrixClientPeg.get()) return;
|
||||
MatrixClientPeg.get().removeListener("RoomState.events", this._onEvent.bind(this));
|
||||
}
|
||||
|
||||
async getOrCreatePersonalList(): Promise<BanList> {
|
||||
let personalRoomId = SettingsStore.getValue("mjolnirPersonalRoom");
|
||||
if (!personalRoomId) {
|
||||
const resp = await MatrixClientPeg.get().createRoom({
|
||||
name: _t("My Ban List"),
|
||||
topic: _t("This is your list of users/servers you have blocked - don't leave the room!"),
|
||||
preset: "private_chat",
|
||||
});
|
||||
personalRoomId = resp['room_id'];
|
||||
await SettingsStore.setValue(
|
||||
"mjolnirPersonalRoom", null, SettingLevel.ACCOUNT, personalRoomId);
|
||||
await SettingsStore.setValue(
|
||||
"mjolnirRooms", null, SettingLevel.ACCOUNT, [personalRoomId, ...this._roomIds]);
|
||||
}
|
||||
if (!personalRoomId) {
|
||||
throw new Error("Error finding a room ID to use");
|
||||
}
|
||||
|
||||
let list = this._lists.find(b => b.roomId === personalRoomId);
|
||||
if (!list) list = new BanList(personalRoomId);
|
||||
// we don't append the list to the tracked rooms because it should already be there.
|
||||
// we're just trying to get the caller some utility access to the list
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
// get without creating the list
|
||||
getPersonalList(): BanList {
|
||||
const personalRoomId = SettingsStore.getValue("mjolnirPersonalRoom");
|
||||
if (!personalRoomId) return null;
|
||||
|
||||
let list = this._lists.find(b => b.roomId === personalRoomId);
|
||||
if (!list) list = new BanList(personalRoomId);
|
||||
// we don't append the list to the tracked rooms because it should already be there.
|
||||
// we're just trying to get the caller some utility access to the list
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
async subscribeToList(roomId: string) {
|
||||
const roomIds = [...this._roomIds, roomId];
|
||||
await SettingsStore.setValue("mjolnirRooms", null, SettingLevel.ACCOUNT, roomIds);
|
||||
this._lists.push(new BanList(roomId));
|
||||
}
|
||||
|
||||
async unsubscribeFromList(roomId: string) {
|
||||
const roomIds = this._roomIds.filter(r => r !== roomId);
|
||||
await SettingsStore.setValue("mjolnirRooms", null, SettingLevel.ACCOUNT, roomIds);
|
||||
this._lists = this._lists.filter(b => b.roomId !== roomId);
|
||||
}
|
||||
|
||||
_onEvent(event) {
|
||||
if (!MatrixClientPeg.get()) return;
|
||||
if (!this._roomIds.includes(event.getRoomId())) return;
|
||||
if (!ALL_RULE_TYPES.includes(event.getType())) return;
|
||||
|
||||
this._updateLists(this._roomIds);
|
||||
}
|
||||
|
||||
_onListsChanged(settingName, roomId, atLevel, newValue) {
|
||||
// We know that ban lists are only recorded at one level so we don't need to re-eval them
|
||||
this._updateLists(newValue);
|
||||
}
|
||||
|
||||
_updateLists(listRoomIds: string[]) {
|
||||
if (!MatrixClientPeg.get()) return;
|
||||
|
||||
console.log("Updating Mjolnir ban lists to: " + listRoomIds);
|
||||
this._lists = [];
|
||||
this._roomIds = listRoomIds || [];
|
||||
if (!listRoomIds) return;
|
||||
|
||||
for (const roomId of listRoomIds) {
|
||||
// Creating the list updates it
|
||||
this._lists.push(new BanList(roomId));
|
||||
}
|
||||
}
|
||||
|
||||
isServerBanned(serverName: string): boolean {
|
||||
for (const list of this._lists) {
|
||||
for (const rule of list.serverRules) {
|
||||
if (rule.isMatch(serverName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
isUserBanned(userId: string): boolean {
|
||||
for (const list of this._lists) {
|
||||
for (const rule of list.userRules) {
|
||||
if (rule.isMatch(userId)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static sharedInstance(): Mjolnir {
|
||||
if (!Mjolnir._instance) {
|
||||
Mjolnir._instance = new Mjolnir();
|
||||
}
|
||||
return Mjolnir._instance;
|
||||
}
|
||||
}
|
||||
|
@ -126,6 +126,20 @@ export const SETTINGS = {
|
||||
supportedLevels: LEVELS_FEATURE,
|
||||
default: false,
|
||||
},
|
||||
"feature_mjolnir": {
|
||||
isFeature: true,
|
||||
displayName: _td("Try out new ways to ignore people (experimental)"),
|
||||
supportedLevels: LEVELS_FEATURE,
|
||||
default: false,
|
||||
},
|
||||
"mjolnirRooms": {
|
||||
supportedLevels: ['account'],
|
||||
default: [],
|
||||
},
|
||||
"mjolnirPersonalRoom": {
|
||||
supportedLevels: ['account'],
|
||||
default: null,
|
||||
},
|
||||
"feature_dm_verification": {
|
||||
isFeature: true,
|
||||
displayName: _td("Send verification requests in direct message"),
|
||||
|
@ -20,6 +20,7 @@ import MatrixClientPeg from '../MatrixClientPeg';
|
||||
import sdk from '../index';
|
||||
import Modal from '../Modal';
|
||||
import { _t } from '../languageHandler';
|
||||
import { getCachedRoomIDForAlias, storeRoomAliasInCache } from '../RoomAliasCache';
|
||||
|
||||
const INITIAL_STATE = {
|
||||
// Whether we're joining the currently viewed room (see isJoining())
|
||||
@ -137,7 +138,7 @@ class RoomViewStore extends Store {
|
||||
}
|
||||
}
|
||||
|
||||
_viewRoom(payload) {
|
||||
async _viewRoom(payload) {
|
||||
if (payload.room_id) {
|
||||
const newState = {
|
||||
roomId: payload.room_id,
|
||||
@ -176,34 +177,44 @@ class RoomViewStore extends Store {
|
||||
this._joinRoom(payload);
|
||||
}
|
||||
} else if (payload.room_alias) {
|
||||
// Resolve the alias and then do a second dispatch with the room ID acquired
|
||||
this._setState({
|
||||
roomId: null,
|
||||
initialEventId: null,
|
||||
initialEventPixelOffset: null,
|
||||
isInitialEventHighlighted: null,
|
||||
roomAlias: payload.room_alias,
|
||||
roomLoading: true,
|
||||
roomLoadError: null,
|
||||
});
|
||||
MatrixClientPeg.get().getRoomIdForAlias(payload.room_alias).done(
|
||||
(result) => {
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: result.room_id,
|
||||
event_id: payload.event_id,
|
||||
highlighted: payload.highlighted,
|
||||
room_alias: payload.room_alias,
|
||||
auto_join: payload.auto_join,
|
||||
oob_data: payload.oob_data,
|
||||
});
|
||||
}, (err) => {
|
||||
dis.dispatch({
|
||||
action: 'view_room_error',
|
||||
room_id: null,
|
||||
room_alias: payload.room_alias,
|
||||
err: err,
|
||||
// Try the room alias to room ID navigation cache first to avoid
|
||||
// blocking room navigation on the homeserver.
|
||||
let roomId = getCachedRoomIDForAlias(payload.room_alias);
|
||||
if (!roomId) {
|
||||
// Room alias cache miss, so let's ask the homeserver. Resolve the alias
|
||||
// and then do a second dispatch with the room ID acquired.
|
||||
this._setState({
|
||||
roomId: null,
|
||||
initialEventId: null,
|
||||
initialEventPixelOffset: null,
|
||||
isInitialEventHighlighted: null,
|
||||
roomAlias: payload.room_alias,
|
||||
roomLoading: true,
|
||||
roomLoadError: null,
|
||||
});
|
||||
try {
|
||||
const result = await MatrixClientPeg.get().getRoomIdForAlias(payload.room_alias);
|
||||
storeRoomAliasInCache(payload.room_alias, result.room_id);
|
||||
roomId = result.room_id;
|
||||
} catch (err) {
|
||||
dis.dispatch({
|
||||
action: 'view_room_error',
|
||||
room_id: null,
|
||||
room_alias: payload.room_alias,
|
||||
err,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: roomId,
|
||||
event_id: payload.event_id,
|
||||
highlighted: payload.highlighted,
|
||||
room_alias: payload.room_alias,
|
||||
auto_join: payload.auto_join,
|
||||
oob_data: payload.oob_data,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
53
src/utils/MatrixGlob.js
Normal file
53
src/utils/MatrixGlob.js
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
import globToRegexp from "glob-to-regexp";
|
||||
|
||||
// Taken with permission from matrix-bot-sdk:
|
||||
// https://github.com/turt2live/matrix-js-bot-sdk/blob/eb148c2ecec7bf3ade801d73deb43df042d55aef/src/MatrixGlob.ts
|
||||
|
||||
/**
|
||||
* Represents a common Matrix glob. This is commonly used
|
||||
* for server ACLs and similar functions.
|
||||
*/
|
||||
export class MatrixGlob {
|
||||
_regex: RegExp;
|
||||
|
||||
/**
|
||||
* Creates a new Matrix Glob
|
||||
* @param {string} glob The glob to convert. Eg: "*.example.org"
|
||||
*/
|
||||
constructor(glob: string) {
|
||||
const globRegex = globToRegexp(glob, {
|
||||
extended: false,
|
||||
globstar: false,
|
||||
});
|
||||
|
||||
// We need to convert `?` manually because globToRegexp's extended mode
|
||||
// does more than we want it to.
|
||||
const replaced = globRegex.toString().replace(/\\\?/g, ".");
|
||||
this._regex = new RegExp(replaced.substring(1, replaced.length - 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the glob against a value, returning true if it matches.
|
||||
* @param {string} val The value to test.
|
||||
* @returns {boolean} True if the value matches the glob, false otherwise.
|
||||
*/
|
||||
test(val: string): boolean {
|
||||
return this._regex.test(val);
|
||||
}
|
||||
}
|
@ -3674,6 +3674,11 @@ glob-to-regexp@^0.3.0:
|
||||
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
|
||||
integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=
|
||||
|
||||
glob-to-regexp@^0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
|
||||
integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
|
||||
|
||||
glob@7.1.2:
|
||||
version "7.1.2"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
|
||||
|
Loading…
Reference in New Issue
Block a user