Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into export-conversations

This commit is contained in:
Jaiwanth 2021-06-24 22:54:40 +05:30
commit 91b8b2ac5a
13 changed files with 229 additions and 203 deletions

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { ICryptoCallbacks, IDeviceTrustLevel, ISecretStorageKeyInfo } from 'matrix-js-sdk/src/matrix'; import { ICryptoCallbacks, ISecretStorageKeyInfo } from 'matrix-js-sdk/src/matrix';
import { MatrixClient } from 'matrix-js-sdk/src/client'; import { MatrixClient } from 'matrix-js-sdk/src/client';
import Modal from './Modal'; import Modal from './Modal';
import * as sdk from './index'; import * as sdk from './index';
@ -28,6 +28,7 @@ import AccessSecretStorageDialog from './components/views/dialogs/security/Acces
import RestoreKeyBackupDialog from './components/views/dialogs/security/RestoreKeyBackupDialog'; import RestoreKeyBackupDialog from './components/views/dialogs/security/RestoreKeyBackupDialog';
import SettingsStore from "./settings/SettingsStore"; import SettingsStore from "./settings/SettingsStore";
import SecurityCustomisations from "./customisations/Security"; import SecurityCustomisations from "./customisations/Security";
import { DeviceTrustLevel } from 'matrix-js-sdk/src/crypto/CrossSigning';
// This stores the secret storage private keys in memory for the JS SDK. This is // This stores the secret storage private keys in memory for the JS SDK. This is
// only meant to act as a cache to avoid prompting the user multiple times // only meant to act as a cache to avoid prompting the user multiple times
@ -244,7 +245,7 @@ async function onSecretRequested(
deviceId: string, deviceId: string,
requestId: string, requestId: string,
name: string, name: string,
deviceTrust: IDeviceTrustLevel, deviceTrust: DeviceTrustLevel,
): Promise<string> { ): Promise<string> {
console.log("onSecretRequested", userId, deviceId, requestId, name, deviceTrust); console.log("onSecretRequested", userId, deviceId, requestId, name, deviceTrust);
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();

View file

@ -32,76 +32,89 @@ function textForMemberEvent(ev): () => string | null {
const targetName = ev.target ? ev.target.name : ev.getStateKey(); const targetName = ev.target ? ev.target.name : ev.getStateKey();
const prevContent = ev.getPrevContent(); const prevContent = ev.getPrevContent();
const content = ev.getContent(); const content = ev.getContent();
const reason = content.reason;
const getReason = () => content.reason ? (_t('Reason') + ': ' + content.reason) : '';
switch (content.membership) { switch (content.membership) {
case 'invite': { case 'invite': {
const threePidContent = content.third_party_invite; const threePidContent = content.third_party_invite;
if (threePidContent) { if (threePidContent) {
if (threePidContent.display_name) { if (threePidContent.display_name) {
return () => _t('%(targetName)s accepted the invitation for %(displayName)s.', { return () => _t('%(targetName)s accepted the invitation for %(displayName)s', {
targetName, targetName,
displayName: threePidContent.display_name, displayName: threePidContent.display_name,
}); });
} else { } else {
return () => _t('%(targetName)s accepted an invitation.', {targetName}); return () => _t('%(targetName)s accepted an invitation', { targetName });
} }
} else { } else {
return () => _t('%(senderName)s invited %(targetName)s.', {senderName, targetName}); return () => _t('%(senderName)s invited %(targetName)s', { senderName, targetName });
} }
} }
case 'ban': case 'ban':
return () => _t('%(senderName)s banned %(targetName)s.', {senderName, targetName}) + ' ' + getReason(); return () => reason
? _t('%(senderName)s banned %(targetName)s: %(reason)s', { senderName, targetName, reason })
: _t('%(senderName)s banned %(targetName)s', { senderName, targetName });
case 'join': case 'join':
if (prevContent && prevContent.membership === 'join') { if (prevContent && prevContent.membership === 'join') {
if (prevContent.displayname && content.displayname && prevContent.displayname !== content.displayname) { if (prevContent.displayname && content.displayname && prevContent.displayname !== content.displayname) {
return () => _t('%(oldDisplayName)s changed their display name to %(displayName)s.', { return () => _t('%(oldDisplayName)s changed their display name to %(displayName)s', {
oldDisplayName: prevContent.displayname, oldDisplayName: prevContent.displayname,
displayName: content.displayname, displayName: content.displayname,
}); });
} else if (!prevContent.displayname && content.displayname) { } else if (!prevContent.displayname && content.displayname) {
return () => _t('%(senderName)s set their display name to %(displayName)s.', { return () => _t('%(senderName)s set their display name to %(displayName)s', {
senderName: ev.getSender(), senderName: ev.getSender(),
displayName: content.displayname, displayName: content.displayname,
}); });
} else if (prevContent.displayname && !content.displayname) { } else if (prevContent.displayname && !content.displayname) {
return () => _t('%(senderName)s removed their display name (%(oldDisplayName)s).', { return () => _t('%(senderName)s removed their display name (%(oldDisplayName)s)', {
senderName, senderName,
oldDisplayName: prevContent.displayname, oldDisplayName: prevContent.displayname,
}); });
} else if (prevContent.avatar_url && !content.avatar_url) { } else if (prevContent.avatar_url && !content.avatar_url) {
return () => _t('%(senderName)s removed their profile picture.', {senderName}); return () => _t('%(senderName)s removed their profile picture', { senderName });
} else if (prevContent.avatar_url && content.avatar_url && } else if (prevContent.avatar_url && content.avatar_url &&
prevContent.avatar_url !== content.avatar_url) { prevContent.avatar_url !== content.avatar_url) {
return () => _t('%(senderName)s changed their profile picture.', {senderName}); return () => _t('%(senderName)s changed their profile picture', { senderName });
} else if (!prevContent.avatar_url && content.avatar_url) { } else if (!prevContent.avatar_url && content.avatar_url) {
return () => _t('%(senderName)s set a profile picture.', {senderName}); return () => _t('%(senderName)s set a profile picture', { senderName });
} else if (SettingsStore.getValue("showHiddenEventsInTimeline")) { } else if (SettingsStore.getValue("showHiddenEventsInTimeline")) {
// This is a null rejoin, it will only be visible if the Labs option is enabled // This is a null rejoin, it will only be visible if using 'show hidden events' (labs)
return () => _t("%(senderName)s made no change.", {senderName}); return () => _t("%(senderName)s made no change", { senderName });
} else { } else {
return null; return null;
} }
} else { } else {
if (!ev.target) console.warn("Join message has no target! -- " + ev.getContent().state_key); if (!ev.target) console.warn("Join message has no target! -- " + ev.getContent().state_key);
return () => _t('%(targetName)s joined the room.', {targetName}); return () => _t('%(targetName)s joined the room', { targetName });
} }
case 'leave': case 'leave':
if (ev.getSender() === ev.getStateKey()) { if (ev.getSender() === ev.getStateKey()) {
if (prevContent.membership === "invite") { if (prevContent.membership === "invite") {
return () => _t('%(targetName)s rejected the invitation.', {targetName}); return () => _t('%(targetName)s rejected the invitation', { targetName });
} else { } else {
return () => _t('%(targetName)s left the room.', {targetName}); return () => reason
? _t('%(targetName)s left the room: %(reason)s', { targetName, reason })
: _t('%(targetName)s left the room', { targetName });
} }
} else if (prevContent.membership === "ban") { } else if (prevContent.membership === "ban") {
return () => _t('%(senderName)s unbanned %(targetName)s.', {senderName, targetName}); return () => _t('%(senderName)s unbanned %(targetName)s', { senderName, targetName });
} else if (prevContent.membership === "invite") { } else if (prevContent.membership === "invite") {
return () => _t('%(senderName)s withdrew %(targetName)s\'s invitation.', { return () => reason
? _t('%(senderName)s withdrew %(targetName)s\'s invitation: %(reason)s', {
senderName, senderName,
targetName, targetName,
}) + ' ' + getReason(); reason,
})
: _t('%(senderName)s withdrew %(targetName)s\'s invitation', { senderName, targetName })
} else if (prevContent.membership === "join") { } else if (prevContent.membership === "join") {
return () => _t('%(senderName)s kicked %(targetName)s.', {senderName, targetName}) + ' ' + getReason(); return () => reason
? _t('%(senderName)s kicked %(targetName)s: %(reason)s', {
senderName,
targetName,
reason,
})
: _t('%(senderName)s kicked %(targetName)s', { senderName, targetName });
} else { } else {
return null; return null;
} }

View file

@ -766,7 +766,7 @@ class VerificationExplorer extends React.PureComponent<IExplorerProps> {
render() { render() {
const cli = this.context; const cli = this.context;
const room = this.props.room; const room = this.props.room;
const inRoomChannel = cli.crypto._inRoomVerificationRequests; const inRoomChannel = cli.crypto.inRoomVerificationRequests;
const inRoomRequests = (inRoomChannel._requestsByRoomId || new Map()).get(room.roomId) || new Map(); const inRoomRequests = (inRoomChannel._requestsByRoomId || new Map()).get(room.roomId) || new Map();
return (<div> return (<div>

View file

@ -503,7 +503,7 @@ const isMuted = (member: RoomMember, powerLevelContent: IPowerLevelsContent) =>
return member.powerLevel < levelToSend; return member.powerLevel < levelToSend;
}; };
const getPowerLevels = room => room.currentState.getStateEvents(EventType.RoomPowerLevels, "")?.getContent() || {}; const getPowerLevels = room => room?.currentState?.getStateEvents(EventType.RoomPowerLevels, "")?.getContent() || {};
export const useRoomPowerLevels = (cli: MatrixClient, room: Room) => { export const useRoomPowerLevels = (cli: MatrixClient, room: Room) => {
const [powerLevels, setPowerLevels] = useState<IPowerLevelsContent>(getPowerLevels(room)); const [powerLevels, setPowerLevels] = useState<IPowerLevelsContent>(getPowerLevels(room));

View file

@ -17,13 +17,23 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import AccessibleButton from '../elements/AccessibleButton'; import AccessibleButton from '../elements/AccessibleButton';
import { _t } from '../../../languageHandler'; import { _td } from '../../../languageHandler';
import classNames from "classnames"; import classNames from "classnames";
import E2EIcon from './E2EIcon'; import E2EIcon from './E2EIcon';
import {replaceableComponent} from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import BaseAvatar from '../avatars/BaseAvatar';
import PresenceLabel from "./PresenceLabel";
export enum PowerStatus {
Admin = "admin",
Moderator = "moderator",
}
const PowerLabel: Record<PowerStatus, string> = {
[PowerStatus.Admin]: _td("Admin"),
[PowerStatus.Moderator]: _td("Mod"),
}
const PRESENCE_CLASS = { const PRESENCE_CLASS = {
"offline": "mx_EntityTile_offline", "offline": "mx_EntityTile_offline",
@ -31,14 +41,14 @@ const PRESENCE_CLASS = {
"unavailable": "mx_EntityTile_unavailable", "unavailable": "mx_EntityTile_unavailable",
}; };
function presenceClassForMember(presenceState, lastActiveAgo, showPresence) { function presenceClassForMember(presenceState: string, lastActiveAgo: number, showPresence: boolean): string {
if (showPresence === false) { if (showPresence === false) {
return 'mx_EntityTile_online_beenactive'; return 'mx_EntityTile_online_beenactive';
} }
// offline is split into two categories depending on whether we have // offline is split into two categories depending on whether we have
// a last_active_ago for them. // a last_active_ago for them.
if (presenceState == 'offline') { if (presenceState === 'offline') {
if (lastActiveAgo) { if (lastActiveAgo) {
return PRESENCE_CLASS['offline'] + '_beenactive'; return PRESENCE_CLASS['offline'] + '_beenactive';
} else { } else {
@ -51,29 +61,32 @@ function presenceClassForMember(presenceState, lastActiveAgo, showPresence) {
} }
} }
@replaceableComponent("views.rooms.EntityTile") interface IProps {
class EntityTile extends React.Component { name?: string;
static propTypes = { title?: string;
name: PropTypes.string, avatarJsx?: JSX.Element; // <BaseAvatar />
title: PropTypes.string, className?: string;
avatarJsx: PropTypes.any, // <BaseAvatar /> presenceState?: string;
className: PropTypes.string, presenceLastActiveAgo?: number;
presenceState: PropTypes.string, presenceLastTs?: number;
presenceLastActiveAgo: PropTypes.number, presenceCurrentlyActive?: boolean;
presenceLastTs: PropTypes.number, showInviteButton?: boolean;
presenceCurrentlyActive: PropTypes.bool, onClick?(): void;
showInviteButton: PropTypes.bool, suppressOnHover?: boolean;
shouldComponentUpdate: PropTypes.func, showPresence?: boolean;
onClick: PropTypes.func, subtextLabel?: string;
suppressOnHover: PropTypes.bool, e2eStatus?: string;
showPresence: PropTypes.bool, powerStatus?: PowerStatus;
subtextLabel: PropTypes.string, }
e2eStatus: PropTypes.string,
};
interface IState {
hover: boolean;
}
@replaceableComponent("views.rooms.EntityTile")
export default class EntityTile extends React.PureComponent<IProps, IState> {
static defaultProps = { static defaultProps = {
shouldComponentUpdate: function(nextProps, nextState) { return true; }, onClick: () => {},
onClick: function() {},
presenceState: "offline", presenceState: "offline",
presenceLastActiveAgo: 0, presenceLastActiveAgo: 0,
presenceLastTs: 0, presenceLastTs: 0,
@ -82,13 +95,12 @@ class EntityTile extends React.Component {
showPresence: true, showPresence: true,
}; };
state = { constructor(props: IProps) {
super(props);
this.state = {
hover: false, hover: false,
}; };
shouldComponentUpdate(nextProps, nextState) {
if (this.state.hover !== nextState.hover) return true;
return this.props.shouldComponentUpdate(nextProps, nextState);
} }
render() { render() {
@ -110,7 +122,6 @@ class EntityTile extends React.Component {
const activeAgo = this.props.presenceLastActiveAgo ? const activeAgo = this.props.presenceLastActiveAgo ?
(Date.now() - (this.props.presenceLastTs - this.props.presenceLastActiveAgo)) : -1; (Date.now() - (this.props.presenceLastTs - this.props.presenceLastActiveAgo)) : -1;
const PresenceLabel = sdk.getComponent("rooms.PresenceLabel");
let presenceLabel = null; let presenceLabel = null;
if (this.props.showPresence) { if (this.props.showPresence) {
presenceLabel = <PresenceLabel activeAgo={activeAgo} presenceLabel = <PresenceLabel activeAgo={activeAgo}
@ -155,10 +166,7 @@ class EntityTile extends React.Component {
let powerLabel; let powerLabel;
const powerStatus = this.props.powerStatus; const powerStatus = this.props.powerStatus;
if (powerStatus) { if (powerStatus) {
const powerText = { const powerText = PowerLabel[powerStatus];
[EntityTile.POWER_STATUS_MODERATOR]: _t("Mod"),
[EntityTile.POWER_STATUS_ADMIN]: _t("Admin"),
}[powerStatus];
powerLabel = <div className="mx_EntityTile_power">{powerText}</div>; powerLabel = <div className="mx_EntityTile_power">{powerText}</div>;
} }
@ -168,14 +176,12 @@ class EntityTile extends React.Component {
e2eIcon = <E2EIcon status={e2eStatus} isUser={true} bordered={true} />; e2eIcon = <E2EIcon status={e2eStatus} isUser={true} bordered={true} />;
} }
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const av = this.props.avatarJsx || const av = this.props.avatarJsx ||
<BaseAvatar name={this.props.name} width={36} height={36} aria-hidden="true" />; <BaseAvatar name={this.props.name} width={36} height={36} aria-hidden="true" />;
// The wrapping div is required to make the magic mouse listener work, for some reason. // The wrapping div is required to make the magic mouse listener work, for some reason.
return ( return (
<div ref={(c) => this.container = c} > <div>
<AccessibleButton <AccessibleButton
className={classNames(mainClassNames)} className={classNames(mainClassNames)}
title={this.props.title} title={this.props.title}
@ -193,8 +199,3 @@ class EntityTile extends React.Component {
); );
} }
} }
EntityTile.POWER_STATUS_MODERATOR = "moderator";
EntityTile.POWER_STATUS_ADMIN = "admin";
export default EntityTile;

View file

@ -17,20 +17,33 @@ limitations under the License.
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import * as sdk from "../../../index";
import dis from "../../../dispatcher/dispatcher"; import dis from "../../../dispatcher/dispatcher";
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { MatrixClientPeg } from "../../../MatrixClientPeg";
import {Action} from "../../../dispatcher/actions"; import {Action} from "../../../dispatcher/actions";
import {replaceableComponent} from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { EventType } from "matrix-js-sdk/src/@types/event";
import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo";
import EntityTile, { PowerStatus } from "./EntityTile";
import MemberAvatar from "./../avatars/MemberAvatar";
interface IProps {
member: RoomMember;
showPresence?: boolean;
}
interface IState {
statusMessage: string;
isRoomEncrypted: boolean;
e2eStatus: string;
}
@replaceableComponent("views.rooms.MemberTile") @replaceableComponent("views.rooms.MemberTile")
export default class MemberTile extends React.Component { export default class MemberTile extends React.Component<IProps, IState> {
static propTypes = { private userLastModifiedTime: number;
member: PropTypes.any.isRequired, // RoomMember private memberLastModifiedTime: number;
showPresence: PropTypes.bool,
};
static defaultProps = { static defaultProps = {
showPresence: true, showPresence: true,
@ -52,7 +65,7 @@ export default class MemberTile extends React.Component {
if (SettingsStore.getValue("feature_custom_status")) { if (SettingsStore.getValue("feature_custom_status")) {
const { user } = this.props.member; const { user } = this.props.member;
if (user) { if (user) {
user.on("User._unstable_statusMessage", this._onStatusMessageCommitted); user.on("User._unstable_statusMessage", this.onStatusMessageCommitted);
} }
} }
@ -80,7 +93,7 @@ export default class MemberTile extends React.Component {
if (user) { if (user) {
user.removeListener( user.removeListener(
"User._unstable_statusMessage", "User._unstable_statusMessage",
this._onStatusMessageCommitted, this.onStatusMessageCommitted,
); );
} }
@ -91,8 +104,8 @@ export default class MemberTile extends React.Component {
} }
} }
onRoomStateEvents = ev => { private onRoomStateEvents = (ev: MatrixEvent): void => {
if (ev.getType() !== "m.room.encryption") return; if (ev.getType() !== EventType.RoomEncryption) return;
const { roomId } = this.props.member; const { roomId } = this.props.member;
if (ev.getRoomId() !== roomId) return; if (ev.getRoomId() !== roomId) return;
@ -105,17 +118,17 @@ export default class MemberTile extends React.Component {
this.updateE2EStatus(); this.updateE2EStatus();
}; };
onUserTrustStatusChanged = (userId, trustStatus) => { private onUserTrustStatusChanged = (userId: string, trustStatus: string): void => {
if (userId !== this.props.member.userId) return; if (userId !== this.props.member.userId) return;
this.updateE2EStatus(); this.updateE2EStatus();
}; };
onDeviceVerificationChanged = (userId, deviceId, deviceInfo) => { private onDeviceVerificationChanged = (userId: string, deviceId: string, deviceInfo: DeviceInfo): void => {
if (userId !== this.props.member.userId) return; if (userId !== this.props.member.userId) return;
this.updateE2EStatus(); this.updateE2EStatus();
}; };
async updateE2EStatus() { private async updateE2EStatus(): Promise<void> {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const { userId } = this.props.member; const { userId } = this.props.member;
const isMe = userId === cli.getUserId(); const isMe = userId === cli.getUserId();
@ -143,32 +156,32 @@ export default class MemberTile extends React.Component {
}); });
} }
getStatusMessage() { private getStatusMessage(): string {
const { user } = this.props.member; const { user } = this.props.member;
if (!user) { if (!user) {
return ""; return "";
} }
return user._unstable_statusMessage; return user.unstable_statusMessage;
} }
_onStatusMessageCommitted = () => { private onStatusMessageCommitted = (): void => {
// The `User` object has observed a status message change. // The `User` object has observed a status message change.
this.setState({ this.setState({
statusMessage: this.getStatusMessage(), statusMessage: this.getStatusMessage(),
}); });
}; };
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps: IProps, nextState: IState): boolean {
if ( if (
this.member_last_modified_time === undefined || this.memberLastModifiedTime === undefined ||
this.member_last_modified_time < nextProps.member.getLastModifiedTime() this.memberLastModifiedTime < nextProps.member.getLastModifiedTime()
) { ) {
return true; return true;
} }
if ( if (
nextProps.member.user && nextProps.member.user &&
(this.user_last_modified_time === undefined || (this.userLastModifiedTime === undefined ||
this.user_last_modified_time < nextProps.member.user.getLastModifiedTime()) this.userLastModifiedTime < nextProps.member.user.getLastModifiedTime())
) { ) {
return true; return true;
} }
@ -181,18 +194,18 @@ export default class MemberTile extends React.Component {
return false; return false;
} }
onClick = e => { private onClick = (): void => {
dis.dispatch({ dis.dispatch({
action: Action.ViewUser, action: Action.ViewUser,
member: this.props.member, member: this.props.member,
}); });
}; };
_getDisplayName() { private getDisplayName(): string {
return this.props.member.name; return this.props.member.name;
} }
getPowerLabel() { private getPowerLabel(): string {
return _t("%(userName)s (power %(powerLevelNumber)s)", { return _t("%(userName)s (power %(powerLevelNumber)s)", {
userName: this.props.member.userId, userName: this.props.member.userId,
powerLevelNumber: this.props.member.powerLevel, powerLevelNumber: this.props.member.powerLevel,
@ -200,11 +213,8 @@ export default class MemberTile extends React.Component {
} }
render() { render() {
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
const EntityTile = sdk.getComponent('rooms.EntityTile');
const member = this.props.member; const member = this.props.member;
const name = this._getDisplayName(); const name = this.getDisplayName();
const presenceState = member.user ? member.user.presence : null; const presenceState = member.user ? member.user.presence : null;
let statusMessage = null; let statusMessage = null;
@ -217,13 +227,13 @@ export default class MemberTile extends React.Component {
); );
if (member.user) { if (member.user) {
this.user_last_modified_time = member.user.getLastModifiedTime(); this.userLastModifiedTime = member.user.getLastModifiedTime();
} }
this.member_last_modified_time = member.getLastModifiedTime(); this.memberLastModifiedTime = member.getLastModifiedTime();
const powerStatusMap = new Map([ const powerStatusMap = new Map([
[100, EntityTile.POWER_STATUS_ADMIN], [100, PowerStatus.Admin],
[50, EntityTile.POWER_STATUS_MODERATOR], [50, PowerStatus.Moderator],
]); ]);
// Find the nearest power level with a badge // Find the nearest power level with a badge

View file

@ -15,26 +15,23 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import {replaceableComponent} from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
@replaceableComponent("views.rooms.PresenceLabel") interface IProps {
export default class PresenceLabel extends React.Component {
static propTypes = {
// number of milliseconds ago this user was last active. // number of milliseconds ago this user was last active.
// zero = unknown // zero = unknown
activeAgo: PropTypes.number, activeAgo?: number;
// if true, activeAgo is an approximation and "Now" should // if true, activeAgo is an approximation and "Now" should
// be shown instead // be shown instead
currentlyActive: PropTypes.bool, currentlyActive?: boolean;
// offline, online, etc // offline, online, etc
presenceState: PropTypes.string, presenceState?: string;
}; }
@replaceableComponent("views.rooms.PresenceLabel")
export default class PresenceLabel extends React.Component<IProps> {
static defaultProps = { static defaultProps = {
activeAgo: -1, activeAgo: -1,
presenceState: null, presenceState: null,
@ -42,29 +39,29 @@ export default class PresenceLabel extends React.Component {
// Return duration as a string using appropriate time units // Return duration as a string using appropriate time units
// XXX: This would be better handled using a culture-aware library, but we don't use one yet. // XXX: This would be better handled using a culture-aware library, but we don't use one yet.
getDuration(time) { private getDuration(time: number): string {
if (!time) return; if (!time) return;
const t = parseInt(time / 1000); const t = time / 1000;
const s = t % 60; const s = t % 60;
const m = parseInt(t / 60) % 60; const m = t / 60 % 60;
const h = parseInt(t / (60 * 60)) % 24; const h = t / (60 * 60) % 24;
const d = parseInt(t / (60 * 60 * 24)); const d = t / (60 * 60 * 24);
if (t < 60) { if (t < 60) {
if (t < 0) { if (t < 0) {
return _t("%(duration)ss", {duration: 0}); return _t("%(duration)ss", { duration: 0 });
} }
return _t("%(duration)ss", {duration: s}); return _t("%(duration)ss", { duration: s });
} }
if (t < 60 * 60) { if (t < 60 * 60) {
return _t("%(duration)sm", {duration: m}); return _t("%(duration)sm", { duration: m });
} }
if (t < 24 * 60 * 60) { if (t < 24 * 60 * 60) {
return _t("%(duration)sh", {duration: h}); return _t("%(duration)sh", { duration: h });
} }
return _t("%(duration)sd", {duration: d}); return _t("%(duration)sd", { duration: d });
} }
getPrettyPresence(presence, activeAgo, currentlyActive) { private getPrettyPresence(presence: string, activeAgo: number, currentlyActive: boolean): string {
if (!currentlyActive && activeAgo !== undefined && activeAgo > 0) { if (!currentlyActive && activeAgo !== undefined && activeAgo > 0) {
const duration = this.getDuration(activeAgo); const duration = this.getDuration(activeAgo);
if (presence === "online") return _t("Online for %(duration)s", { duration: duration }); if (presence === "online") return _t("Online for %(duration)s", { duration: duration });

View file

@ -79,8 +79,8 @@ export default class CrossSigningPanel extends React.PureComponent {
async _getUpdatedStatus() { async _getUpdatedStatus() {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const pkCache = cli.getCrossSigningCacheCallbacks(); const pkCache = cli.getCrossSigningCacheCallbacks();
const crossSigning = cli.crypto._crossSigningInfo; const crossSigning = cli.crypto.crossSigningInfo;
const secretStorage = cli.crypto._secretStorage; const secretStorage = cli.crypto.secretStorage;
const crossSigningPublicKeysOnDevice = crossSigning.getId(); const crossSigningPublicKeysOnDevice = crossSigning.getId();
const crossSigningPrivateKeysInStorage = await crossSigning.isStoredInSecretStorage(secretStorage); const crossSigningPrivateKeysInStorage = await crossSigning.isStoredInSecretStorage(secretStorage);
const masterPrivateKeyCached = !!(pkCache && await pkCache.getCrossSigningKeyCache("master")); const masterPrivateKeyCached = !!(pkCache && await pkCache.getCrossSigningKeyCache("master"));

View file

@ -131,7 +131,7 @@ export default class SecureBackupPanel extends React.PureComponent {
async _getUpdatedDiagnostics() { async _getUpdatedDiagnostics() {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const secretStorage = cli.crypto._secretStorage; const secretStorage = cli.crypto.secretStorage;
const backupKeyStored = !!(await cli.isKeyBackupKeyStored()); const backupKeyStored = !!(await cli.isKeyBackupKeyStored());
const backupKeyFromCache = await cli.crypto.getSessionBackupPrivateKey(); const backupKeyFromCache = await cli.crypto.getSessionBackupPrivateKey();

View file

@ -491,24 +491,27 @@
"Converts the room to a DM": "Converts the room to a DM", "Converts the room to a DM": "Converts the room to a DM",
"Converts the DM to a room": "Converts the DM to a room", "Converts the DM to a room": "Converts the DM to a room",
"Displays action": "Displays action", "Displays action": "Displays action",
"Reason": "Reason", "%(targetName)s accepted the invitation for %(displayName)s": "%(targetName)s accepted the invitation for %(displayName)s",
"%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s accepted the invitation for %(displayName)s.", "%(targetName)s accepted an invitation": "%(targetName)s accepted an invitation",
"%(targetName)s accepted an invitation.": "%(targetName)s accepted an invitation.", "%(senderName)s invited %(targetName)s": "%(senderName)s invited %(targetName)s",
"%(senderName)s invited %(targetName)s.": "%(senderName)s invited %(targetName)s.", "%(senderName)s banned %(targetName)s: %(reason)s": "%(senderName)s banned %(targetName)s: %(reason)s",
"%(senderName)s banned %(targetName)s.": "%(senderName)s banned %(targetName)s.", "%(senderName)s banned %(targetName)s": "%(senderName)s banned %(targetName)s",
"%(oldDisplayName)s changed their display name to %(displayName)s.": "%(oldDisplayName)s changed their display name to %(displayName)s.", "%(oldDisplayName)s changed their display name to %(displayName)s": "%(oldDisplayName)s changed their display name to %(displayName)s",
"%(senderName)s set their display name to %(displayName)s.": "%(senderName)s set their display name to %(displayName)s.", "%(senderName)s set their display name to %(displayName)s": "%(senderName)s set their display name to %(displayName)s",
"%(senderName)s removed their display name (%(oldDisplayName)s).": "%(senderName)s removed their display name (%(oldDisplayName)s).", "%(senderName)s removed their display name (%(oldDisplayName)s)": "%(senderName)s removed their display name (%(oldDisplayName)s)",
"%(senderName)s removed their profile picture.": "%(senderName)s removed their profile picture.", "%(senderName)s removed their profile picture": "%(senderName)s removed their profile picture",
"%(senderName)s changed their profile picture.": "%(senderName)s changed their profile picture.", "%(senderName)s changed their profile picture": "%(senderName)s changed their profile picture",
"%(senderName)s set a profile picture.": "%(senderName)s set a profile picture.", "%(senderName)s set a profile picture": "%(senderName)s set a profile picture",
"%(senderName)s made no change.": "%(senderName)s made no change.", "%(senderName)s made no change": "%(senderName)s made no change",
"%(targetName)s joined the room.": "%(targetName)s joined the room.", "%(targetName)s joined the room": "%(targetName)s joined the room",
"%(targetName)s rejected the invitation.": "%(targetName)s rejected the invitation.", "%(targetName)s rejected the invitation": "%(targetName)s rejected the invitation",
"%(targetName)s left the room.": "%(targetName)s left the room.", "%(targetName)s left the room: %(reason)s": "%(targetName)s left the room: %(reason)s",
"%(senderName)s unbanned %(targetName)s.": "%(senderName)s unbanned %(targetName)s.", "%(targetName)s left the room": "%(targetName)s left the room",
"%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s withdrew %(targetName)s's invitation.", "%(senderName)s unbanned %(targetName)s": "%(senderName)s unbanned %(targetName)s",
"%(senderName)s kicked %(targetName)s.": "%(senderName)s kicked %(targetName)s.", "%(senderName)s withdrew %(targetName)s's invitation: %(reason)s": "%(senderName)s withdrew %(targetName)s's invitation: %(reason)s",
"%(senderName)s withdrew %(targetName)s's invitation": "%(senderName)s withdrew %(targetName)s's invitation",
"%(senderName)s kicked %(targetName)s: %(reason)s": "%(senderName)s kicked %(targetName)s: %(reason)s",
"%(senderName)s kicked %(targetName)s": "%(senderName)s kicked %(targetName)s",
"%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s changed the topic to \"%(topic)s\".", "%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s changed the topic to \"%(topic)s\".",
"%(senderDisplayName)s changed the room avatar.": "%(senderDisplayName)s changed the room avatar.", "%(senderDisplayName)s changed the room avatar.": "%(senderDisplayName)s changed the room avatar.",
"%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s removed the room name.", "%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s removed the room name.",
@ -1422,6 +1425,7 @@
"Failed to unban": "Failed to unban", "Failed to unban": "Failed to unban",
"Unban": "Unban", "Unban": "Unban",
"Banned by %(displayName)s": "Banned by %(displayName)s", "Banned by %(displayName)s": "Banned by %(displayName)s",
"Reason": "Reason",
"Error changing power level requirement": "Error changing power level requirement", "Error changing power level requirement": "Error changing power level requirement",
"An error occurred changing the room's power level requirements. Ensure you have sufficient permissions and try again.": "An error occurred changing the room's power level requirements. Ensure you have sufficient permissions and try again.", "An error occurred changing the room's power level requirements. Ensure you have sufficient permissions and try again.": "An error occurred changing the room's power level requirements. Ensure you have sufficient permissions and try again.",
"Error changing power level": "Error changing power level", "Error changing power level": "Error changing power level",

View file

@ -17,7 +17,7 @@ limitations under the License.
// The following interfaces take their names and member names from seshat and the spec // The following interfaces take their names and member names from seshat and the spec
/* eslint-disable camelcase */ /* eslint-disable camelcase */
export interface MatrixEvent { export interface IMatrixEvent {
type: string; type: string;
sender: string; sender: string;
content: {}; content: {};
@ -27,37 +27,37 @@ export interface MatrixEvent {
roomId: string; roomId: string;
} }
export interface MatrixProfile { export interface IMatrixProfile {
avatar_url: string; avatar_url: string;
displayname: string; displayname: string;
} }
export interface CrawlerCheckpoint { export interface ICrawlerCheckpoint {
roomId: string; roomId: string;
token: string; token: string;
fullCrawl?: boolean; fullCrawl?: boolean;
direction: string; direction: string;
} }
export interface ResultContext { export interface IResultContext {
events_before: [MatrixEvent]; events_before: [IMatrixEvent];
events_after: [MatrixEvent]; events_after: [IMatrixEvent];
profile_info: Map<string, MatrixProfile>; profile_info: Map<string, IMatrixProfile>;
} }
export interface ResultsElement { export interface IResultsElement {
rank: number; rank: number;
result: MatrixEvent; result: IMatrixEvent;
context: ResultContext; context: IResultContext;
} }
export interface SearchResult { export interface ISearchResult {
count: number; count: number;
results: [ResultsElement]; results: [IResultsElement];
highlights: [string]; highlights: [string];
} }
export interface SearchArgs { export interface ISearchArgs {
search_term: string; search_term: string;
before_limit: number; before_limit: number;
after_limit: number; after_limit: number;
@ -65,19 +65,19 @@ export interface SearchArgs {
room_id?: string; room_id?: string;
} }
export interface EventAndProfile { export interface IEventAndProfile {
event: MatrixEvent; event: IMatrixEvent;
profile: MatrixProfile; profile: IMatrixProfile;
} }
export interface LoadArgs { export interface ILoadArgs {
roomId: string; roomId: string;
limit: number; limit: number;
fromEvent?: string; fromEvent?: string;
direction?: string; direction?: string;
} }
export interface IndexStats { export interface IIndexStats {
size: number; size: number;
eventCount: number; eventCount: number;
roomCount: number; roomCount: number;
@ -119,13 +119,13 @@ export default abstract class BaseEventIndexManager {
* Queue up an event to be added to the index. * Queue up an event to be added to the index.
* *
* @param {MatrixEvent} ev The event that should be added to the index. * @param {MatrixEvent} ev The event that should be added to the index.
* @param {MatrixProfile} profile The profile of the event sender at the * @param {IMatrixProfile} profile The profile of the event sender at the
* time of the event receival. * time of the event receival.
* *
* @return {Promise} A promise that will resolve when the was queued up for * @return {Promise} A promise that will resolve when the was queued up for
* addition. * addition.
*/ */
async addEventToIndex(ev: MatrixEvent, profile: MatrixProfile): Promise<void> { async addEventToIndex(ev: IMatrixEvent, profile: IMatrixProfile): Promise<void> {
throw new Error("Unimplemented"); throw new Error("Unimplemented");
} }
@ -160,10 +160,10 @@ export default abstract class BaseEventIndexManager {
/** /**
* Get statistical information of the index. * Get statistical information of the index.
* *
* @return {Promise<IndexStats>} A promise that will resolve to the index * @return {Promise<IIndexStats>} A promise that will resolve to the index
* statistics. * statistics.
*/ */
async getStats(): Promise<IndexStats> { async getStats(): Promise<IIndexStats> {
throw new Error("Unimplemented"); throw new Error("Unimplemented");
} }
@ -203,13 +203,13 @@ export default abstract class BaseEventIndexManager {
/** /**
* Search the event index using the given term for matching events. * Search the event index using the given term for matching events.
* *
* @param {SearchArgs} searchArgs The search configuration for the search, * @param {ISearchArgs} searchArgs The search configuration for the search,
* sets the search term and determines the search result contents. * sets the search term and determines the search result contents.
* *
* @return {Promise<[SearchResult]>} A promise that will resolve to an array * @return {Promise<[ISearchResult]>} A promise that will resolve to an array
* of search results once the search is done. * of search results once the search is done.
*/ */
async searchEventIndex(searchArgs: SearchArgs): Promise<SearchResult> { async searchEventIndex(searchArgs: ISearchArgs): Promise<ISearchResult> {
throw new Error("Unimplemented"); throw new Error("Unimplemented");
} }
@ -218,12 +218,12 @@ export default abstract class BaseEventIndexManager {
* *
* This is used to add a batch of events to the index. * This is used to add a batch of events to the index.
* *
* @param {[EventAndProfile]} events The list of events and profiles that * @param {[IEventAndProfile]} events The list of events and profiles that
* should be added to the event index. * should be added to the event index.
* @param {[CrawlerCheckpoint]} checkpoint A new crawler checkpoint that * @param {[ICrawlerCheckpoint]} checkpoint A new crawler checkpoint that
* should be stored in the index which should be used to continue crawling * should be stored in the index which should be used to continue crawling
* the room. * the room.
* @param {[CrawlerCheckpoint]} oldCheckpoint The checkpoint that was used * @param {[ICrawlerCheckpoint]} oldCheckpoint The checkpoint that was used
* to fetch the current batch of events. This checkpoint will be removed * to fetch the current batch of events. This checkpoint will be removed
* from the index. * from the index.
* *
@ -231,9 +231,9 @@ export default abstract class BaseEventIndexManager {
* were already added to the index, false otherwise. * were already added to the index, false otherwise.
*/ */
async addHistoricEvents( async addHistoricEvents(
events: [EventAndProfile], events: IEventAndProfile[],
checkpoint: CrawlerCheckpoint | null, checkpoint: ICrawlerCheckpoint | null,
oldCheckpoint: CrawlerCheckpoint | null, oldCheckpoint: ICrawlerCheckpoint | null,
): Promise<boolean> { ): Promise<boolean> {
throw new Error("Unimplemented"); throw new Error("Unimplemented");
} }
@ -241,36 +241,36 @@ export default abstract class BaseEventIndexManager {
/** /**
* Add a new crawler checkpoint to the index. * Add a new crawler checkpoint to the index.
* *
* @param {CrawlerCheckpoint} checkpoint The checkpoint that should be added * @param {ICrawlerCheckpoint} checkpoint The checkpoint that should be added
* to the index. * to the index.
* *
* @return {Promise} A promise that will resolve once the checkpoint has * @return {Promise} A promise that will resolve once the checkpoint has
* been stored. * been stored.
*/ */
async addCrawlerCheckpoint(checkpoint: CrawlerCheckpoint): Promise<void> { async addCrawlerCheckpoint(checkpoint: ICrawlerCheckpoint): Promise<void> {
throw new Error("Unimplemented"); throw new Error("Unimplemented");
} }
/** /**
* Add a new crawler checkpoint to the index. * Add a new crawler checkpoint to the index.
* *
* @param {CrawlerCheckpoint} checkpoint The checkpoint that should be * @param {ICrawlerCheckpoint} checkpoint The checkpoint that should be
* removed from the index. * removed from the index.
* *
* @return {Promise} A promise that will resolve once the checkpoint has * @return {Promise} A promise that will resolve once the checkpoint has
* been removed. * been removed.
*/ */
async removeCrawlerCheckpoint(checkpoint: CrawlerCheckpoint): Promise<void> { async removeCrawlerCheckpoint(checkpoint: ICrawlerCheckpoint): Promise<void> {
throw new Error("Unimplemented"); throw new Error("Unimplemented");
} }
/** /**
* Load the stored checkpoints from the index. * Load the stored checkpoints from the index.
* *
* @return {Promise<[CrawlerCheckpoint]>} A promise that will resolve to an * @return {Promise<[ICrawlerCheckpoint]>} A promise that will resolve to an
* array of crawler checkpoints once they have been loaded from the index. * array of crawler checkpoints once they have been loaded from the index.
*/ */
async loadCheckpoints(): Promise<[CrawlerCheckpoint]> { async loadCheckpoints(): Promise<ICrawlerCheckpoint[]> {
throw new Error("Unimplemented"); throw new Error("Unimplemented");
} }
@ -286,11 +286,11 @@ export default abstract class BaseEventIndexManager {
* @param {string} args.direction The direction to which we should continue * @param {string} args.direction The direction to which we should continue
* loading events from. This is used only if fromEvent is used as well. * loading events from. This is used only if fromEvent is used as well.
* *
* @return {Promise<[EventAndProfile]>} A promise that will resolve to an * @return {Promise<[IEventAndProfile]>} A promise that will resolve to an
* array of Matrix events that contain mxc URLs accompanied with the * array of Matrix events that contain mxc URLs accompanied with the
* historic profile of the sender. * historic profile of the sender.
*/ */
async loadFileEvents(args: LoadArgs): Promise<[EventAndProfile]> { async loadFileEvents(args: ILoadArgs): Promise<IEventAndProfile[]> {
throw new Error("Unimplemented"); throw new Error("Unimplemented");
} }

View file

@ -28,7 +28,7 @@ import { MatrixClientPeg } from "../MatrixClientPeg";
import { sleep } from "../utils/promise"; import { sleep } from "../utils/promise";
import SettingsStore from "../settings/SettingsStore"; import SettingsStore from "../settings/SettingsStore";
import { SettingLevel } from "../settings/SettingLevel"; import { SettingLevel } from "../settings/SettingLevel";
import {CrawlerCheckpoint, LoadArgs, SearchArgs} from "./BaseEventIndexManager"; import { ICrawlerCheckpoint, ILoadArgs, ISearchArgs } from "./BaseEventIndexManager";
// The time in ms that the crawler will wait loop iterations if there // The time in ms that the crawler will wait loop iterations if there
// have not been any checkpoints to consume in the last iteration. // have not been any checkpoints to consume in the last iteration.
@ -45,9 +45,9 @@ interface ICrawler {
* Event indexing class that wraps the platform specific event indexing. * Event indexing class that wraps the platform specific event indexing.
*/ */
export default class EventIndex extends EventEmitter { export default class EventIndex extends EventEmitter {
private crawlerCheckpoints: CrawlerCheckpoint[] = []; private crawlerCheckpoints: ICrawlerCheckpoint[] = [];
private crawler: ICrawler = null; private crawler: ICrawler = null;
private currentCheckpoint: CrawlerCheckpoint = null; private currentCheckpoint: ICrawlerCheckpoint = null;
public async init() { public async init() {
const indexManager = PlatformPeg.get().getEventIndexingManager(); const indexManager = PlatformPeg.get().getEventIndexingManager();
@ -111,14 +111,14 @@ export default class EventIndex extends EventEmitter {
const timeline = room.getLiveTimeline(); const timeline = room.getLiveTimeline();
const token = timeline.getPaginationToken("b"); const token = timeline.getPaginationToken("b");
const backCheckpoint: CrawlerCheckpoint = { const backCheckpoint: ICrawlerCheckpoint = {
roomId: room.roomId, roomId: room.roomId,
token: token, token: token,
direction: "b", direction: "b",
fullCrawl: true, fullCrawl: true,
}; };
const forwardCheckpoint: CrawlerCheckpoint = { const forwardCheckpoint: ICrawlerCheckpoint = {
roomId: room.roomId, roomId: room.roomId,
token: token, token: token,
direction: "f", direction: "f",
@ -668,13 +668,13 @@ export default class EventIndex extends EventEmitter {
/** /**
* Search the event index using the given term for matching events. * Search the event index using the given term for matching events.
* *
* @param {SearchArgs} searchArgs The search configuration for the search, * @param {ISearchArgs} searchArgs The search configuration for the search,
* sets the search term and determines the search result contents. * sets the search term and determines the search result contents.
* *
* @return {Promise<[SearchResult]>} A promise that will resolve to an array * @return {Promise<[SearchResult]>} A promise that will resolve to an array
* of search results once the search is done. * of search results once the search is done.
*/ */
public async search(searchArgs: SearchArgs) { public async search(searchArgs: ISearchArgs) {
const indexManager = PlatformPeg.get().getEventIndexingManager(); const indexManager = PlatformPeg.get().getEventIndexingManager();
return indexManager.searchEventIndex(searchArgs); return indexManager.searchEventIndex(searchArgs);
} }
@ -709,7 +709,7 @@ export default class EventIndex extends EventEmitter {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
const indexManager = PlatformPeg.get().getEventIndexingManager(); const indexManager = PlatformPeg.get().getEventIndexingManager();
const loadArgs: LoadArgs = { const loadArgs: ILoadArgs = {
roomId: room.roomId, roomId: room.roomId,
limit: limit, limit: limit,
}; };

View file

@ -86,8 +86,8 @@ async function collectBugReport(opts: IOpts = {}, gzipLogs = true) {
body.append('cross_signing_key', client.getCrossSigningId()); body.append('cross_signing_key', client.getCrossSigningId());
// add cross-signing status information // add cross-signing status information
const crossSigning = client.crypto._crossSigningInfo; const crossSigning = client.crypto.crossSigningInfo;
const secretStorage = client.crypto._secretStorage; const secretStorage = client.crypto.secretStorage;
body.append("cross_signing_ready", String(await client.isCrossSigningReady())); body.append("cross_signing_ready", String(await client.isCrossSigningReady()));
body.append("cross_signing_supported_by_hs", body.append("cross_signing_supported_by_hs",