Merge branch 'experimental' into bwindels/searchmakeover
This commit is contained in:
commit
899ee265e3
18 changed files with 325 additions and 157 deletions
|
@ -48,7 +48,6 @@ src/components/views/rooms/LinkPreviewWidget.js
|
|||
src/components/views/rooms/MemberDeviceInfo.js
|
||||
src/components/views/rooms/MemberInfo.js
|
||||
src/components/views/rooms/MemberList.js
|
||||
src/components/views/rooms/MemberTile.js
|
||||
src/components/views/rooms/MessageComposer.js
|
||||
src/components/views/rooms/PinnedEventTile.js
|
||||
src/components/views/rooms/RoomList.js
|
||||
|
|
|
@ -30,8 +30,8 @@ limitations under the License.
|
|||
}
|
||||
|
||||
.mx_ContextualMenu {
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.21);
|
||||
border-radius: 4px;
|
||||
box-shadow: 4px 4px 12px 0 rgba(118, 131, 156, 0.6);;
|
||||
background-color: $menu-bg-color;
|
||||
color: $primary-fg-color;
|
||||
position: absolute;
|
||||
|
|
|
@ -14,8 +14,16 @@ 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;
|
||||
padding-right: 0 !important; /* Override AccessibleButton styling */
|
||||
.mx_MessageComposer_avatar .mx_BaseAvatar {
|
||||
padding: 2px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.mx_MessageComposer_avatar .mx_BaseAvatar_initial {
|
||||
left: 2px;
|
||||
}
|
||||
|
||||
.mx_MemberStatusMessageAvatar_hasStatus .mx_BaseAvatar {
|
||||
border-color: $accent-color;
|
||||
}
|
||||
|
|
|
@ -14,42 +14,43 @@ 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;
|
||||
.mx_StatusMessageContextMenu {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.mx_StatusMessageContextMenu_form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
input.mx_StatusMessageContextMenu_message {
|
||||
border-radius: 4px;
|
||||
border: 1px solid $input-border-color;
|
||||
font-size: 13px;
|
||||
padding: 7px 7px 7px 9px;
|
||||
width: 135px;
|
||||
background-color: $primary-bg-color !important;
|
||||
padding: 6.5px 11px;
|
||||
background-color: $primary-bg-color;
|
||||
font-weight: normal;
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
|
||||
.mx_StatusMessageContextMenu_submit {
|
||||
display: inline-block;
|
||||
.mx_StatusMessageContextMenu_message::placeholder {
|
||||
color: $memberstatus-placeholder-color;
|
||||
}
|
||||
|
||||
.mx_StatusMessageContextMenu_submitFaded {
|
||||
opacity: 0.5;
|
||||
.mx_StatusMessageContextMenu_submit,
|
||||
.mx_StatusMessageContextMenu_clear {
|
||||
@mixin mx_DialogButton;
|
||||
align-self: start;
|
||||
font-size: 12px;
|
||||
padding: 6px 1em;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.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_submit[disabled] {
|
||||
opacity: 0.49;
|
||||
}
|
||||
|
||||
.mx_StatusMessageContextMenu_clear {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.mx_StatusMessageContextMenu_hasStatus .mx_StatusMessageContextMenu_clear {
|
||||
color: $warning-color;
|
||||
background-color: transparent;
|
||||
border: 1px solid $warning-color;
|
||||
}
|
||||
|
|
|
@ -58,17 +58,13 @@ limitations under the License.
|
|||
}
|
||||
|
||||
.mx_MessageComposer .mx_MessageComposer_avatar {
|
||||
padding: 0 28px;
|
||||
padding: 0 27px;
|
||||
}
|
||||
|
||||
.mx_MessageComposer .mx_MessageComposer_avatar .mx_BaseAvatar {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mx_MessageComposer .mx_AccessibleButton {
|
||||
padding: 0 12px 0 0;
|
||||
}
|
||||
|
||||
.mx_MessageComposer_composecontrols {
|
||||
width: 100%;
|
||||
}
|
||||
|
@ -185,7 +181,7 @@ limitations under the License.
|
|||
/*display: table-cell;*/
|
||||
/*vertical-align: middle;*/
|
||||
/*padding-left: 10px;*/
|
||||
padding-right: 5px;
|
||||
padding-right: 12px;
|
||||
cursor: pointer;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
<?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>
|
Before Width: | Height: | Size: 1.1 KiB |
|
@ -68,7 +68,7 @@ $event-selected-color: #f7f7f7;
|
|||
$primary-hairline-color: #e5e5e5;
|
||||
|
||||
// used for the border of input text fields
|
||||
$input-border-color: #f0f0f0;
|
||||
$input-border-color: #e7e7e7;
|
||||
$input-darker-bg-color: rgba(193, 201, 214, 0.29);
|
||||
$input-darker-fg-color: #9fa9ba;
|
||||
$input-lighter-bg-color: #f2f5f8;
|
||||
|
@ -192,6 +192,8 @@ $progressbar-color: #000;
|
|||
|
||||
$room-warning-bg-color: #fff8e3;
|
||||
|
||||
$memberstatus-placeholder-color: $roomtile-name-color;
|
||||
|
||||
/*** form elements ***/
|
||||
|
||||
// .mx_textinput is a container for a text input
|
||||
|
|
|
@ -188,6 +188,8 @@ $progressbar-color: #000;
|
|||
|
||||
$room-warning-bg-color: #fff8e3;
|
||||
|
||||
$memberstatus-placeholder-color: $roomtile-name-color;
|
||||
|
||||
// ***** Mixins! *****
|
||||
|
||||
@define-mixin mx_DialogButton {
|
||||
|
|
|
@ -34,6 +34,9 @@ import { _t } from '../../languageHandler';
|
|||
|
||||
import {instanceForInstanceId, protocolNameForInstanceId} from '../../utils/DirectoryUtils';
|
||||
|
||||
const MAX_NAME_LENGTH = 80;
|
||||
const MAX_TOPIC_LENGTH = 160;
|
||||
|
||||
linkifyMatrix(linkify);
|
||||
|
||||
module.exports = React.createClass({
|
||||
|
@ -390,7 +393,6 @@ module.exports = React.createClass({
|
|||
const self = this;
|
||||
let guestRead; let guestJoin; let perms;
|
||||
for (let i = 0; i < rooms.length; i++) {
|
||||
const name = rooms[i].name || get_display_alias_for_room(rooms[i]) || _t('Unnamed room');
|
||||
guestRead = null;
|
||||
guestJoin = null;
|
||||
|
||||
|
@ -410,7 +412,15 @@ module.exports = React.createClass({
|
|||
perms = <div className="mx_RoomDirectory_perms">{guestRead}{guestJoin}</div>;
|
||||
}
|
||||
|
||||
let name = rooms[i].name || get_display_alias_for_room(rooms[i]) || _t('Unnamed room');
|
||||
if (name.length > MAX_NAME_LENGTH) {
|
||||
name = `${name.substring(0, MAX_NAME_LENGTH)}...`;
|
||||
}
|
||||
|
||||
let topic = rooms[i].topic || '';
|
||||
if (topic.length > MAX_TOPIC_LENGTH) {
|
||||
topic = `${topic.substring(0, MAX_TOPIC_LENGTH)}...`;
|
||||
}
|
||||
topic = linkifyString(sanitizeHtml(topic));
|
||||
|
||||
rows.push(
|
||||
|
|
|
@ -82,11 +82,10 @@ const SIMPLE_SETTINGS = [
|
|||
{ id: "VideoView.flipVideoHorizontally" },
|
||||
{ id: "TagPanel.disableTagPanel" },
|
||||
{ id: "enableWidgetScreenshots" },
|
||||
{ id: "RoomSubList.showEmpty" },
|
||||
{ id: "pinMentionedRooms" },
|
||||
{ id: "pinUnreadRooms" },
|
||||
{ id: "showDeveloperTools" },
|
||||
{ id: "alwaysRetryInvites" },
|
||||
{ id: "alwaysInviteUnknownUsers" },
|
||||
];
|
||||
|
||||
// These settings must be defined in SettingsStore
|
||||
|
|
|
@ -40,38 +40,50 @@ export default class MemberStatusMessageAvatar extends React.Component {
|
|||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
hasStatus: this.hasStatus,
|
||||
};
|
||||
}
|
||||
|
||||
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: ""});
|
||||
if (!SettingsStore.isFeatureEnabled("feature_custom_status")) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (MatrixClientPeg.get()) {
|
||||
MatrixClientPeg.get().removeListener("RoomState.events", this._onRoomStateEvents);
|
||||
const { user } = this.props.member;
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
user.on("User._unstable_statusMessage", this._onStatusMessageCommitted);
|
||||
}
|
||||
|
||||
_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"]});
|
||||
componentWillUmount() {
|
||||
const { user } = this.props.member;
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
user.removeListener(
|
||||
"User._unstable_statusMessage",
|
||||
this._onStatusMessageCommitted,
|
||||
);
|
||||
}
|
||||
|
||||
get hasStatus() {
|
||||
const { user } = this.props.member;
|
||||
if (!user) {
|
||||
return false;
|
||||
}
|
||||
return !!user._unstable_statusMessage;
|
||||
}
|
||||
|
||||
_onStatusMessageCommitted = () => {
|
||||
// The `User` object has observed a status message change.
|
||||
this.setState({
|
||||
hasStatus: this.hasStatus,
|
||||
});
|
||||
};
|
||||
|
||||
_onClick = (e) => {
|
||||
|
@ -79,42 +91,43 @@ export default class MemberStatusMessageAvatar extends React.Component {
|
|||
|
||||
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
|
||||
const x = (elementRect.left + window.pageXOffset);
|
||||
const chevronWidth = 16; // See .mx_ContextualMenu_chevron_bottom
|
||||
const chevronOffset = (elementRect.width - chevronWidth) / 2;
|
||||
const chevronMargin = 1; // Add some spacing away from target
|
||||
const y = elementRect.top + window.pageYOffset - chevronMargin;
|
||||
|
||||
ContextualMenu.createMenu(StatusMessageContextMenu, {
|
||||
chevronOffset: chevronOffset,
|
||||
chevronFace: 'bottom',
|
||||
left: x,
|
||||
top: y,
|
||||
menuWidth: 190,
|
||||
menuWidth: 226,
|
||||
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 avatar = <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;
|
||||
if (!SettingsStore.isFeatureEnabled("feature_custom_status")) {
|
||||
return avatar;
|
||||
}
|
||||
|
||||
const classes = classNames({
|
||||
"mx_MemberStatusMessageAvatar": true,
|
||||
"mx_MemberStatusMessageAvatar_hasStatus": hasStatus,
|
||||
"mx_MemberStatusMessageAvatar_hasStatus": this.state.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} />
|
||||
return <AccessibleButton className={classes}
|
||||
element="div" onClick={this._onClick}
|
||||
>
|
||||
{avatar}
|
||||
</AccessibleButton>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ 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 = {
|
||||
|
@ -31,13 +30,42 @@ export default class StatusMessageContextMenu extends React.Component {
|
|||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
message: props.user ? props.user._unstable_statusMessage : "",
|
||||
message: this.comittedStatusMessage,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
const { user } = this.props;
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
user.on("User._unstable_statusMessage", this._onStatusMessageCommitted);
|
||||
}
|
||||
|
||||
componentWillUmount() {
|
||||
const { user } = this.props;
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
user.removeListener(
|
||||
"User._unstable_statusMessage",
|
||||
this._onStatusMessageCommitted,
|
||||
);
|
||||
}
|
||||
|
||||
get comittedStatusMessage() {
|
||||
return this.props.user ? this.props.user._unstable_statusMessage : "";
|
||||
}
|
||||
|
||||
_onStatusMessageCommitted = () => {
|
||||
// The `User` object has observed a status message change.
|
||||
this.setState({
|
||||
message: this.comittedStatusMessage,
|
||||
});
|
||||
};
|
||||
|
||||
_onClearClick = async (e) => {
|
||||
await MatrixClientPeg.get()._unstable_setStatusMessage("");
|
||||
this.setState({message: ""});
|
||||
};
|
||||
|
||||
_onSubmit = (e) => {
|
||||
|
@ -46,41 +74,49 @@ export default class StatusMessageContextMenu extends React.Component {
|
|||
};
|
||||
|
||||
_onStatusChange = (e) => {
|
||||
this.setState({message: e.target.value});
|
||||
// The input field's value was changed.
|
||||
this.setState({
|
||||
message: e.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const formSubmitClasses = classNames({
|
||||
"mx_StatusMessageContextMenu_submit": true,
|
||||
"mx_StatusMessageContextMenu_submitFaded": !this.state.message, // no message == faded
|
||||
});
|
||||
let actionButton;
|
||||
if (this.comittedStatusMessage) {
|
||||
if (this.state.message === this.comittedStatusMessage) {
|
||||
actionButton = <AccessibleButton className="mx_StatusMessageContextMenu_clear"
|
||||
onClick={this._onClearClick}
|
||||
>
|
||||
<span>{_t("Clear status")}</span>
|
||||
</AccessibleButton>;
|
||||
} else {
|
||||
actionButton = <AccessibleButton className="mx_StatusMessageContextMenu_submit"
|
||||
onClick={this._onSubmit}
|
||||
>
|
||||
<span>{_t("Update status")}</span>
|
||||
</AccessibleButton>;
|
||||
}
|
||||
} else {
|
||||
actionButton = <AccessibleButton className="mx_StatusMessageContextMenu_submit"
|
||||
disabled={!this.state.message} onClick={this._onSubmit}
|
||||
>
|
||||
<span>{_t("Set status")}</span>
|
||||
</AccessibleButton>;
|
||||
}
|
||||
|
||||
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>
|
||||
const form = <form className="mx_StatusMessageContextMenu_form"
|
||||
autoComplete="off" onSubmit={this._onSubmit}
|
||||
>
|
||||
<input type="text" className="mx_StatusMessageContextMenu_message"
|
||||
key="message" placeholder={_t("Set a new status...")}
|
||||
autoFocus={true} maxLength="60" value={this.state.message}
|
||||
onChange={this._onStatusChange}
|
||||
/>
|
||||
{actionButton}
|
||||
</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}>
|
||||
return <div className="mx_StatusMessageContextMenu">
|
||||
{ form }
|
||||
<hr />
|
||||
{ clearButton }
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ export default class ChangelogDialog extends React.Component {
|
|||
for (let i=0; i<REPOS.length; i++) {
|
||||
const oldVersion = version2[2*i];
|
||||
const newVersion = version[2*i];
|
||||
const url = `https://api.github.com/repos/${REPOS[i]}/compare/${oldVersion}...${newVersion}`;
|
||||
const url = `https://riot.im/github/repos/${REPOS[i]}/compare/${oldVersion}...${newVersion}`;
|
||||
request(url, (err, response, body) => {
|
||||
if (response.statusCode < 200 || response.statusCode >= 300) {
|
||||
this.setState({ [REPOS[i]]: response.statusText });
|
||||
|
|
|
@ -21,10 +21,8 @@ import SettingsStore from "../../../settings/SettingsStore";
|
|||
const React = require('react');
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||
const sdk = require('../../../index');
|
||||
const dis = require('../../../dispatcher');
|
||||
const Modal = require("../../../Modal");
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
module.exports = React.createClass({
|
||||
|
@ -42,7 +40,46 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {};
|
||||
return {
|
||||
statusMessage: this.getStatusMessage(),
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
if (!SettingsStore.isFeatureEnabled("feature_custom_status")) {
|
||||
return;
|
||||
}
|
||||
const { user } = this.props.member;
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
user.on("User._unstable_statusMessage", this._onStatusMessageCommitted);
|
||||
},
|
||||
|
||||
componentWillUmount() {
|
||||
const { user } = this.props.member;
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
user.removeListener(
|
||||
"User._unstable_statusMessage",
|
||||
this._onStatusMessageCommitted,
|
||||
);
|
||||
},
|
||||
|
||||
getStatusMessage() {
|
||||
const { user } = this.props.member;
|
||||
if (!user) {
|
||||
return "";
|
||||
}
|
||||
return user._unstable_statusMessage;
|
||||
},
|
||||
|
||||
_onStatusMessageCommitted() {
|
||||
// The `User` object has observed a status message change.
|
||||
this.setState({
|
||||
statusMessage: this.getStatusMessage(),
|
||||
});
|
||||
},
|
||||
|
||||
shouldComponentUpdate: function(nextProps, nextState) {
|
||||
|
@ -74,22 +111,23 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
getPowerLabel: function() {
|
||||
return _t("%(userName)s (power %(powerLevelNumber)s)", {userName: this.props.member.userId, powerLevelNumber: this.props.member.powerLevel});
|
||||
return _t("%(userName)s (power %(powerLevelNumber)s)", {
|
||||
userName: this.props.member.userId,
|
||||
powerLevelNumber: this.props.member.powerLevel,
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
|
||||
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||
const EntityTile = sdk.getComponent('rooms.EntityTile');
|
||||
|
||||
const member = this.props.member;
|
||||
const name = this._getDisplayName();
|
||||
const active = -1;
|
||||
const presenceState = member.user ? member.user.presence : null;
|
||||
|
||||
let statusMessage = null;
|
||||
if (member.user && SettingsStore.isFeatureEnabled("feature_custom_status")) {
|
||||
statusMessage = member.user._unstable_statusMessage;
|
||||
statusMessage = this.state.statusMessage;
|
||||
}
|
||||
|
||||
const av = (
|
||||
|
|
|
@ -62,6 +62,7 @@ module.exports = React.createClass({
|
|||
notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId),
|
||||
notificationCount: this.props.room.getUnreadNotificationCount(),
|
||||
selected: this.props.room.roomId === ActiveRoomObserver.getActiveRoomId(),
|
||||
statusMessage: this._getStatusMessage(),
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -79,6 +80,33 @@ module.exports = React.createClass({
|
|||
return Boolean(dmRooms);
|
||||
},
|
||||
|
||||
_shouldShowStatusMessage() {
|
||||
if (!SettingsStore.isFeatureEnabled("feature_custom_status")) {
|
||||
return false;
|
||||
}
|
||||
const isInvite = this.props.room.getMyMembership() === "invite";
|
||||
const isJoined = this.props.room.getMyMembership() === "join";
|
||||
const looksLikeDm = this.props.room.getInvitedAndJoinedMemberCount() === 2;
|
||||
return !isInvite && isJoined && looksLikeDm;
|
||||
},
|
||||
|
||||
_getStatusMessageUser() {
|
||||
const selfId = MatrixClientPeg.get().getUserId();
|
||||
const otherMember = this.props.room.currentState.getMembersExcept([selfId])[0];
|
||||
if (!otherMember) {
|
||||
return null;
|
||||
}
|
||||
return otherMember.user;
|
||||
},
|
||||
|
||||
_getStatusMessage() {
|
||||
const statusUser = this._getStatusMessageUser();
|
||||
if (!statusUser) {
|
||||
return "";
|
||||
}
|
||||
return statusUser._unstable_statusMessage;
|
||||
},
|
||||
|
||||
onRoomTimeline: function(ev, room) {
|
||||
if (room !== this.props.room) return;
|
||||
this.setState({
|
||||
|
@ -112,7 +140,13 @@ module.exports = React.createClass({
|
|||
this.setState({
|
||||
notificationCount: this.props.room.getUnreadNotificationCount(),
|
||||
});
|
||||
break;
|
||||
break;
|
||||
// RoomTiles are one of the few components that may show custom status and
|
||||
// also remain on screen while in Settings toggling the feature. This ensures
|
||||
// you can clearly see the status hide and show when toggling the feature.
|
||||
case 'feature_custom_status_changed':
|
||||
this.forceUpdate();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -128,6 +162,16 @@ module.exports = React.createClass({
|
|||
MatrixClientPeg.get().on("Room.name", this.onRoomName);
|
||||
ActiveRoomObserver.addListener(this.props.room.roomId, this._onActiveRoomChange);
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
|
||||
if (this._shouldShowStatusMessage()) {
|
||||
const statusUser = this._getStatusMessageUser();
|
||||
if (statusUser) {
|
||||
statusUser.on(
|
||||
"User._unstable_statusMessage",
|
||||
this._onStatusMessageCommitted,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
|
@ -139,6 +183,16 @@ module.exports = React.createClass({
|
|||
}
|
||||
ActiveRoomObserver.removeListener(this.props.room.roomId, this._onActiveRoomChange);
|
||||
dis.unregister(this.dispatcherRef);
|
||||
|
||||
if (this._shouldShowStatusMessage()) {
|
||||
const statusUser = this._getStatusMessageUser();
|
||||
if (statusUser) {
|
||||
statusUser.removeListener(
|
||||
"User._unstable_statusMessage",
|
||||
this._onStatusMessageCommitted,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(props) {
|
||||
|
@ -166,6 +220,13 @@ module.exports = React.createClass({
|
|||
return false;
|
||||
},
|
||||
|
||||
_onStatusMessageCommitted() {
|
||||
// The status message `User` object has observed a message change.
|
||||
this.setState({
|
||||
statusMessage: this._getStatusMessage(),
|
||||
});
|
||||
},
|
||||
|
||||
onClick: function(ev) {
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick(this.props.room.roomId, ev);
|
||||
|
@ -251,15 +312,9 @@ module.exports = React.createClass({
|
|||
const mentionBadges = this.props.highlight && this._shouldShowMentionBadge();
|
||||
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;
|
||||
}
|
||||
if (this._shouldShowStatusMessage()) {
|
||||
subtext = this.state.statusMessage;
|
||||
}
|
||||
|
||||
const classes = classNames({
|
||||
|
|
|
@ -296,7 +296,6 @@
|
|||
"Pin rooms I'm mentioned in to the top of the room list": "Pin rooms I'm mentioned in to the top of the room list",
|
||||
"Pin unread rooms to the top of the room list": "Pin unread rooms to the top of the room list",
|
||||
"Enable widget screenshots on supported widgets": "Enable widget screenshots on supported widgets",
|
||||
"Show empty room list headings": "Show empty room list headings",
|
||||
"Always invite users which may not exist": "Always invite users which may not exist",
|
||||
"Show developer tools": "Show developer tools",
|
||||
"Collecting app version information": "Collecting app version information",
|
||||
|
@ -1088,8 +1087,10 @@
|
|||
"Forget": "Forget",
|
||||
"Low Priority": "Low Priority",
|
||||
"Direct Chat": "Direct Chat",
|
||||
"Set a new status...": "Set a new status...",
|
||||
"Clear status": "Clear status",
|
||||
"Update status": "Update status",
|
||||
"Set status": "Set status",
|
||||
"Set a new status...": "Set a new status...",
|
||||
"View as Grid": "View as Grid",
|
||||
"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.",
|
||||
|
|
|
@ -22,6 +22,7 @@ import {
|
|||
NotificationsEnabledController,
|
||||
} from "./controllers/NotificationControllers";
|
||||
import LazyLoadingController from "./controllers/LazyLoadingController";
|
||||
import CustomStatusController from "./controllers/CustomStatusController";
|
||||
|
||||
// These are just a bunch of helper arrays to avoid copy/pasting a bunch of times
|
||||
const LEVELS_ROOM_SETTINGS = ['device', 'room-device', 'room-account', 'account', 'config'];
|
||||
|
@ -88,6 +89,7 @@ export const SETTINGS = {
|
|||
displayName: _td("Custom user status messages"),
|
||||
supportedLevels: LEVELS_FEATURE,
|
||||
default: false,
|
||||
controller: new CustomStatusController(),
|
||||
},
|
||||
"feature_lazyloading": {
|
||||
isFeature: true,
|
||||
|
@ -324,11 +326,6 @@ export const SETTINGS = {
|
|||
supportedLevels: ['room-device'],
|
||||
default: false,
|
||||
},
|
||||
"RoomSubList.showEmpty": {
|
||||
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
||||
displayName: _td('Show empty room list headings'),
|
||||
default: true,
|
||||
},
|
||||
"alwaysInviteUnknownUsers": {
|
||||
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
||||
displayName: _td('Always invite users which may not exist'),
|
||||
|
|
28
src/settings/controllers/CustomStatusController.js
Normal file
28
src/settings/controllers/CustomStatusController.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
Copyright 2019 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 SettingController from "./SettingController";
|
||||
import dis from "../../dispatcher";
|
||||
|
||||
export default class CustomStatusController extends SettingController {
|
||||
onChange(level, roomId, newValue) {
|
||||
// Dispatch setting change so that some components that are still visible when the
|
||||
// Settings page is open (such as RoomTiles) can reflect the change.
|
||||
dis.dispatch({
|
||||
action: "feature_custom_status_changed",
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue