Merge pull request #2347 from matrix-org/travis/custom-status
Custom status messages
This commit is contained in:
commit
2e1e536d5c
16 changed files with 396 additions and 5 deletions
|
@ -24,8 +24,10 @@
|
||||||
@import "./structures/_ViewSource.scss";
|
@import "./structures/_ViewSource.scss";
|
||||||
@import "./structures/login/_Login.scss";
|
@import "./structures/login/_Login.scss";
|
||||||
@import "./views/avatars/_BaseAvatar.scss";
|
@import "./views/avatars/_BaseAvatar.scss";
|
||||||
|
@import "./views/avatars/_MemberStatusMessageAvatar.scss";
|
||||||
@import "./views/context_menus/_MessageContextMenu.scss";
|
@import "./views/context_menus/_MessageContextMenu.scss";
|
||||||
@import "./views/context_menus/_RoomTileContextMenu.scss";
|
@import "./views/context_menus/_RoomTileContextMenu.scss";
|
||||||
|
@import "./views/context_menus/_StatusMessageContextMenu.scss";
|
||||||
@import "./views/context_menus/_TagTileContextMenu.scss";
|
@import "./views/context_menus/_TagTileContextMenu.scss";
|
||||||
@import "./views/dialogs/_BugReportDialog.scss";
|
@import "./views/dialogs/_BugReportDialog.scss";
|
||||||
@import "./views/dialogs/_ChangelogDialog.scss";
|
@import "./views/dialogs/_ChangelogDialog.scss";
|
||||||
|
|
20
res/css/views/avatars/_MemberStatusMessageAvatar.scss
Normal file
20
res/css/views/avatars/_MemberStatusMessageAvatar.scss
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 New Vector Ltd
|
||||||
|
|
||||||
|
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_MemberStatusMessageAvatar_hasStatus {
|
||||||
|
border: 2px solid $accent-color;
|
||||||
|
border-radius: 40px;
|
||||||
|
}
|
55
res/css/views/context_menus/_StatusMessageContextMenu.scss
Normal file
55
res/css/views/context_menus/_StatusMessageContextMenu.scss
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 New Vector Ltd
|
||||||
|
|
||||||
|
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_StatusMessageContextMenu_message {
|
||||||
|
display: inline-block;
|
||||||
|
border-radius: 3px 0 0 3px;
|
||||||
|
border: 1px solid $input-border-color;
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 7px 7px 7px 9px;
|
||||||
|
width: 135px;
|
||||||
|
background-color: $primary-bg-color !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_StatusMessageContextMenu_submit {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_StatusMessageContextMenu_submitFaded {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_StatusMessageContextMenu_submit img {
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_StatusMessageContextMenu hr {
|
||||||
|
border: 0.5px solid $menu-border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_StatusMessageContextMenu_clearIcon {
|
||||||
|
margin: 5px 15px 5px 5px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_StatusMessageContextMenu_clear {
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_StatusMessageContextMenu_hasStatus .mx_StatusMessageContextMenu_clear {
|
||||||
|
color: $warning-color;
|
||||||
|
}
|
|
@ -111,4 +111,12 @@ limitations under the License.
|
||||||
opacity: 0.25;
|
opacity: 0.25;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_EntityTile_subtext {
|
||||||
|
font-size: 11px;
|
||||||
|
opacity: 0.5;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: clip;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -110,3 +110,10 @@ limitations under the License.
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_MemberInfo_statusMessage {
|
||||||
|
font-size: 11px;
|
||||||
|
opacity: 0.5;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: clip;
|
||||||
|
}
|
||||||
|
|
|
@ -35,7 +35,19 @@ limitations under the License.
|
||||||
.mx_RoomTile_nameContainer {
|
.mx_RoomTile_nameContainer {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 180px;
|
width: 180px;
|
||||||
height: 24px;
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_RoomTile_subtext {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 0 0 0 7px;
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: clip;
|
||||||
|
position: relative;
|
||||||
|
bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomTile_avatar_container {
|
.mx_RoomTile_avatar_container {
|
||||||
|
@ -49,10 +61,14 @@ limitations under the License.
|
||||||
padding-left: 16px;
|
padding-left: 16px;
|
||||||
padding-right: 6px;
|
padding-right: 6px;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_RoomTile_hasSubtext .mx_RoomTile_avatar {
|
||||||
|
padding-top: 0;
|
||||||
|
vertical-align: super;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_RoomTile_dm {
|
.mx_RoomTile_dm {
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
17
res/img/icons-checkmark.svg
Normal file
17
res/img/icons-checkmark.svg
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 52.5 (67469) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>Tick</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="Custom-Status-Copy" transform="translate(-529.000000, -917.000000)" fill-rule="nonzero">
|
||||||
|
<g id="Tick" transform="translate(530.000000, 918.000000)">
|
||||||
|
<circle id="Oval" stroke="#6AAC8C" fill="#75CFA6" cx="9" cy="9" r="9"></circle>
|
||||||
|
<g id="Glyph" transform="translate(8.949747, 7.949747) rotate(-45.000000) translate(-8.949747, -7.949747) translate(4.449747, 5.449747)" fill="#FFFFFF">
|
||||||
|
<rect id="Rectangle" x="0" y="0" width="2" height="5"></rect>
|
||||||
|
<rect id="Rectangle" x="0" y="3" width="9" height="2"></rect>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
120
src/components/views/avatars/MemberStatusMessageAvatar.js
Normal file
120
src/components/views/avatars/MemberStatusMessageAvatar.js
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 New Vector Ltd
|
||||||
|
|
||||||
|
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 MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
|
import MemberAvatar from '../avatars/MemberAvatar';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import * as ContextualMenu from "../../structures/ContextualMenu";
|
||||||
|
import StatusMessageContextMenu from "../context_menus/StatusMessageContextMenu";
|
||||||
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
|
||||||
|
export default class MemberStatusMessageAvatar extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
member: PropTypes.object.isRequired,
|
||||||
|
width: PropTypes.number,
|
||||||
|
height: PropTypes.number,
|
||||||
|
resizeMethod: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
resizeMethod: 'crop',
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
if (this.props.member.userId !== MatrixClientPeg.get().getUserId()) {
|
||||||
|
throw new Error("Cannot use MemberStatusMessageAvatar on anyone but the logged in user");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
MatrixClientPeg.get().on("RoomState.events", this._onRoomStateEvents);
|
||||||
|
|
||||||
|
if (this.props.member.user) {
|
||||||
|
this.setState({message: this.props.member.user._unstable_statusMessage});
|
||||||
|
} else {
|
||||||
|
this.setState({message: ""});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (MatrixClientPeg.get()) {
|
||||||
|
MatrixClientPeg.get().removeListener("RoomState.events", this._onRoomStateEvents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_onRoomStateEvents = (ev, state) => {
|
||||||
|
if (ev.getStateKey() !== MatrixClientPeg.get().getUserId()) return;
|
||||||
|
if (ev.getType() !== "im.vector.user_status") return;
|
||||||
|
// TODO: We should be relying on `this.props.member.user._unstable_statusMessage`
|
||||||
|
// We don't currently because the js-sdk doesn't emit a specific event for this
|
||||||
|
// change, and we don't want to race it. This should be improved when we rip out
|
||||||
|
// the im.vector.user_status stuff and replace it with a complete solution.
|
||||||
|
this.setState({message: ev.getContent()["status"]});
|
||||||
|
};
|
||||||
|
|
||||||
|
_onClick = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
const elementRect = e.target.getBoundingClientRect();
|
||||||
|
|
||||||
|
// The window X and Y offsets are to adjust position when zoomed in to page
|
||||||
|
const x = (elementRect.left + window.pageXOffset) - (elementRect.width / 2) + 3;
|
||||||
|
const chevronOffset = 12;
|
||||||
|
let y = elementRect.top + (elementRect.height / 2) + window.pageYOffset;
|
||||||
|
y = y - (chevronOffset + 4); // where 4 is 1/4 the height of the chevron
|
||||||
|
|
||||||
|
ContextualMenu.createMenu(StatusMessageContextMenu, {
|
||||||
|
chevronOffset: chevronOffset,
|
||||||
|
chevronFace: 'bottom',
|
||||||
|
left: x,
|
||||||
|
top: y,
|
||||||
|
menuWidth: 190,
|
||||||
|
user: this.props.member.user,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (!SettingsStore.isFeatureEnabled("feature_custom_status")) {
|
||||||
|
return <MemberAvatar member={this.props.member}
|
||||||
|
width={this.props.width}
|
||||||
|
height={this.props.height}
|
||||||
|
resizeMethod={this.props.resizeMethod} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasStatus = this.props.member.user ? !!this.props.member.user._unstable_statusMessage : false;
|
||||||
|
|
||||||
|
const classes = classNames({
|
||||||
|
"mx_MemberStatusMessageAvatar": true,
|
||||||
|
"mx_MemberStatusMessageAvatar_hasStatus": hasStatus,
|
||||||
|
});
|
||||||
|
|
||||||
|
return <AccessibleButton onClick={this._onClick} className={classes} element="div">
|
||||||
|
<MemberAvatar member={this.props.member}
|
||||||
|
width={this.props.width}
|
||||||
|
height={this.props.height}
|
||||||
|
resizeMethod={this.props.resizeMethod} />
|
||||||
|
</AccessibleButton>;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 New Vector Ltd
|
||||||
|
|
||||||
|
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';
|
||||||
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
export default class StatusMessageContextMenu extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
// js-sdk User object. Not required because it might not exist.
|
||||||
|
user: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
message: props.user ? props.user._unstable_statusMessage : "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_onClearClick = async(e) => {
|
||||||
|
await MatrixClientPeg.get()._unstable_setStatusMessage("");
|
||||||
|
this.setState({message: ""});
|
||||||
|
};
|
||||||
|
|
||||||
|
_onSubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
MatrixClientPeg.get()._unstable_setStatusMessage(this.state.message);
|
||||||
|
};
|
||||||
|
|
||||||
|
_onStatusChange = (e) => {
|
||||||
|
this.setState({message: e.target.value});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const formSubmitClasses = classNames({
|
||||||
|
"mx_StatusMessageContextMenu_submit": true,
|
||||||
|
"mx_StatusMessageContextMenu_submitFaded": !this.state.message, // no message == faded
|
||||||
|
});
|
||||||
|
|
||||||
|
const form = <form className="mx_StatusMessageContextMenu_form" onSubmit={this._onSubmit} autoComplete="off">
|
||||||
|
<input type="text" key="message" placeholder={_t("Set a new status...")} autoFocus={true}
|
||||||
|
className="mx_StatusMessageContextMenu_message"
|
||||||
|
value={this.state.message} onChange={this._onStatusChange} maxLength="60" />
|
||||||
|
<AccessibleButton onClick={this._onSubmit} element="div" className={formSubmitClasses}>
|
||||||
|
<img src="img/icons-checkmark.svg" width="22" height="22" />
|
||||||
|
</AccessibleButton>
|
||||||
|
</form>;
|
||||||
|
|
||||||
|
const clearIcon = this.state.message ? "img/cancel-red.svg" : "img/cancel.svg";
|
||||||
|
const clearButton = <AccessibleButton onClick={this._onClearClick} disabled={!this.state.message}
|
||||||
|
className="mx_StatusMessageContextMenu_clear">
|
||||||
|
<img src={clearIcon} alt={_t('Clear status')} width="12" height="12"
|
||||||
|
className="mx_filterFlipColor mx_StatusMessageContextMenu_clearIcon" />
|
||||||
|
<span>{_t("Clear status")}</span>
|
||||||
|
</AccessibleButton>;
|
||||||
|
|
||||||
|
const menuClasses = classNames({
|
||||||
|
"mx_StatusMessageContextMenu": true,
|
||||||
|
"mx_StatusMessageContextMenu_hasStatus": this.state.message,
|
||||||
|
});
|
||||||
|
|
||||||
|
return <div className={menuClasses}>
|
||||||
|
{ form }
|
||||||
|
<hr />
|
||||||
|
{ clearButton }
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
}
|
|
@ -70,6 +70,7 @@ const EntityTile = React.createClass({
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
suppressOnHover: PropTypes.bool,
|
suppressOnHover: PropTypes.bool,
|
||||||
showPresence: PropTypes.bool,
|
showPresence: PropTypes.bool,
|
||||||
|
subtextLabel: PropTypes.string,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
|
@ -129,6 +130,9 @@ const EntityTile = React.createClass({
|
||||||
presenceState={this.props.presenceState} />;
|
presenceState={this.props.presenceState} />;
|
||||||
nameClasses += ' mx_EntityTile_name_hover';
|
nameClasses += ' mx_EntityTile_name_hover';
|
||||||
}
|
}
|
||||||
|
if (this.props.subtextLabel) {
|
||||||
|
presenceLabel = <span className="mx_EntityTile_subtext">{this.props.subtextLabel}</span>;
|
||||||
|
}
|
||||||
nameEl = (
|
nameEl = (
|
||||||
<div className="mx_EntityTile_details">
|
<div className="mx_EntityTile_details">
|
||||||
<img className="mx_EntityTile_chevron" src="img/member_chevron.png" width="8" height="12" />
|
<img className="mx_EntityTile_chevron" src="img/member_chevron.png" width="8" height="12" />
|
||||||
|
@ -138,6 +142,15 @@ const EntityTile = React.createClass({
|
||||||
{presenceLabel}
|
{presenceLabel}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
} else if (this.props.subtextLabel) {
|
||||||
|
nameEl = (
|
||||||
|
<div className="mx_EntityTile_details">
|
||||||
|
<EmojiText element="div" className="mx_EntityTile_name" dir="auto">
|
||||||
|
{name}
|
||||||
|
</EmojiText>
|
||||||
|
<span className="mx_EntityTile_subtext">{this.props.subtextLabel}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
nameEl = (
|
nameEl = (
|
||||||
<EmojiText element="div" className="mx_EntityTile_name" dir="auto">{ name }</EmojiText>
|
<EmojiText element="div" className="mx_EntityTile_name" dir="auto">{ name }</EmojiText>
|
||||||
|
|
|
@ -42,6 +42,7 @@ import AccessibleButton from '../elements/AccessibleButton';
|
||||||
import RoomViewStore from '../../../stores/RoomViewStore';
|
import RoomViewStore from '../../../stores/RoomViewStore';
|
||||||
import SdkConfig from '../../../SdkConfig';
|
import SdkConfig from '../../../SdkConfig';
|
||||||
import MultiInviter from "../../../utils/MultiInviter";
|
import MultiInviter from "../../../utils/MultiInviter";
|
||||||
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
|
||||||
module.exports = withMatrixClient(React.createClass({
|
module.exports = withMatrixClient(React.createClass({
|
||||||
displayName: 'MemberInfo',
|
displayName: 'MemberInfo',
|
||||||
|
@ -889,11 +890,16 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
let presenceState;
|
let presenceState;
|
||||||
let presenceLastActiveAgo;
|
let presenceLastActiveAgo;
|
||||||
let presenceCurrentlyActive;
|
let presenceCurrentlyActive;
|
||||||
|
let statusMessage;
|
||||||
|
|
||||||
if (this.props.member.user) {
|
if (this.props.member.user) {
|
||||||
presenceState = this.props.member.user.presence;
|
presenceState = this.props.member.user.presence;
|
||||||
presenceLastActiveAgo = this.props.member.user.lastActiveAgo;
|
presenceLastActiveAgo = this.props.member.user.lastActiveAgo;
|
||||||
presenceCurrentlyActive = this.props.member.user.currentlyActive;
|
presenceCurrentlyActive = this.props.member.user.currentlyActive;
|
||||||
|
|
||||||
|
if (SettingsStore.isFeatureEnabled("feature_custom_status")) {
|
||||||
|
statusMessage = this.props.member.user._unstable_statusMessage;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const room = this.props.matrixClient.getRoom(this.props.member.roomId);
|
const room = this.props.matrixClient.getRoom(this.props.member.roomId);
|
||||||
|
@ -915,6 +921,11 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
presenceState={presenceState} />;
|
presenceState={presenceState} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let statusLabel = null;
|
||||||
|
if (statusMessage) {
|
||||||
|
statusLabel = <span className="mx_MemberInfo_statusMessage">{ statusMessage }</span>;
|
||||||
|
}
|
||||||
|
|
||||||
let roomMemberDetails = null;
|
let roomMemberDetails = null;
|
||||||
if (this.props.member.roomId) { // is in room
|
if (this.props.member.roomId) { // is in room
|
||||||
const PowerSelector = sdk.getComponent('elements.PowerSelector');
|
const PowerSelector = sdk.getComponent('elements.PowerSelector');
|
||||||
|
@ -931,6 +942,7 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_MemberInfo_profileField">
|
<div className="mx_MemberInfo_profileField">
|
||||||
{presenceLabel}
|
{presenceLabel}
|
||||||
|
{statusLabel}
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@ limitations under the License.
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
@ -85,6 +87,11 @@ module.exports = React.createClass({
|
||||||
const active = -1;
|
const active = -1;
|
||||||
const presenceState = member.user ? member.user.presence : null;
|
const presenceState = member.user ? member.user.presence : null;
|
||||||
|
|
||||||
|
let statusMessage = null;
|
||||||
|
if (member.user && SettingsStore.isFeatureEnabled("feature_custom_status")) {
|
||||||
|
statusMessage = member.user._unstable_statusMessage;
|
||||||
|
}
|
||||||
|
|
||||||
const av = (
|
const av = (
|
||||||
<MemberAvatar member={member} width={36} height={36} />
|
<MemberAvatar member={member} width={36} height={36} />
|
||||||
);
|
);
|
||||||
|
@ -106,7 +113,9 @@ module.exports = React.createClass({
|
||||||
presenceLastTs={member.user ? member.user.lastPresenceTs : 0}
|
presenceLastTs={member.user ? member.user.lastPresenceTs : 0}
|
||||||
presenceCurrentlyActive={member.user ? member.user.currentlyActive : false}
|
presenceCurrentlyActive={member.user ? member.user.currentlyActive : false}
|
||||||
avatarJsx={av} title={this.getPowerLabel()} onClick={this.onClick}
|
avatarJsx={av} title={this.getPowerLabel()} onClick={this.onClick}
|
||||||
name={name} powerStatus={powerStatus} showPresence={this.props.showPresence} />
|
name={name} powerStatus={powerStatus} showPresence={this.props.showPresence}
|
||||||
|
subtextLabel={statusMessage}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -291,7 +291,7 @@ export default class MessageComposer extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const uploadInputStyle = {display: 'none'};
|
const uploadInputStyle = {display: 'none'};
|
||||||
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
|
const MemberStatusMessageAvatar = sdk.getComponent('avatars.MemberStatusMessageAvatar');
|
||||||
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||||
const MessageComposerInput = sdk.getComponent("rooms.MessageComposerInput");
|
const MessageComposerInput = sdk.getComponent("rooms.MessageComposerInput");
|
||||||
|
|
||||||
|
@ -300,7 +300,7 @@ export default class MessageComposer extends React.Component {
|
||||||
if (this.state.me) {
|
if (this.state.me) {
|
||||||
controls.push(
|
controls.push(
|
||||||
<div key="controls_avatar" className="mx_MessageComposer_avatar">
|
<div key="controls_avatar" className="mx_MessageComposer_avatar">
|
||||||
<MemberAvatar member={this.state.me} width={24} height={24} />
|
<MemberStatusMessageAvatar member={this.state.me} width={24} height={24} />
|
||||||
</div>,
|
</div>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ import * as FormattingUtils from '../../../utils/FormattingUtils';
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
import ActiveRoomObserver from '../../../ActiveRoomObserver';
|
import ActiveRoomObserver from '../../../ActiveRoomObserver';
|
||||||
import RoomViewStore from '../../../stores/RoomViewStore';
|
import RoomViewStore from '../../../stores/RoomViewStore';
|
||||||
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'RoomTile',
|
displayName: 'RoomTile',
|
||||||
|
@ -251,6 +252,17 @@ module.exports = React.createClass({
|
||||||
const mentionBadges = this.props.highlight && this._shouldShowMentionBadge();
|
const mentionBadges = this.props.highlight && this._shouldShowMentionBadge();
|
||||||
const badges = notifBadges || mentionBadges;
|
const badges = notifBadges || mentionBadges;
|
||||||
|
|
||||||
|
const isJoined = this.props.room.getMyMembership() === "join";
|
||||||
|
const looksLikeDm = this.props.room.getInvitedAndJoinedMemberCount() === 2;
|
||||||
|
let subtext = null;
|
||||||
|
if (!isInvite && isJoined && looksLikeDm && SettingsStore.isFeatureEnabled("feature_custom_status")) {
|
||||||
|
const selfId = MatrixClientPeg.get().getUserId();
|
||||||
|
const otherMember = this.props.room.currentState.getMembersExcept([selfId])[0];
|
||||||
|
if (otherMember && otherMember.user && otherMember.user._unstable_statusMessage) {
|
||||||
|
subtext = otherMember.user._unstable_statusMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const classes = classNames({
|
const classes = classNames({
|
||||||
'mx_RoomTile': true,
|
'mx_RoomTile': true,
|
||||||
'mx_RoomTile_selected': this.state.selected,
|
'mx_RoomTile_selected': this.state.selected,
|
||||||
|
@ -261,6 +273,7 @@ module.exports = React.createClass({
|
||||||
'mx_RoomTile_menuDisplayed': this.state.menuDisplayed,
|
'mx_RoomTile_menuDisplayed': this.state.menuDisplayed,
|
||||||
'mx_RoomTile_noBadges': !badges,
|
'mx_RoomTile_noBadges': !badges,
|
||||||
'mx_RoomTile_transparent': this.props.transparent,
|
'mx_RoomTile_transparent': this.props.transparent,
|
||||||
|
'mx_RoomTile_hasSubtext': subtext && !this.props.collapsed,
|
||||||
});
|
});
|
||||||
|
|
||||||
const avatarClasses = classNames({
|
const avatarClasses = classNames({
|
||||||
|
@ -291,6 +304,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
const EmojiText = sdk.getComponent('elements.EmojiText');
|
const EmojiText = sdk.getComponent('elements.EmojiText');
|
||||||
let label;
|
let label;
|
||||||
|
let subtextLabel;
|
||||||
let tooltip;
|
let tooltip;
|
||||||
if (!this.props.collapsed) {
|
if (!this.props.collapsed) {
|
||||||
const nameClasses = classNames({
|
const nameClasses = classNames({
|
||||||
|
@ -299,6 +313,8 @@ module.exports = React.createClass({
|
||||||
'mx_RoomTile_badgeShown': badges || this.state.badgeHover || this.state.menuDisplayed,
|
'mx_RoomTile_badgeShown': badges || this.state.badgeHover || this.state.menuDisplayed,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
subtextLabel = subtext ? <span className="mx_RoomTile_subtext">{ subtext }</span> : null;
|
||||||
|
|
||||||
if (this.state.selected) {
|
if (this.state.selected) {
|
||||||
const nameSelected = <EmojiText>{ name }</EmojiText>;
|
const nameSelected = <EmojiText>{ name }</EmojiText>;
|
||||||
|
|
||||||
|
@ -339,6 +355,7 @@ module.exports = React.createClass({
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_RoomTile_nameContainer">
|
<div className="mx_RoomTile_nameContainer">
|
||||||
{ label }
|
{ label }
|
||||||
|
{ subtextLabel }
|
||||||
{ badge }
|
{ badge }
|
||||||
</div>
|
</div>
|
||||||
{ /* { incomingCallBox } */ }
|
{ /* { incomingCallBox } */ }
|
||||||
|
|
|
@ -257,6 +257,7 @@
|
||||||
"Please contact your homeserver administrator.": "Please contact your homeserver administrator.",
|
"Please contact your homeserver administrator.": "Please contact your homeserver administrator.",
|
||||||
"Failed to join room": "Failed to join room",
|
"Failed to join room": "Failed to join room",
|
||||||
"Message Pinning": "Message Pinning",
|
"Message Pinning": "Message Pinning",
|
||||||
|
"Custom user status messages": "Custom user status messages",
|
||||||
"Increase performance by only loading room members on first view": "Increase performance by only loading room members on first view",
|
"Increase performance by only loading room members on first view": "Increase performance by only loading room members on first view",
|
||||||
"Backup of encryption keys to server": "Backup of encryption keys to server",
|
"Backup of encryption keys to server": "Backup of encryption keys to server",
|
||||||
"Disable Emoji suggestions while typing": "Disable Emoji suggestions while typing",
|
"Disable Emoji suggestions while typing": "Disable Emoji suggestions while typing",
|
||||||
|
@ -1061,6 +1062,8 @@
|
||||||
"Forget": "Forget",
|
"Forget": "Forget",
|
||||||
"Low Priority": "Low Priority",
|
"Low Priority": "Low Priority",
|
||||||
"Direct Chat": "Direct Chat",
|
"Direct Chat": "Direct Chat",
|
||||||
|
"Set a new status...": "Set a new status...",
|
||||||
|
"Clear status": "Clear status",
|
||||||
"View Community": "View Community",
|
"View Community": "View Community",
|
||||||
"Sorry, your browser is <b>not</b> able to run Riot.": "Sorry, your browser is <b>not</b> able to run Riot.",
|
"Sorry, your browser is <b>not</b> able to run Riot.": "Sorry, your browser is <b>not</b> able to run Riot.",
|
||||||
"Riot uses many advanced browser features, some of which are not available or experimental in your current browser.": "Riot uses many advanced browser features, some of which are not available or experimental in your current browser.",
|
"Riot uses many advanced browser features, some of which are not available or experimental in your current browser.": "Riot uses many advanced browser features, some of which are not available or experimental in your current browser.",
|
||||||
|
|
|
@ -83,6 +83,12 @@ export const SETTINGS = {
|
||||||
supportedLevels: LEVELS_FEATURE,
|
supportedLevels: LEVELS_FEATURE,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
"feature_custom_status": {
|
||||||
|
isFeature: true,
|
||||||
|
displayName: _td("Custom user status messages"),
|
||||||
|
supportedLevels: LEVELS_FEATURE,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
"feature_lazyloading": {
|
"feature_lazyloading": {
|
||||||
isFeature: true,
|
isFeature: true,
|
||||||
displayName: _td("Increase performance by only loading room members on first view"),
|
displayName: _td("Increase performance by only loading room members on first view"),
|
||||||
|
|
Loading…
Reference in a new issue