Show invite reasons
Displays the reason for invitation in the invitation dialog, requiring a click to reveal the message. Signed-off-by: Robin Townsend <robin@robin.town>
This commit is contained in:
parent
667c94b387
commit
f0333b5b1c
9 changed files with 144 additions and 31 deletions
|
@ -40,6 +40,35 @@ limitations under the License.
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_RoomPreviewBar_reason {
|
||||||
|
text-align: left;
|
||||||
|
background-color: $primary-bg-color;
|
||||||
|
border: 1px solid $invite-reason-border-color;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 0 16px 12px 16px;
|
||||||
|
margin: 5px 0 20px 0;
|
||||||
|
|
||||||
|
div {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_msgOption {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_MatrixChat_useCompactLayout & {
|
||||||
|
padding-top: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mx_EventTilePreview_faded {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.mx_SenderProfile, .mx_EventTile_avatar {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.mx_Spinner {
|
.mx_Spinner {
|
||||||
width: auto;
|
width: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
|
|
|
@ -205,6 +205,8 @@ $user-tile-hover-bg-color: $header-panel-bg-color;
|
||||||
// Appearance tab colors
|
// Appearance tab colors
|
||||||
$appearance-tab-border-color: $room-highlight-color;
|
$appearance-tab-border-color: $room-highlight-color;
|
||||||
|
|
||||||
|
$invite-reason-border-color: $room-highlight-color;
|
||||||
|
|
||||||
// blur amounts for left left panel (only for element theme, used in _mods.scss)
|
// blur amounts for left left panel (only for element theme, used in _mods.scss)
|
||||||
$roomlist-background-blur-amount: 60px;
|
$roomlist-background-blur-amount: 60px;
|
||||||
$groupFilterPanel-background-blur-amount: 30px;
|
$groupFilterPanel-background-blur-amount: 30px;
|
||||||
|
|
|
@ -200,6 +200,8 @@ $user-tile-hover-bg-color: $header-panel-bg-color;
|
||||||
// Appearance tab colors
|
// Appearance tab colors
|
||||||
$appearance-tab-border-color: $room-highlight-color;
|
$appearance-tab-border-color: $room-highlight-color;
|
||||||
|
|
||||||
|
$invite-reason-border-color: $room-highlight-color;
|
||||||
|
|
||||||
$composer-shadow-color: tranparent;
|
$composer-shadow-color: tranparent;
|
||||||
|
|
||||||
// ***** Mixins! *****
|
// ***** Mixins! *****
|
||||||
|
|
|
@ -324,6 +324,8 @@ $user-tile-hover-bg-color: $header-panel-bg-color;
|
||||||
// FontSlider colors
|
// FontSlider colors
|
||||||
$appearance-tab-border-color: $input-darker-bg-color;
|
$appearance-tab-border-color: $input-darker-bg-color;
|
||||||
|
|
||||||
|
$invite-reason-border-color: $input-darker-bg-color;
|
||||||
|
|
||||||
$composer-shadow-color: tranparent;
|
$composer-shadow-color: tranparent;
|
||||||
|
|
||||||
// ***** Mixins! *****
|
// ***** Mixins! *****
|
||||||
|
|
|
@ -325,6 +325,8 @@ $user-tile-hover-bg-color: $header-panel-bg-color;
|
||||||
// FontSlider colors
|
// FontSlider colors
|
||||||
$appearance-tab-border-color: $input-darker-bg-color;
|
$appearance-tab-border-color: $input-darker-bg-color;
|
||||||
|
|
||||||
|
$invite-reason-border-color: $input-darker-bg-color;
|
||||||
|
|
||||||
// blur amounts for left left panel (only for element theme, used in _mods.scss)
|
// blur amounts for left left panel (only for element theme, used in _mods.scss)
|
||||||
$roomlist-background-blur-amount: 40px;
|
$roomlist-background-blur-amount: 40px;
|
||||||
$groupFilterPanel-background-blur-amount: 20px;
|
$groupFilterPanel-background-blur-amount: 20px;
|
||||||
|
|
|
@ -19,7 +19,6 @@ import classnames from 'classnames';
|
||||||
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
|
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
|
||||||
|
|
||||||
import * as Avatar from '../../../Avatar';
|
import * as Avatar from '../../../Avatar';
|
||||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
|
||||||
import EventTile from '../rooms/EventTile';
|
import EventTile from '../rooms/EventTile';
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import {Layout} from "../../../settings/Layout";
|
import {Layout} from "../../../settings/Layout";
|
||||||
|
@ -40,61 +39,84 @@ interface IProps {
|
||||||
* classnames to apply to the wrapper of the preview
|
* classnames to apply to the wrapper of the preview
|
||||||
*/
|
*/
|
||||||
className: string;
|
className: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ID of the displayed user
|
||||||
|
*/
|
||||||
|
userId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The display name of the displayed user
|
||||||
|
*/
|
||||||
|
displayName?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The mxc:// avatar URL of the displayed user
|
||||||
|
*/
|
||||||
|
avatarUrl?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the EventTile should appear faded
|
||||||
|
*/
|
||||||
|
faded?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for when the component is clicked
|
||||||
|
*/
|
||||||
|
onClick?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable camelcase */
|
|
||||||
interface IState {
|
interface IState {
|
||||||
userId: string;
|
message: string;
|
||||||
displayname: string;
|
faded: boolean;
|
||||||
avatar_url: string;
|
eventTileKey: number;
|
||||||
}
|
}
|
||||||
/* eslint-enable camelcase */
|
|
||||||
|
|
||||||
const AVATAR_SIZE = 32;
|
const AVATAR_SIZE = 32;
|
||||||
|
|
||||||
export default class EventTilePreview extends React.Component<IProps, IState> {
|
export default class EventTilePreview extends React.Component<IProps, IState> {
|
||||||
constructor(props: IProps) {
|
constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
userId: "@erim:fink.fink",
|
message: props.message,
|
||||||
displayname: "Erimayas Fink",
|
faded: !!props.faded,
|
||||||
avatar_url: null,
|
eventTileKey: 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
changeMessage(message: string) {
|
||||||
// Fetch current user data
|
|
||||||
const client = MatrixClientPeg.get();
|
|
||||||
const userId = client.getUserId();
|
|
||||||
const profileInfo = await client.getProfileInfo(userId);
|
|
||||||
const avatarUrl = Avatar.avatarUrlForUser(
|
|
||||||
{avatarUrl: profileInfo.avatar_url},
|
|
||||||
AVATAR_SIZE, AVATAR_SIZE, "crop");
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
userId,
|
message,
|
||||||
displayname: profileInfo.displayname,
|
// Change the EventTile key to force React to create a new instance
|
||||||
avatar_url: avatarUrl,
|
eventTileKey: this.state.eventTileKey + 1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private fakeEvent({userId, displayname, avatar_url: avatarUrl}: IState) {
|
unfade() {
|
||||||
|
this.setState({ faded: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
private fakeEvent({message}: IState) {
|
||||||
|
const avatarUrl = Avatar.avatarUrlForUser(
|
||||||
|
{ avatarUrl: this.props.avatarUrl },
|
||||||
|
AVATAR_SIZE, AVATAR_SIZE, "crop",
|
||||||
|
);
|
||||||
|
|
||||||
// Fake it till we make it
|
// Fake it till we make it
|
||||||
/* eslint-disable quote-props */
|
/* eslint-disable quote-props */
|
||||||
const rawEvent = {
|
const rawEvent = {
|
||||||
type: "m.room.message",
|
type: "m.room.message",
|
||||||
sender: userId,
|
sender: this.props.userId,
|
||||||
content: {
|
content: {
|
||||||
"m.new_content": {
|
"m.new_content": {
|
||||||
msgtype: "m.text",
|
msgtype: "m.text",
|
||||||
body: this.props.message,
|
body: message,
|
||||||
displayname: displayname,
|
displayname: this.props.displayName,
|
||||||
avatar_url: avatarUrl,
|
avatar_url: avatarUrl,
|
||||||
},
|
},
|
||||||
msgtype: "m.text",
|
msgtype: "m.text",
|
||||||
body: this.props.message,
|
body: message,
|
||||||
displayname: displayname,
|
displayname: this.props.displayName,
|
||||||
avatar_url: avatarUrl,
|
avatar_url: avatarUrl,
|
||||||
},
|
},
|
||||||
unsigned: {
|
unsigned: {
|
||||||
|
@ -108,8 +130,8 @@ export default class EventTilePreview extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
// Fake it more
|
// Fake it more
|
||||||
event.sender = {
|
event.sender = {
|
||||||
name: displayname,
|
name: this.props.displayName,
|
||||||
userId: userId,
|
userId: this.props.userId,
|
||||||
getAvatarUrl: (..._) => {
|
getAvatarUrl: (..._) => {
|
||||||
return avatarUrl;
|
return avatarUrl;
|
||||||
},
|
},
|
||||||
|
@ -124,10 +146,12 @@ export default class EventTilePreview extends React.Component<IProps, IState> {
|
||||||
const className = classnames(this.props.className, {
|
const className = classnames(this.props.className, {
|
||||||
"mx_IRCLayout": this.props.layout == Layout.IRC,
|
"mx_IRCLayout": this.props.layout == Layout.IRC,
|
||||||
"mx_GroupLayout": this.props.layout == Layout.Group,
|
"mx_GroupLayout": this.props.layout == Layout.Group,
|
||||||
|
"mx_EventTilePreview_faded": this.state.faded,
|
||||||
});
|
});
|
||||||
|
|
||||||
return <div className={className}>
|
return <div className={className} onClick={this.props.onClick}>
|
||||||
<EventTile
|
<EventTile
|
||||||
|
key={this.state.eventTileKey}
|
||||||
mxEvent={event}
|
mxEvent={event}
|
||||||
layout={this.props.layout}
|
layout={this.props.layout}
|
||||||
enableFlair={SettingsStore.getValue(UIFeature.Flair)}
|
enableFlair={SettingsStore.getValue(UIFeature.Flair)}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import classNames from 'classnames';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import SdkConfig from "../../../SdkConfig";
|
import SdkConfig from "../../../SdkConfig";
|
||||||
import IdentityAuthClient from '../../../IdentityAuthClient';
|
import IdentityAuthClient from '../../../IdentityAuthClient';
|
||||||
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore";
|
import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore";
|
||||||
import {UPDATE_EVENT} from "../../../stores/AsyncStore";
|
import {UPDATE_EVENT} from "../../../stores/AsyncStore";
|
||||||
|
|
||||||
|
@ -300,10 +301,12 @@ export default class RoomPreviewBar extends React.Component {
|
||||||
const brand = SdkConfig.get().brand;
|
const brand = SdkConfig.get().brand;
|
||||||
const Spinner = sdk.getComponent('elements.Spinner');
|
const Spinner = sdk.getComponent('elements.Spinner');
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||||
|
const EventTilePreview = sdk.getComponent('elements.EventTilePreview');
|
||||||
|
|
||||||
let showSpinner = false;
|
let showSpinner = false;
|
||||||
let title;
|
let title;
|
||||||
let subTitle;
|
let subTitle;
|
||||||
|
let reasonElement;
|
||||||
let primaryActionHandler;
|
let primaryActionHandler;
|
||||||
let primaryActionLabel;
|
let primaryActionLabel;
|
||||||
let secondaryActionHandler;
|
let secondaryActionHandler;
|
||||||
|
@ -489,6 +492,29 @@ export default class RoomPreviewBar extends React.Component {
|
||||||
primaryActionLabel = _t("Accept");
|
primaryActionLabel = _t("Accept");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const myUserId = MatrixClientPeg.get().getUserId();
|
||||||
|
const reason = this.props.room.currentState.getMember(myUserId).events.member.event.content.reason;
|
||||||
|
if (reason) {
|
||||||
|
this.reasonElement = React.createRef();
|
||||||
|
// We hide the reason for invitation by default, since it can be a
|
||||||
|
// vector for spam/harassment.
|
||||||
|
const showReason = () => {
|
||||||
|
this.reasonElement.current.unfade();
|
||||||
|
this.reasonElement.current.changeMessage(reason);
|
||||||
|
};
|
||||||
|
reasonElement = <EventTilePreview
|
||||||
|
ref={this.reasonElement}
|
||||||
|
onClick={showReason}
|
||||||
|
className="mx_RoomPreviewBar_reason"
|
||||||
|
message={_t("Invite messages are hidden by default. Click to show the message.")}
|
||||||
|
layout={SettingsStore.getValue("layout")}
|
||||||
|
userId={inviteMember.userId}
|
||||||
|
displayName={inviteMember.rawDisplayName}
|
||||||
|
avatarUrl={inviteMember.events.member.event.content.avatar_url}
|
||||||
|
faded={true}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
primaryActionHandler = this.props.onJoinClick;
|
primaryActionHandler = this.props.onJoinClick;
|
||||||
secondaryActionLabel = _t("Reject");
|
secondaryActionLabel = _t("Reject");
|
||||||
secondaryActionHandler = this.props.onRejectClick;
|
secondaryActionHandler = this.props.onRejectClick;
|
||||||
|
@ -580,6 +606,7 @@ export default class RoomPreviewBar extends React.Component {
|
||||||
{ titleElement }
|
{ titleElement }
|
||||||
{ subTitleElements }
|
{ subTitleElements }
|
||||||
</div>
|
</div>
|
||||||
|
{ reasonElement }
|
||||||
<div className="mx_RoomPreviewBar_actions">
|
<div className="mx_RoomPreviewBar_actions">
|
||||||
{ secondaryButton }
|
{ secondaryButton }
|
||||||
{ extraComponents }
|
{ extraComponents }
|
||||||
|
|
|
@ -18,6 +18,7 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {_t} from "../../../../../languageHandler";
|
import {_t} from "../../../../../languageHandler";
|
||||||
import SdkConfig from "../../../../../SdkConfig";
|
import SdkConfig from "../../../../../SdkConfig";
|
||||||
|
import { MatrixClientPeg } from '../../../../../MatrixClientPeg';
|
||||||
import SettingsStore from "../../../../../settings/SettingsStore";
|
import SettingsStore from "../../../../../settings/SettingsStore";
|
||||||
import { enumerateThemes } from "../../../../../theme";
|
import { enumerateThemes } from "../../../../../theme";
|
||||||
import ThemeWatcher from "../../../../../settings/watchers/ThemeWatcher";
|
import ThemeWatcher from "../../../../../settings/watchers/ThemeWatcher";
|
||||||
|
@ -62,6 +63,10 @@ interface IState extends IThemeState {
|
||||||
systemFont: string;
|
systemFont: string;
|
||||||
showAdvanced: boolean;
|
showAdvanced: boolean;
|
||||||
layout: Layout;
|
layout: Layout;
|
||||||
|
// User profile data for the message preview
|
||||||
|
userId: string;
|
||||||
|
displayName: string;
|
||||||
|
avatarUrl: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -83,9 +88,25 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
|
||||||
systemFont: SettingsStore.getValue("systemFont"),
|
systemFont: SettingsStore.getValue("systemFont"),
|
||||||
showAdvanced: false,
|
showAdvanced: false,
|
||||||
layout: SettingsStore.getValue("layout"),
|
layout: SettingsStore.getValue("layout"),
|
||||||
|
userId: "@erim:fink.fink",
|
||||||
|
displayName: "Erimayas Fink",
|
||||||
|
avatarUrl: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async componentDidMount() {
|
||||||
|
// Fetch the current user profile for the message preview
|
||||||
|
const client = MatrixClientPeg.get();
|
||||||
|
const userId = client.getUserId();
|
||||||
|
const profileInfo = await client.getProfileInfo(userId);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
userId,
|
||||||
|
displayName: profileInfo.displayname,
|
||||||
|
avatarUrl: profileInfo.avatar_url,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private calculateThemeState(): IThemeState {
|
private calculateThemeState(): IThemeState {
|
||||||
// We have to mirror the logic from ThemeWatcher.getEffectiveTheme so we
|
// We have to mirror the logic from ThemeWatcher.getEffectiveTheme so we
|
||||||
// show the right values for things.
|
// show the right values for things.
|
||||||
|
@ -306,6 +327,9 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
|
||||||
className="mx_AppearanceUserSettingsTab_fontSlider_preview"
|
className="mx_AppearanceUserSettingsTab_fontSlider_preview"
|
||||||
message={this.MESSAGE_PREVIEW_TEXT}
|
message={this.MESSAGE_PREVIEW_TEXT}
|
||||||
layout={this.state.layout}
|
layout={this.state.layout}
|
||||||
|
userId={this.state.userId}
|
||||||
|
displayName={this.state.displayName}
|
||||||
|
avatarUrl={this.state.avatarUrl}
|
||||||
/>
|
/>
|
||||||
<div className="mx_AppearanceUserSettingsTab_fontSlider">
|
<div className="mx_AppearanceUserSettingsTab_fontSlider">
|
||||||
<div className="mx_AppearanceUserSettingsTab_fontSlider_smallText">Aa</div>
|
<div className="mx_AppearanceUserSettingsTab_fontSlider_smallText">Aa</div>
|
||||||
|
|
|
@ -1525,6 +1525,7 @@
|
||||||
"Start chatting": "Start chatting",
|
"Start chatting": "Start chatting",
|
||||||
"Do you want to join %(roomName)s?": "Do you want to join %(roomName)s?",
|
"Do you want to join %(roomName)s?": "Do you want to join %(roomName)s?",
|
||||||
"<userName/> invited you": "<userName/> invited you",
|
"<userName/> invited you": "<userName/> invited you",
|
||||||
|
"Invite messages are hidden by default. Click to show the message.": "Invite messages are hidden by default. Click to show the message.",
|
||||||
"Reject": "Reject",
|
"Reject": "Reject",
|
||||||
"Reject & Ignore user": "Reject & Ignore user",
|
"Reject & Ignore user": "Reject & Ignore user",
|
||||||
"You're previewing %(roomName)s. Want to join it?": "You're previewing %(roomName)s. Want to join it?",
|
"You're previewing %(roomName)s. Want to join it?": "You're previewing %(roomName)s. Want to join it?",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue