Merge remote-tracking branch 'origin/develop' into jryans/4s-new-key-backup
This commit is contained in:
commit
fae819dfe5
149 changed files with 3358 additions and 12421 deletions
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, {createRef} from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
@ -48,6 +48,8 @@ module.exports = createReactClass({
|
|||
|
||||
componentWillMount: function() {
|
||||
this._captchaWidgetId = null;
|
||||
|
||||
this._recaptchaContainer = createRef();
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
|
@ -67,7 +69,7 @@ module.exports = createReactClass({
|
|||
scriptTag.setAttribute(
|
||||
'src', `${protocol}//www.recaptcha.net/recaptcha/api.js?onload=mx_on_recaptcha_loaded&render=explicit`,
|
||||
);
|
||||
this.refs.recaptchaContainer.appendChild(scriptTag);
|
||||
this._recaptchaContainer.current.appendChild(scriptTag);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -124,11 +126,11 @@ module.exports = createReactClass({
|
|||
}
|
||||
|
||||
return (
|
||||
<div ref="recaptchaContainer">
|
||||
<div ref={this._recaptchaContainer}>
|
||||
<p>{_t(
|
||||
"This homeserver would like to make sure you are not a robot.",
|
||||
)}</p>
|
||||
<div id={DIV_ID}></div>
|
||||
<div id={DIV_ID} />
|
||||
{ error }
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -16,7 +16,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, {createRef} from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
import url from 'url';
|
||||
|
@ -581,6 +581,8 @@ export const FallbackAuthEntry = createReactClass({
|
|||
// the popup if we open it immediately.
|
||||
this._popupWindow = null;
|
||||
window.addEventListener("message", this._onReceiveMessage);
|
||||
|
||||
this._fallbackButton = createRef();
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
|
@ -591,8 +593,8 @@ export const FallbackAuthEntry = createReactClass({
|
|||
},
|
||||
|
||||
focus: function() {
|
||||
if (this.refs.fallbackButton) {
|
||||
this.refs.fallbackButton.focus();
|
||||
if (this._fallbackButton.current) {
|
||||
this._fallbackButton.current.focus();
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -624,7 +626,7 @@ export const FallbackAuthEntry = createReactClass({
|
|||
}
|
||||
return (
|
||||
<div>
|
||||
<a ref="fallbackButton" onClick={this._onShowFallbackClick}>{ _t("Start authentication") }</a>
|
||||
<a ref={this._fallbackButton} onClick={this._onShowFallbackClick}>{ _t("Start authentication") }</a>
|
||||
{errorSection}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -14,15 +14,15 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, {createRef} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import {_t} from "../../../languageHandler";
|
||||
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";
|
||||
import {ContextMenu, ContextMenuButton} from "../../structures/ContextMenu";
|
||||
|
||||
export default class MemberStatusMessageAvatar extends React.Component {
|
||||
static propTypes = {
|
||||
|
@ -43,7 +43,10 @@ export default class MemberStatusMessageAvatar extends React.Component {
|
|||
|
||||
this.state = {
|
||||
hasStatus: this.hasStatus,
|
||||
menuDisplayed: false,
|
||||
};
|
||||
|
||||
this._button = createRef();
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
|
@ -86,25 +89,12 @@ export default class MemberStatusMessageAvatar extends React.Component {
|
|||
});
|
||||
};
|
||||
|
||||
_onClick = (e) => {
|
||||
e.stopPropagation();
|
||||
openMenu = () => {
|
||||
this.setState({ menuDisplayed: true });
|
||||
};
|
||||
|
||||
const elementRect = e.target.getBoundingClientRect();
|
||||
|
||||
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: 226,
|
||||
user: this.props.member.user,
|
||||
});
|
||||
closeMenu = () => {
|
||||
this.setState({ menuDisplayed: false });
|
||||
};
|
||||
|
||||
render() {
|
||||
|
@ -124,10 +114,39 @@ export default class MemberStatusMessageAvatar extends React.Component {
|
|||
"mx_MemberStatusMessageAvatar_hasStatus": this.state.hasStatus,
|
||||
});
|
||||
|
||||
return <AccessibleButton className={classes}
|
||||
element="div" onClick={this._onClick}
|
||||
>
|
||||
{avatar}
|
||||
</AccessibleButton>;
|
||||
let contextMenu;
|
||||
if (this.state.menuDisplayed) {
|
||||
const elementRect = this._button.current.getBoundingClientRect();
|
||||
|
||||
const chevronWidth = 16; // See .mx_ContextualMenu_chevron_bottom
|
||||
const chevronMargin = 1; // Add some spacing away from target
|
||||
|
||||
contextMenu = (
|
||||
<ContextMenu
|
||||
chevronOffset={(elementRect.width - chevronWidth) / 2}
|
||||
chevronFace="bottom"
|
||||
left={elementRect.left + window.pageXOffset}
|
||||
top={elementRect.top + window.pageYOffset - chevronMargin}
|
||||
menuWidth={226}
|
||||
onFinished={this.closeMenu}
|
||||
>
|
||||
<StatusMessageContextMenu user={this.props.member.user} onFinished={this.closeMenu} />
|
||||
</ContextMenu>
|
||||
);
|
||||
}
|
||||
|
||||
return <React.Fragment>
|
||||
<ContextMenuButton
|
||||
className={classes}
|
||||
inputRef={this._button}
|
||||
onClick={this.openMenu}
|
||||
isExpanded={this.state.menuDisplayed}
|
||||
label={_t("User Status")}
|
||||
>
|
||||
{avatar}
|
||||
</ContextMenuButton>
|
||||
|
||||
{ contextMenu }
|
||||
</React.Fragment>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import { _t } from '../../../languageHandler';
|
|||
import Modal from '../../../Modal';
|
||||
import {Group} from 'matrix-js-sdk';
|
||||
import GroupStore from "../../../stores/GroupStore";
|
||||
import {MenuItem} from "../../structures/ContextMenu";
|
||||
|
||||
export default class GroupInviteTileContextMenu extends React.Component {
|
||||
static propTypes = {
|
||||
|
@ -36,7 +37,7 @@ export default class GroupInviteTileContextMenu extends React.Component {
|
|||
this._onClickReject = this._onClickReject.bind(this);
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
componentDidMount() {
|
||||
this._unmounted = false;
|
||||
}
|
||||
|
||||
|
@ -78,12 +79,11 @@ export default class GroupInviteTileContextMenu extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
return <div>
|
||||
<AccessibleButton className="mx_RoomTileContextMenu_leave" onClick={this._onClickReject} >
|
||||
<img className="mx_RoomTileContextMenu_tag_icon" src={require("../../../../res/img/icon_context_delete.svg")} width="15" height="15" />
|
||||
<MenuItem className="mx_RoomTileContextMenu_leave" onClick={this._onClickReject}>
|
||||
<img className="mx_RoomTileContextMenu_tag_icon" src={require("../../../../res/img/icon_context_delete.svg")} width="15" height="15" alt="" />
|
||||
{ _t('Reject') }
|
||||
</AccessibleButton>
|
||||
</MenuItem>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import Resend from '../../../Resend';
|
|||
import SettingsStore from '../../../settings/SettingsStore';
|
||||
import { isUrlPermitted } from '../../../HtmlUtils';
|
||||
import { isContentActionable } from '../../../utils/EventUtils';
|
||||
import {MenuItem} from "../../structures/ContextMenu";
|
||||
|
||||
function canCancel(eventStatus) {
|
||||
return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT;
|
||||
|
@ -289,8 +290,6 @@ module.exports = createReactClass({
|
|||
},
|
||||
|
||||
render: function() {
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
|
||||
const cli = MatrixClientPeg.get();
|
||||
const me = cli.getUserId();
|
||||
const mxEvent = this.props.mxEvent;
|
||||
|
@ -322,89 +321,89 @@ module.exports = createReactClass({
|
|||
if (!mxEvent.isRedacted()) {
|
||||
if (eventStatus === EventStatus.NOT_SENT) {
|
||||
resendButton = (
|
||||
<AccessibleButton className="mx_MessageContextMenu_field" onClick={this.onResendClick}>
|
||||
<MenuItem className="mx_MessageContextMenu_field" onClick={this.onResendClick}>
|
||||
{ _t('Resend') }
|
||||
</AccessibleButton>
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
if (editStatus === EventStatus.NOT_SENT) {
|
||||
resendEditButton = (
|
||||
<AccessibleButton className="mx_MessageContextMenu_field" onClick={this.onResendEditClick}>
|
||||
<MenuItem className="mx_MessageContextMenu_field" onClick={this.onResendEditClick}>
|
||||
{ _t('Resend edit') }
|
||||
</AccessibleButton>
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
if (unsentReactionsCount !== 0) {
|
||||
resendReactionsButton = (
|
||||
<AccessibleButton className="mx_MessageContextMenu_field" onClick={this.onResendReactionsClick}>
|
||||
<MenuItem className="mx_MessageContextMenu_field" onClick={this.onResendReactionsClick}>
|
||||
{ _t('Resend %(unsentCount)s reaction(s)', {unsentCount: unsentReactionsCount}) }
|
||||
</AccessibleButton>
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (redactStatus === EventStatus.NOT_SENT) {
|
||||
resendRedactionButton = (
|
||||
<AccessibleButton className="mx_MessageContextMenu_field" onClick={this.onResendRedactionClick}>
|
||||
<MenuItem className="mx_MessageContextMenu_field" onClick={this.onResendRedactionClick}>
|
||||
{ _t('Resend removal') }
|
||||
</AccessibleButton>
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
if (isSent && this.state.canRedact) {
|
||||
redactButton = (
|
||||
<AccessibleButton className="mx_MessageContextMenu_field" onClick={this.onRedactClick}>
|
||||
<MenuItem className="mx_MessageContextMenu_field" onClick={this.onRedactClick}>
|
||||
{ _t('Remove') }
|
||||
</AccessibleButton>
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
if (allowCancel) {
|
||||
cancelButton = (
|
||||
<AccessibleButton className="mx_MessageContextMenu_field" onClick={this.onCancelSendClick}>
|
||||
<MenuItem className="mx_MessageContextMenu_field" onClick={this.onCancelSendClick}>
|
||||
{ _t('Cancel Sending') }
|
||||
</AccessibleButton>
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
if (isContentActionable(mxEvent)) {
|
||||
forwardButton = (
|
||||
<AccessibleButton className="mx_MessageContextMenu_field" onClick={this.onForwardClick}>
|
||||
<MenuItem className="mx_MessageContextMenu_field" onClick={this.onForwardClick}>
|
||||
{ _t('Forward Message') }
|
||||
</AccessibleButton>
|
||||
</MenuItem>
|
||||
);
|
||||
|
||||
if (this.state.canPin) {
|
||||
pinButton = (
|
||||
<AccessibleButton className="mx_MessageContextMenu_field" onClick={this.onPinClick}>
|
||||
<MenuItem className="mx_MessageContextMenu_field" onClick={this.onPinClick}>
|
||||
{ this._isPinned() ? _t('Unpin Message') : _t('Pin Message') }
|
||||
</AccessibleButton>
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const viewSourceButton = (
|
||||
<AccessibleButton className="mx_MessageContextMenu_field" onClick={this.onViewSourceClick}>
|
||||
<MenuItem className="mx_MessageContextMenu_field" onClick={this.onViewSourceClick}>
|
||||
{ _t('View Source') }
|
||||
</AccessibleButton>
|
||||
</MenuItem>
|
||||
);
|
||||
|
||||
if (mxEvent.getType() !== mxEvent.getWireType()) {
|
||||
viewClearSourceButton = (
|
||||
<AccessibleButton className="mx_MessageContextMenu_field" onClick={this.onViewClearSourceClick}>
|
||||
<MenuItem className="mx_MessageContextMenu_field" onClick={this.onViewClearSourceClick}>
|
||||
{ _t('View Decrypted Source') }
|
||||
</AccessibleButton>
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.eventTileOps) {
|
||||
if (this.props.eventTileOps.isWidgetHidden()) {
|
||||
unhidePreviewButton = (
|
||||
<AccessibleButton className="mx_MessageContextMenu_field" onClick={this.onUnhidePreviewClick}>
|
||||
<MenuItem className="mx_MessageContextMenu_field" onClick={this.onUnhidePreviewClick}>
|
||||
{ _t('Unhide Preview') }
|
||||
</AccessibleButton>
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -415,19 +414,19 @@ module.exports = createReactClass({
|
|||
}
|
||||
// XXX: if we use room ID, we should also include a server where the event can be found (other than in the domain of the event ID)
|
||||
const permalinkButton = (
|
||||
<AccessibleButton className="mx_MessageContextMenu_field">
|
||||
<MenuItem className="mx_MessageContextMenu_field">
|
||||
<a href={permalink} target="_blank" rel="noopener" onClick={this.onPermalinkClick} tabIndex={-1}>
|
||||
{ mxEvent.isRedacted() || mxEvent.getType() !== 'm.room.message'
|
||||
? _t('Share Permalink') : _t('Share Message') }
|
||||
</a>
|
||||
</AccessibleButton>
|
||||
</MenuItem>
|
||||
);
|
||||
|
||||
if (this.props.eventTileOps && this.props.eventTileOps.getInnerText) {
|
||||
quoteButton = (
|
||||
<AccessibleButton className="mx_MessageContextMenu_field" onClick={this.onQuoteClick}>
|
||||
<MenuItem className="mx_MessageContextMenu_field" onClick={this.onQuoteClick}>
|
||||
{ _t('Quote') }
|
||||
</AccessibleButton>
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -437,7 +436,7 @@ module.exports = createReactClass({
|
|||
isUrlPermitted(mxEvent.event.content.external_url)
|
||||
) {
|
||||
externalURLButton = (
|
||||
<AccessibleButton className="mx_MessageContextMenu_field">
|
||||
<MenuItem className="mx_MessageContextMenu_field">
|
||||
<a
|
||||
href={mxEvent.event.content.external_url}
|
||||
target="_blank"
|
||||
|
@ -447,33 +446,33 @@ module.exports = createReactClass({
|
|||
>
|
||||
{ _t('Source URL') }
|
||||
</a>
|
||||
</AccessibleButton>
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.collapseReplyThread) {
|
||||
collapseReplyThread = (
|
||||
<AccessibleButton className="mx_MessageContextMenu_field" onClick={this.onCollapseReplyThreadClick}>
|
||||
<MenuItem className="mx_MessageContextMenu_field" onClick={this.onCollapseReplyThreadClick}>
|
||||
{ _t('Collapse Reply Thread') }
|
||||
</AccessibleButton>
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
let e2eInfo;
|
||||
if (this.props.e2eInfoCallback) {
|
||||
e2eInfo = (
|
||||
<AccessibleButton className="mx_MessageContextMenu_field" onClick={this.e2eInfoClicked}>
|
||||
<MenuItem className="mx_MessageContextMenu_field" onClick={this.e2eInfoClicked}>
|
||||
{ _t('End-to-end encryption information') }
|
||||
</AccessibleButton>
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
let reportEventButton;
|
||||
if (mxEvent.getSender() !== me) {
|
||||
reportEventButton = (
|
||||
<AccessibleButton className="mx_MessageContextMenu_field" onClick={this.onReportEventClick}>
|
||||
<MenuItem className="mx_MessageContextMenu_field" onClick={this.onReportEventClick}>
|
||||
{ _t('Report Content') }
|
||||
</AccessibleButton>
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -32,6 +32,36 @@ import Modal from '../../../Modal';
|
|||
import RoomListActions from '../../../actions/RoomListActions';
|
||||
import RoomViewStore from '../../../stores/RoomViewStore';
|
||||
import {sleep} from "../../../utils/promise";
|
||||
import {MenuItem, MenuItemCheckbox, MenuItemRadio} from "../../structures/ContextMenu";
|
||||
|
||||
const RoomTagOption = ({active, onClick, src, srcSet, label}) => {
|
||||
const classes = classNames('mx_RoomTileContextMenu_tag_field', {
|
||||
'mx_RoomTileContextMenu_tag_fieldSet': active,
|
||||
'mx_RoomTileContextMenu_tag_fieldDisabled': false,
|
||||
});
|
||||
|
||||
return (
|
||||
<MenuItemCheckbox className={classes} onClick={onClick} active={active} label={label}>
|
||||
<img className="mx_RoomTileContextMenu_tag_icon" src={src} width="15" height="15" alt="" />
|
||||
<img className="mx_RoomTileContextMenu_tag_icon_set" src={srcSet} width="15" height="15" alt="" />
|
||||
{ label }
|
||||
</MenuItemCheckbox>
|
||||
);
|
||||
};
|
||||
|
||||
const NotifOption = ({active, onClick, src, label}) => {
|
||||
const classes = classNames('mx_RoomTileContextMenu_notif_field', {
|
||||
'mx_RoomTileContextMenu_notif_fieldSet': active,
|
||||
});
|
||||
|
||||
return (
|
||||
<MenuItemRadio className={classes} onClick={onClick} active={active} label={label}>
|
||||
<img className="mx_RoomTileContextMenu_notif_activeIcon" src={require("../../../../res/img/notif-active.svg")} width="12" height="12" alt="" />
|
||||
<img className="mx_RoomTileContextMenu_notif_icon mx_filterFlipColor" src={src} width="16" height="12" alt="" />
|
||||
{ label }
|
||||
</MenuItemRadio>
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = createReactClass({
|
||||
displayName: 'RoomTileContextMenu',
|
||||
|
@ -228,53 +258,36 @@ module.exports = createReactClass({
|
|||
},
|
||||
|
||||
_renderNotifMenu: function() {
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
|
||||
const alertMeClasses = classNames({
|
||||
'mx_RoomTileContextMenu_notif_field': true,
|
||||
'mx_RoomTileContextMenu_notif_fieldSet': this.state.roomNotifState == RoomNotifs.ALL_MESSAGES_LOUD,
|
||||
});
|
||||
|
||||
const allNotifsClasses = classNames({
|
||||
'mx_RoomTileContextMenu_notif_field': true,
|
||||
'mx_RoomTileContextMenu_notif_fieldSet': this.state.roomNotifState == RoomNotifs.ALL_MESSAGES,
|
||||
});
|
||||
|
||||
const mentionsClasses = classNames({
|
||||
'mx_RoomTileContextMenu_notif_field': true,
|
||||
'mx_RoomTileContextMenu_notif_fieldSet': this.state.roomNotifState == RoomNotifs.MENTIONS_ONLY,
|
||||
});
|
||||
|
||||
const muteNotifsClasses = classNames({
|
||||
'mx_RoomTileContextMenu_notif_field': true,
|
||||
'mx_RoomTileContextMenu_notif_fieldSet': this.state.roomNotifState == RoomNotifs.MUTE,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="mx_RoomTileContextMenu">
|
||||
<div className="mx_RoomTileContextMenu_notif_picker">
|
||||
<img src={require("../../../../res/img/notif-slider.svg")} width="20" height="107" />
|
||||
<div className="mx_RoomTileContextMenu" role="group" aria-label={_t("Notification settings")}>
|
||||
<div className="mx_RoomTileContextMenu_notif_picker" role="presentation">
|
||||
<img src={require("../../../../res/img/notif-slider.svg")} width="20" height="107" alt="" />
|
||||
</div>
|
||||
<AccessibleButton className={alertMeClasses} onClick={this._onClickAlertMe}>
|
||||
<img className="mx_RoomTileContextMenu_notif_activeIcon" src={require("../../../../res/img/notif-active.svg")} width="12" height="12" />
|
||||
<img className="mx_RoomTileContextMenu_notif_icon mx_filterFlipColor" src={require("../../../../res/img/icon-context-mute-off-copy.svg")} width="16" height="12" />
|
||||
{ _t('All messages (noisy)') }
|
||||
</AccessibleButton>
|
||||
<AccessibleButton className={allNotifsClasses} onClick={this._onClickAllNotifs}>
|
||||
<img className="mx_RoomTileContextMenu_notif_activeIcon" src={require("../../../../res/img/notif-active.svg")} width="12" height="12" />
|
||||
<img className="mx_RoomTileContextMenu_notif_icon mx_filterFlipColor" src={require("../../../../res/img/icon-context-mute-off.svg")} width="16" height="12" />
|
||||
{ _t('All messages') }
|
||||
</AccessibleButton>
|
||||
<AccessibleButton className={mentionsClasses} onClick={this._onClickMentions}>
|
||||
<img className="mx_RoomTileContextMenu_notif_activeIcon" src={require("../../../../res/img/notif-active.svg")} width="12" height="12" />
|
||||
<img className="mx_RoomTileContextMenu_notif_icon mx_filterFlipColor" src={require("../../../../res/img/icon-context-mute-mentions.svg")} width="16" height="12" />
|
||||
{ _t('Mentions only') }
|
||||
</AccessibleButton>
|
||||
<AccessibleButton className={muteNotifsClasses} onClick={this._onClickMute}>
|
||||
<img className="mx_RoomTileContextMenu_notif_activeIcon" src={require("../../../../res/img/notif-active.svg")} width="12" height="12" />
|
||||
<img className="mx_RoomTileContextMenu_notif_icon mx_filterFlipColor" src={require("../../../../res/img/icon-context-mute.svg")} width="16" height="12" />
|
||||
{ _t('Mute') }
|
||||
</AccessibleButton>
|
||||
|
||||
<NotifOption
|
||||
active={this.state.roomNotifState === RoomNotifs.ALL_MESSAGES_LOUD}
|
||||
label={_t('All messages (noisy)')}
|
||||
onClick={this._onClickAlertMe}
|
||||
src={require("../../../../res/img/icon-context-mute-off-copy.svg")}
|
||||
/>
|
||||
<NotifOption
|
||||
active={this.state.roomNotifState === RoomNotifs.ALL_MESSAGES}
|
||||
label={_t('All messages')}
|
||||
onClick={this._onClickAllNotifs}
|
||||
src={require("../../../../res/img/icon-context-mute-off.svg")}
|
||||
/>
|
||||
<NotifOption
|
||||
active={this.state.roomNotifState === RoomNotifs.MENTIONS_ONLY}
|
||||
label={_t('Mentions only')}
|
||||
onClick={this._onClickMentions}
|
||||
src={require("../../../../res/img/icon-context-mute-mentions.svg")}
|
||||
/>
|
||||
<NotifOption
|
||||
active={this.state.roomNotifState === RoomNotifs.MUTE}
|
||||
label={_t('Mute')}
|
||||
onClick={this._onClickMute}
|
||||
src={require("../../../../res/img/icon-context-mute.svg")}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
@ -290,13 +303,12 @@ module.exports = createReactClass({
|
|||
},
|
||||
|
||||
_renderSettingsMenu: function() {
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
return (
|
||||
<div>
|
||||
<AccessibleButton className="mx_RoomTileContextMenu_tag_field" onClick={this._onClickSettings} >
|
||||
<img className="mx_RoomTileContextMenu_tag_icon" src={require("../../../../res/img/icons-settings-room.svg")} width="15" height="15" />
|
||||
<MenuItem className="mx_RoomTileContextMenu_tag_field" onClick={this._onClickSettings}>
|
||||
<img className="mx_RoomTileContextMenu_tag_icon" src={require("../../../../res/img/icons-settings-room.svg")} width="15" height="15" alt="" />
|
||||
{ _t('Settings') }
|
||||
</AccessibleButton>
|
||||
</MenuItem>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
@ -306,8 +318,6 @@ module.exports = createReactClass({
|
|||
return null;
|
||||
}
|
||||
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
|
||||
let leaveClickHandler = null;
|
||||
let leaveText = null;
|
||||
|
||||
|
@ -329,52 +339,38 @@ module.exports = createReactClass({
|
|||
|
||||
return (
|
||||
<div>
|
||||
<AccessibleButton className="mx_RoomTileContextMenu_leave" onClick={leaveClickHandler} >
|
||||
<img className="mx_RoomTileContextMenu_tag_icon" src={require("../../../../res/img/icon_context_delete.svg")} width="15" height="15" />
|
||||
<MenuItem className="mx_RoomTileContextMenu_leave" onClick={leaveClickHandler}>
|
||||
<img className="mx_RoomTileContextMenu_tag_icon" src={require("../../../../res/img/icon_context_delete.svg")} width="15" height="15" alt="" />
|
||||
{ leaveText }
|
||||
</AccessibleButton>
|
||||
</MenuItem>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
_renderRoomTagMenu: function() {
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
|
||||
const favouriteClasses = classNames({
|
||||
'mx_RoomTileContextMenu_tag_field': true,
|
||||
'mx_RoomTileContextMenu_tag_fieldSet': this.state.isFavourite,
|
||||
'mx_RoomTileContextMenu_tag_fieldDisabled': false,
|
||||
});
|
||||
|
||||
const lowPriorityClasses = classNames({
|
||||
'mx_RoomTileContextMenu_tag_field': true,
|
||||
'mx_RoomTileContextMenu_tag_fieldSet': this.state.isLowPriority,
|
||||
'mx_RoomTileContextMenu_tag_fieldDisabled': false,
|
||||
});
|
||||
|
||||
const dmClasses = classNames({
|
||||
'mx_RoomTileContextMenu_tag_field': true,
|
||||
'mx_RoomTileContextMenu_tag_fieldSet': this.state.isDirectMessage,
|
||||
'mx_RoomTileContextMenu_tag_fieldDisabled': false,
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<AccessibleButton className={favouriteClasses} onClick={this._onClickFavourite} >
|
||||
<img className="mx_RoomTileContextMenu_tag_icon" src={require("../../../../res/img/icon_context_fave.svg")} width="15" height="15" />
|
||||
<img className="mx_RoomTileContextMenu_tag_icon_set" src={require("../../../../res/img/icon_context_fave_on.svg")} width="15" height="15" />
|
||||
{ _t('Favourite') }
|
||||
</AccessibleButton>
|
||||
<AccessibleButton className={lowPriorityClasses} onClick={this._onClickLowPriority} >
|
||||
<img className="mx_RoomTileContextMenu_tag_icon" src={require("../../../../res/img/icon_context_low.svg")} width="15" height="15" />
|
||||
<img className="mx_RoomTileContextMenu_tag_icon_set" src={require("../../../../res/img/icon_context_low_on.svg")} width="15" height="15" />
|
||||
{ _t('Low Priority') }
|
||||
</AccessibleButton>
|
||||
<AccessibleButton className={dmClasses} onClick={this._onClickDM} >
|
||||
<img className="mx_RoomTileContextMenu_tag_icon" src={require("../../../../res/img/icon_context_person.svg")} width="15" height="15" />
|
||||
<img className="mx_RoomTileContextMenu_tag_icon_set" src={require("../../../../res/img/icon_context_person_on.svg")} width="15" height="15" />
|
||||
{ _t('Direct Chat') }
|
||||
</AccessibleButton>
|
||||
<RoomTagOption
|
||||
active={this.state.isFavourite}
|
||||
label={_t('Favourite')}
|
||||
onClick={this._onClickFavourite}
|
||||
src={require("../../../../res/img/icon_context_fave.svg")}
|
||||
srcSet={require("../../../../res/img/icon_context_fave_on.svg")}
|
||||
/>
|
||||
<RoomTagOption
|
||||
active={this.state.isLowPriority}
|
||||
label={_t('Low Priority')}
|
||||
onClick={this._onClickLowPriority}
|
||||
src={require("../../../../res/img/icon_context_low.svg")}
|
||||
srcSet={require("../../../../res/img/icon_context_low_on.svg")}
|
||||
/>
|
||||
<RoomTagOption
|
||||
active={this.state.isDirectMessage}
|
||||
label={_t('Direct Chat')}
|
||||
onClick={this._onClickDM}
|
||||
src={require("../../../../res/img/icon_context_person.svg")}
|
||||
srcSet={require("../../../../res/img/icon_context_person_on.svg")}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
@ -386,11 +382,11 @@ module.exports = createReactClass({
|
|||
case 'join':
|
||||
return <div>
|
||||
{ this._renderNotifMenu() }
|
||||
<hr className="mx_RoomTileContextMenu_separator" />
|
||||
<hr className="mx_RoomTileContextMenu_separator" role="separator" />
|
||||
{ this._renderLeaveMenu(myMembership) }
|
||||
<hr className="mx_RoomTileContextMenu_separator" />
|
||||
<hr className="mx_RoomTileContextMenu_separator" role="separator" />
|
||||
{ this._renderRoomTagMenu() }
|
||||
<hr className="mx_RoomTileContextMenu_separator" />
|
||||
<hr className="mx_RoomTileContextMenu_separator" role="separator" />
|
||||
{ this._renderSettingsMenu() }
|
||||
</div>;
|
||||
case 'invite':
|
||||
|
@ -400,7 +396,7 @@ module.exports = createReactClass({
|
|||
default:
|
||||
return <div>
|
||||
{ this._renderLeaveMenu(myMembership) }
|
||||
<hr className="mx_RoomTileContextMenu_separator" />
|
||||
<hr className="mx_RoomTileContextMenu_separator" role="separator" />
|
||||
{ this._renderSettingsMenu() }
|
||||
</div>;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright 2018 New Vector Ltd
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -16,11 +17,12 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { MatrixClient } from 'matrix-js-sdk';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import dis from '../../../dispatcher';
|
||||
import TagOrderActions from '../../../actions/TagOrderActions';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import sdk from '../../../index';
|
||||
import {MenuItem} from "../../structures/ContextMenu";
|
||||
|
||||
export default class TagTileContextMenu extends React.Component {
|
||||
static propTypes = {
|
||||
|
@ -29,6 +31,10 @@ export default class TagTileContextMenu extends React.Component {
|
|||
onFinished: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
|
@ -45,18 +51,15 @@ export default class TagTileContextMenu extends React.Component {
|
|||
}
|
||||
|
||||
_onRemoveClick() {
|
||||
dis.dispatch(TagOrderActions.removeTag(
|
||||
// XXX: Context menus don't have a MatrixClient context
|
||||
MatrixClientPeg.get(),
|
||||
this.props.tag,
|
||||
));
|
||||
dis.dispatch(TagOrderActions.removeTag(this.context.matrixClient, this.props.tag));
|
||||
this.props.onFinished();
|
||||
}
|
||||
|
||||
render() {
|
||||
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||
|
||||
return <div>
|
||||
<div className="mx_TagTileContextMenu_item" onClick={this._onViewCommunityClick} >
|
||||
<MenuItem className="mx_TagTileContextMenu_item" onClick={this._onViewCommunityClick}>
|
||||
<TintableSvg
|
||||
className="mx_TagTileContextMenu_item_icon"
|
||||
src={require("../../../../res/img/icons-groups.svg")}
|
||||
|
@ -64,12 +67,12 @@ export default class TagTileContextMenu extends React.Component {
|
|||
height="15"
|
||||
/>
|
||||
{ _t('View Community') }
|
||||
</div>
|
||||
<hr className="mx_TagTileContextMenu_separator" />
|
||||
<div className="mx_TagTileContextMenu_item" onClick={this._onRemoveClick} >
|
||||
<img className="mx_TagTileContextMenu_item_icon" src={require("../../../../res/img/icon_context_delete.svg")} width="15" height="15" />
|
||||
</MenuItem>
|
||||
<hr className="mx_TagTileContextMenu_separator" role="separator" />
|
||||
<MenuItem className="mx_TagTileContextMenu_item" onClick={this._onRemoveClick}>
|
||||
<img className="mx_TagTileContextMenu_item_icon" src={require("../../../../res/img/icon_context_delete.svg")} width="15" height="15" alt="" />
|
||||
{ _t('Hide') }
|
||||
</div>
|
||||
</MenuItem>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ import Modal from "../../../Modal";
|
|||
import SdkConfig from '../../../SdkConfig';
|
||||
import { getHostingLink } from '../../../utils/HostingLink';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import sdk from "../../../index";
|
||||
import {MenuItem} from "../../structures/ContextMenu";
|
||||
|
||||
export class TopLeftMenu extends React.Component {
|
||||
static propTypes = {
|
||||
|
@ -58,8 +58,6 @@ export class TopLeftMenu extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
|
||||
const isGuest = MatrixClientPeg.get().isGuest();
|
||||
|
||||
const hostingSignupLink = getHostingLink('user-context-menu');
|
||||
|
@ -69,10 +67,10 @@ export class TopLeftMenu extends React.Component {
|
|||
{_t(
|
||||
"<a>Upgrade</a> to your own domain", {},
|
||||
{
|
||||
a: sub => <a href={hostingSignupLink} target="_blank" rel="noopener" tabIndex="0">{sub}</a>,
|
||||
a: sub => <a href={hostingSignupLink} target="_blank" rel="noopener" tabIndex={-1}>{sub}</a>,
|
||||
},
|
||||
)}
|
||||
<a href={hostingSignupLink} target="_blank" rel="noopener" aria-hidden={true}>
|
||||
<a href={hostingSignupLink} target="_blank" rel="noopener" role="presentation" aria-hidden={true} tabIndex={-1}>
|
||||
<img src={require("../../../../res/img/external-link.svg")} width="11" height="10" alt='' />
|
||||
</a>
|
||||
</div>;
|
||||
|
@ -81,40 +79,40 @@ export class TopLeftMenu extends React.Component {
|
|||
let homePageItem = null;
|
||||
if (this.hasHomePage()) {
|
||||
homePageItem = (
|
||||
<AccessibleButton element="li" className="mx_TopLeftMenu_icon_home" onClick={this.viewHomePage}>
|
||||
<MenuItem className="mx_TopLeftMenu_icon_home" onClick={this.viewHomePage}>
|
||||
{_t("Home")}
|
||||
</AccessibleButton>
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
let signInOutItem;
|
||||
if (isGuest) {
|
||||
signInOutItem = (
|
||||
<AccessibleButton element="li" className="mx_TopLeftMenu_icon_signin" onClick={this.signIn}>
|
||||
<MenuItem className="mx_TopLeftMenu_icon_signin" onClick={this.signIn}>
|
||||
{_t("Sign in")}
|
||||
</AccessibleButton>
|
||||
</MenuItem>
|
||||
);
|
||||
} else {
|
||||
signInOutItem = (
|
||||
<AccessibleButton element="li" className="mx_TopLeftMenu_icon_signout" onClick={this.signOut}>
|
||||
<MenuItem className="mx_TopLeftMenu_icon_signout" onClick={this.signOut}>
|
||||
{_t("Sign out")}
|
||||
</AccessibleButton>
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
const settingsItem = (
|
||||
<AccessibleButton element="li" className="mx_TopLeftMenu_icon_settings" onClick={this.openSettings}>
|
||||
<MenuItem className="mx_TopLeftMenu_icon_settings" onClick={this.openSettings}>
|
||||
{_t("Settings")}
|
||||
</AccessibleButton>
|
||||
</MenuItem>
|
||||
);
|
||||
|
||||
return <div className="mx_TopLeftMenu" ref={this.props.containerRef}>
|
||||
<div className="mx_TopLeftMenu_section_noIcon" aria-readonly={true}>
|
||||
return <div className="mx_TopLeftMenu" ref={this.props.containerRef} role="menu">
|
||||
<div className="mx_TopLeftMenu_section_noIcon" aria-readonly={true} tabIndex={-1}>
|
||||
<div>{this.props.displayName}</div>
|
||||
<div className="mx_TopLeftMenu_greyedText" aria-hidden={true}>{this.props.userId}</div>
|
||||
{hostingSignup}
|
||||
</div>
|
||||
<ul className="mx_TopLeftMenu_section_withIcon">
|
||||
<ul className="mx_TopLeftMenu_section_withIcon" role="none">
|
||||
{homePageItem}
|
||||
{settingsItem}
|
||||
{signInOutItem}
|
||||
|
|
|
@ -16,8 +16,8 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import sdk from '../../../index';
|
||||
import {_t} from '../../../languageHandler';
|
||||
import {MenuItem} from "../../structures/ContextMenu";
|
||||
|
||||
export default class WidgetContextMenu extends React.Component {
|
||||
static propTypes = {
|
||||
|
@ -71,50 +71,45 @@ export default class WidgetContextMenu extends React.Component {
|
|||
};
|
||||
|
||||
render() {
|
||||
const AccessibleButton = sdk.getComponent("views.elements.AccessibleButton");
|
||||
|
||||
const options = [];
|
||||
|
||||
if (this.props.onEditClicked) {
|
||||
options.push(
|
||||
<AccessibleButton className='mx_WidgetContextMenu_option' onClick={this.onEditClicked} key='edit'>
|
||||
<MenuItem className='mx_WidgetContextMenu_option' onClick={this.onEditClicked} key='edit'>
|
||||
{_t("Edit")}
|
||||
</AccessibleButton>,
|
||||
</MenuItem>,
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.onReloadClicked) {
|
||||
options.push(
|
||||
<AccessibleButton className='mx_WidgetContextMenu_option' onClick={this.onReloadClicked}
|
||||
key='reload'>
|
||||
<MenuItem className='mx_WidgetContextMenu_option' onClick={this.onReloadClicked} key='reload'>
|
||||
{_t("Reload")}
|
||||
</AccessibleButton>,
|
||||
</MenuItem>,
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.onSnapshotClicked) {
|
||||
options.push(
|
||||
<AccessibleButton className='mx_WidgetContextMenu_option' onClick={this.onSnapshotClicked}
|
||||
key='snap'>
|
||||
<MenuItem className='mx_WidgetContextMenu_option' onClick={this.onSnapshotClicked} key='snap'>
|
||||
{_t("Take picture")}
|
||||
</AccessibleButton>,
|
||||
</MenuItem>,
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.onDeleteClicked) {
|
||||
options.push(
|
||||
<AccessibleButton className='mx_WidgetContextMenu_option' onClick={this.onDeleteClicked}
|
||||
key='delete'>
|
||||
<MenuItem className='mx_WidgetContextMenu_option' onClick={this.onDeleteClicked} key='delete'>
|
||||
{_t("Remove for everyone")}
|
||||
</AccessibleButton>,
|
||||
</MenuItem>,
|
||||
);
|
||||
}
|
||||
|
||||
// Push this last so it appears last. It's always present.
|
||||
options.push(
|
||||
<AccessibleButton className='mx_WidgetContextMenu_option' onClick={this.onRevokeClicked} key='revoke'>
|
||||
<MenuItem className='mx_WidgetContextMenu_option' onClick={this.onRevokeClicked} key='revoke'>
|
||||
{_t("Remove for me")}
|
||||
</AccessibleButton>,
|
||||
</MenuItem>,
|
||||
);
|
||||
|
||||
// Put separators between the options
|
||||
|
|
|
@ -17,7 +17,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, {createRef} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import createReactClass from 'create-react-class';
|
||||
|
||||
|
@ -106,10 +106,14 @@ module.exports = createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
UNSAFE_componentWillMount: function() {
|
||||
this._textinput = createRef();
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
if (this.props.focus) {
|
||||
// Set the cursor at the end of the text input
|
||||
this.refs.textinput.value = this.props.value;
|
||||
this._textinput.current.value = this.props.value;
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -126,8 +130,8 @@ module.exports = createReactClass({
|
|||
let selectedList = this.state.selectedList.slice();
|
||||
// Check the text input field to see if user has an unconverted address
|
||||
// If there is and it's valid add it to the local selectedList
|
||||
if (this.refs.textinput.value !== '') {
|
||||
selectedList = this._addAddressesToList([this.refs.textinput.value]);
|
||||
if (this._textinput.current.value !== '') {
|
||||
selectedList = this._addAddressesToList([this._textinput.current.value]);
|
||||
if (selectedList === null) return;
|
||||
}
|
||||
this.props.onFinished(true, selectedList);
|
||||
|
@ -154,23 +158,23 @@ module.exports = createReactClass({
|
|||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
if (this.addressSelector) this.addressSelector.chooseSelection();
|
||||
} else if (this.refs.textinput.value.length === 0 && this.state.selectedList.length && e.keyCode === 8) { // backspace
|
||||
} else if (this._textinput.current.value.length === 0 && this.state.selectedList.length && e.keyCode === 8) { // backspace
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
this.onDismissed(this.state.selectedList.length - 1)();
|
||||
} else if (e.keyCode === 13) { // enter
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
if (this.refs.textinput.value === '') {
|
||||
if (this._textinput.current.value === '') {
|
||||
// if there's nothing in the input box, submit the form
|
||||
this.onButtonClick();
|
||||
} else {
|
||||
this._addAddressesToList([this.refs.textinput.value]);
|
||||
this._addAddressesToList([this._textinput.current.value]);
|
||||
}
|
||||
} else if (e.keyCode === 188 || e.keyCode === 9) { // comma or tab
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
this._addAddressesToList([this.refs.textinput.value]);
|
||||
this._addAddressesToList([this._textinput.current.value]);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -647,7 +651,7 @@ module.exports = createReactClass({
|
|||
onPaste={this._onPaste}
|
||||
rows="1"
|
||||
id="textinput"
|
||||
ref="textinput"
|
||||
ref={this._textinput}
|
||||
className="mx_AddressPickerDialog_input"
|
||||
onChange={this.onQueryChanged}
|
||||
placeholder={this.getPlaceholder()}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -21,15 +22,12 @@ import sdk from '../../../index';
|
|||
import Analytics from '../../../Analytics';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import * as Lifecycle from '../../../Lifecycle';
|
||||
import Velocity from 'velocity-animate';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
export default class DeactivateAccountDialog extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this._passwordField = null;
|
||||
|
||||
this._onOk = this._onOk.bind(this);
|
||||
this._onCancel = this._onCancel.bind(this);
|
||||
this._onPasswordFieldChange = this._onPasswordFieldChange.bind(this);
|
||||
|
@ -78,7 +76,6 @@ export default class DeactivateAccountDialog extends React.Component {
|
|||
// https://matrix.org/jira/browse/SYN-744
|
||||
if (err.httpStatus === 401 || err.httpStatus === 403) {
|
||||
errStr = _t('Incorrect password');
|
||||
Velocity(this._passwordField, "callout.shake", 300);
|
||||
}
|
||||
this.setState({
|
||||
busy: false,
|
||||
|
@ -181,7 +178,6 @@ export default class DeactivateAccountDialog extends React.Component {
|
|||
label={_t('Password')}
|
||||
onChange={this._onPasswordFieldChange}
|
||||
value={this.state.password}
|
||||
ref={(e) => {this._passwordField = e;}}
|
||||
className={passwordBoxClass}
|
||||
/>
|
||||
</div>
|
||||
|
|
128
src/components/views/dialogs/RoomUpgradeWarningDialog.js
Normal file
128
src/components/views/dialogs/RoomUpgradeWarningDialog.js
Normal file
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {_t} from "../../../languageHandler";
|
||||
import sdk from "../../../index";
|
||||
import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
|
||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||
import Modal from "../../../Modal";
|
||||
|
||||
export default class RoomUpgradeWarningDialog extends React.Component {
|
||||
static propTypes = {
|
||||
onFinished: PropTypes.func.isRequired,
|
||||
roomId: PropTypes.string.isRequired,
|
||||
targetVersion: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
|
||||
const joinRules = room ? room.currentState.getStateEvents("m.room.join_rules", "") : null;
|
||||
const isPrivate = joinRules ? joinRules.getContent()['join_rule'] !== 'public' : true;
|
||||
this.state = {
|
||||
currentVersion: room ? room.getVersion() : "1",
|
||||
isPrivate,
|
||||
inviteUsersToNewRoom: true,
|
||||
};
|
||||
}
|
||||
|
||||
_onContinue = () => {
|
||||
this.props.onFinished({continue: true, invite: this.state.isPrivate && this.state.inviteUsersToNewRoom});
|
||||
};
|
||||
|
||||
_onCancel = () => {
|
||||
this.props.onFinished({continue: false, invite: false});
|
||||
};
|
||||
|
||||
_onInviteUsersToggle = (newVal) => {
|
||||
this.setState({inviteUsersToNewRoom: newVal});
|
||||
};
|
||||
|
||||
_openBugReportDialog = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const BugReportDialog = sdk.getComponent("dialogs.BugReportDialog");
|
||||
Modal.createTrackedDialog('Bug Report Dialog', '', BugReportDialog, {});
|
||||
};
|
||||
|
||||
render() {
|
||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||
|
||||
let inviteToggle = null;
|
||||
if (this.state.isPrivate) {
|
||||
inviteToggle = (
|
||||
<LabelledToggleSwitch
|
||||
value={this.state.inviteUsersToNewRoom}
|
||||
onChange={this._onInviteUsersToggle}
|
||||
label={_t("Automatically invite users")} />
|
||||
);
|
||||
}
|
||||
|
||||
const title = this.state.isPrivate ? _t("Upgrade private room") : _t("Upgrade public room");
|
||||
|
||||
return (
|
||||
<BaseDialog
|
||||
className='mx_RoomUpgradeWarningDialog'
|
||||
hasCancel={true}
|
||||
fixedWidth={false}
|
||||
onFinished={this.props.onFinished}
|
||||
title={title}
|
||||
>
|
||||
<div>
|
||||
<p>
|
||||
{_t(
|
||||
"Upgrading a room is an advanced action and is usually recommended when a room " +
|
||||
"is unstable due to bugs, missing features or security vulnerabilities.",
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
{_t(
|
||||
"This usually only affects how the room is processed on the server. If you're " +
|
||||
"having problems with your Riot, please <a>report a bug</a>.",
|
||||
{}, {
|
||||
"a": (sub) => {
|
||||
return <a href='#' onClick={this._openBugReportDialog}>{sub}</a>;
|
||||
},
|
||||
},
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
{_t(
|
||||
"You'll upgrade this room from <oldVersion /> to <newVersion />.",
|
||||
{},
|
||||
{
|
||||
oldVersion: () => <code>{this.state.currentVersion}</code>,
|
||||
newVersion: () => <code>{this.props.targetVersion}</code>,
|
||||
},
|
||||
)}
|
||||
</p>
|
||||
{inviteToggle}
|
||||
</div>
|
||||
<DialogButtons
|
||||
primaryButton={_t("Upgrade")}
|
||||
onPrimaryButtonClick={this._onContinue}
|
||||
cancelButton={_t("Cancel")}
|
||||
onCancel={this._onCancel}
|
||||
/>
|
||||
</BaseDialog>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, {createRef} from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
import sdk from '../../../index';
|
||||
|
@ -62,8 +62,13 @@ export default createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
UNSAFE_componentWillMount: function() {
|
||||
this._input_value = createRef();
|
||||
this._uiAuth = createRef();
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.refs.input_value.select();
|
||||
this._input_value.current.select();
|
||||
|
||||
this._matrixClient = MatrixClientPeg.get();
|
||||
},
|
||||
|
@ -102,8 +107,8 @@ export default createReactClass({
|
|||
},
|
||||
|
||||
onSubmit: function(ev) {
|
||||
if (this.refs.uiAuth) {
|
||||
this.refs.uiAuth.tryContinue();
|
||||
if (this._uiAuth.current) {
|
||||
this._uiAuth.current.tryContinue();
|
||||
}
|
||||
this.setState({
|
||||
doingUIAuth: true,
|
||||
|
@ -215,7 +220,7 @@ export default createReactClass({
|
|||
onAuthFinished={this._onUIAuthFinished}
|
||||
inputs={{}}
|
||||
poll={true}
|
||||
ref="uiAuth"
|
||||
ref={this._uiAuth}
|
||||
continueIsManaged={true}
|
||||
/>;
|
||||
}
|
||||
|
@ -257,7 +262,7 @@ export default createReactClass({
|
|||
>
|
||||
<div className="mx_Dialog_content" id='mx_Dialog_content'>
|
||||
<div className="mx_SetMxIdDialog_input_group">
|
||||
<input type="text" ref="input_value" value={this.state.username}
|
||||
<input type="text" ref={this._input_value} value={this.state.username}
|
||||
autoFocus={true}
|
||||
onChange={this.onValueChange}
|
||||
onKeyUp={this.onKeyUp}
|
||||
|
|
|
@ -14,14 +14,15 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, {createRef} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Room, User, Group, RoomMember, MatrixEvent} from 'matrix-js-sdk';
|
||||
import sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import QRCode from 'qrcode-react';
|
||||
import {RoomPermalinkCreator, makeGroupPermalink, makeUserPermalink} from "../../../utils/permalinks/Permalinks";
|
||||
import * as ContextualMenu from "../../structures/ContextualMenu";
|
||||
import * as ContextMenu from "../../structures/ContextMenu";
|
||||
import {toRightOf} from "../../structures/ContextMenu";
|
||||
|
||||
const socials = [
|
||||
{
|
||||
|
@ -73,6 +74,8 @@ export default class ShareDialog extends React.Component {
|
|||
// MatrixEvent defaults to share linkSpecificEvent
|
||||
linkSpecificEvent: this.props.target instanceof MatrixEvent,
|
||||
};
|
||||
|
||||
this._link = createRef();
|
||||
}
|
||||
|
||||
static _selectText(target) {
|
||||
|
@ -93,7 +96,7 @@ export default class ShareDialog extends React.Component {
|
|||
onCopyClick(e) {
|
||||
e.preventDefault();
|
||||
|
||||
ShareDialog._selectText(this.refs.link);
|
||||
ShareDialog._selectText(this._link.current);
|
||||
|
||||
let successful;
|
||||
try {
|
||||
|
@ -102,18 +105,12 @@ export default class ShareDialog extends React.Component {
|
|||
console.error('Failed to copy: ', err);
|
||||
}
|
||||
|
||||
const GenericTextContextMenu = sdk.getComponent('context_menus.GenericTextContextMenu');
|
||||
const buttonRect = e.target.getBoundingClientRect();
|
||||
|
||||
// The window X and Y offsets are to adjust position when zoomed in to page
|
||||
const x = buttonRect.right + window.pageXOffset;
|
||||
const y = (buttonRect.top + (buttonRect.height / 2) + window.pageYOffset) - 19;
|
||||
const {close} = ContextualMenu.createMenu(GenericTextContextMenu, {
|
||||
chevronOffset: 10,
|
||||
left: x,
|
||||
top: y,
|
||||
const GenericTextContextMenu = sdk.getComponent('context_menus.GenericTextContextMenu');
|
||||
const {close} = ContextMenu.createMenu(GenericTextContextMenu, {
|
||||
...toRightOf(buttonRect, 2),
|
||||
message: successful ? _t('Copied!') : _t('Failed to copy'),
|
||||
}, false);
|
||||
});
|
||||
// Drop a reference to this close handler for componentWillUnmount
|
||||
this.closeCopiedTooltip = e.target.onmouseleave = close;
|
||||
}
|
||||
|
@ -200,7 +197,7 @@ export default class ShareDialog extends React.Component {
|
|||
>
|
||||
<div className="mx_ShareDialog_content">
|
||||
<div className="mx_ShareDialog_matrixto">
|
||||
<a ref="link"
|
||||
<a ref={this._link}
|
||||
href={matrixToUrl}
|
||||
onClick={ShareDialog.onLinkClick}
|
||||
className="mx_ShareDialog_matrixto_link"
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, {createRef} from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
import sdk from '../../../index';
|
||||
|
@ -42,15 +42,19 @@ export default createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
UNSAFE_componentWillMount: function() {
|
||||
this._textinput = createRef();
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
if (this.props.focus) {
|
||||
// Set the cursor at the end of the text input
|
||||
this.refs.textinput.value = this.props.value;
|
||||
this._textinput.current.value = this.props.value;
|
||||
}
|
||||
},
|
||||
|
||||
onOk: function() {
|
||||
this.props.onFinished(true, this.refs.textinput.value);
|
||||
this.props.onFinished(true, this._textinput.current.value);
|
||||
},
|
||||
|
||||
onCancel: function() {
|
||||
|
@ -70,7 +74,13 @@ export default createReactClass({
|
|||
<label htmlFor="textinput"> { this.props.description } </label>
|
||||
</div>
|
||||
<div>
|
||||
<input id="textinput" ref="textinput" className="mx_TextInputDialog_input" defaultValue={this.props.value} autoFocus={this.props.focus} size="64" />
|
||||
<input
|
||||
id="textinput"
|
||||
ref={this._textinput}
|
||||
className="mx_TextInputDialog_input"
|
||||
defaultValue={this.props.value}
|
||||
autoFocus={this.props.focus}
|
||||
size="64" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -18,7 +18,7 @@ limitations under the License.
|
|||
|
||||
import url from 'url';
|
||||
import qs from 'querystring';
|
||||
import React from 'react';
|
||||
import React, {createRef} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import WidgetMessaging from '../../../WidgetMessaging';
|
||||
|
@ -35,7 +35,7 @@ import ActiveWidgetStore from '../../../stores/ActiveWidgetStore';
|
|||
import classNames from 'classnames';
|
||||
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
|
||||
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
|
||||
import {createMenu} from "../../structures/ContextualMenu";
|
||||
import {aboveLeftOf, ContextMenu, ContextMenuButton} from "../../structures/ContextMenu";
|
||||
import PersistedElement from "./PersistedElement";
|
||||
|
||||
const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:'];
|
||||
|
@ -62,6 +62,10 @@ export default class AppTile extends React.Component {
|
|||
this._revokeWidgetPermission = this._revokeWidgetPermission.bind(this);
|
||||
this._onPopoutWidgetClick = this._onPopoutWidgetClick.bind(this);
|
||||
this._onReloadWidgetClick = this._onReloadWidgetClick.bind(this);
|
||||
|
||||
this._contextMenuButton = createRef();
|
||||
this._appFrame = createRef();
|
||||
this._menu_bar = createRef();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -89,6 +93,7 @@ export default class AppTile extends React.Component {
|
|||
error: null,
|
||||
deleting: false,
|
||||
widgetPageTitle: newProps.widgetPageTitle,
|
||||
menuDisplayed: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -334,14 +339,14 @@ export default class AppTile extends React.Component {
|
|||
// HACK: This is a really dirty way to ensure that Jitsi cleans up
|
||||
// its hold on the webcam. Without this, the widget holds a media
|
||||
// stream open, even after death. See https://github.com/vector-im/riot-web/issues/7351
|
||||
if (this.refs.appFrame) {
|
||||
if (this._appFrame.current) {
|
||||
// In practice we could just do `+= ''` to trick the browser
|
||||
// into thinking the URL changed, however I can foresee this
|
||||
// being optimized out by a browser. Instead, we'll just point
|
||||
// the iframe at a page that is reasonably safe to use in the
|
||||
// event the iframe doesn't wink away.
|
||||
// This is relative to where the Riot instance is located.
|
||||
this.refs.appFrame.src = 'about:blank';
|
||||
this._appFrame.current.src = 'about:blank';
|
||||
}
|
||||
|
||||
WidgetUtils.setRoomWidget(
|
||||
|
@ -386,7 +391,7 @@ export default class AppTile extends React.Component {
|
|||
// FIXME: There's probably no reason to do this here: it should probably be done entirely
|
||||
// in ActiveWidgetStore.
|
||||
const widgetMessaging = new WidgetMessaging(
|
||||
this.props.id, this.props.url, this.props.userWidget, this.refs.appFrame.contentWindow);
|
||||
this.props.id, this.props.url, this.props.userWidget, this._appFrame.current.contentWindow);
|
||||
ActiveWidgetStore.setWidgetMessaging(this.props.id, widgetMessaging);
|
||||
widgetMessaging.getCapabilities().then((requestedCapabilities) => {
|
||||
console.log(`Widget ${this.props.id} requested capabilities: ` + requestedCapabilities);
|
||||
|
@ -493,7 +498,7 @@ export default class AppTile extends React.Component {
|
|||
ev.preventDefault();
|
||||
|
||||
// Ignore clicks on menu bar children
|
||||
if (ev.target !== this.refs.menu_bar) {
|
||||
if (ev.target !== this._menu_bar.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -552,48 +557,15 @@ export default class AppTile extends React.Component {
|
|||
|
||||
_onReloadWidgetClick() {
|
||||
// Reload iframe in this way to avoid cross-origin restrictions
|
||||
this.refs.appFrame.src = this.refs.appFrame.src;
|
||||
this._appFrame.current.src = this._appFrame.current.src;
|
||||
}
|
||||
|
||||
_getMenuOptions(ev) {
|
||||
// TODO: This block of code gets copy/pasted a lot. We should make that happen less.
|
||||
const menuOptions = {};
|
||||
const buttonRect = ev.target.getBoundingClientRect();
|
||||
// The window X and Y offsets are to adjust position when zoomed in to page
|
||||
const buttonLeft = buttonRect.left + window.pageXOffset;
|
||||
const buttonTop = buttonRect.top + window.pageYOffset;
|
||||
// Align the right edge of the menu to the left edge of the button
|
||||
menuOptions.right = window.innerWidth - buttonLeft;
|
||||
// Align the menu vertically on whichever side of the button has more
|
||||
// space available.
|
||||
if (buttonTop < window.innerHeight / 2) {
|
||||
menuOptions.top = buttonTop;
|
||||
} else {
|
||||
menuOptions.bottom = window.innerHeight - buttonTop;
|
||||
}
|
||||
return menuOptions;
|
||||
}
|
||||
_onContextMenuClick = () => {
|
||||
this.setState({ menuDisplayed: true });
|
||||
};
|
||||
|
||||
_onContextMenuClick = (ev) => {
|
||||
const WidgetContextMenu = sdk.getComponent('views.context_menus.WidgetContextMenu');
|
||||
const menuOptions = {
|
||||
...this._getMenuOptions(ev),
|
||||
|
||||
// A revoke handler is always required
|
||||
onRevokeClicked: this._onRevokeClicked,
|
||||
};
|
||||
|
||||
const canUserModify = this._canUserModify();
|
||||
const showEditButton = Boolean(this._scalarClient && canUserModify);
|
||||
const showDeleteButton = (this.props.showDelete === undefined || this.props.showDelete) && canUserModify;
|
||||
const showPictureSnapshotButton = this._hasCapability('m.capability.screenshot') && this.props.show;
|
||||
|
||||
if (showEditButton) menuOptions.onEditClicked = this._onEditClick;
|
||||
if (showDeleteButton) menuOptions.onDeleteClicked = this._onDeleteClick;
|
||||
if (showPictureSnapshotButton) menuOptions.onSnapshotClicked = this._onSnapshotClick;
|
||||
if (this.props.showReload) menuOptions.onReloadClicked = this._onReloadWidgetClick;
|
||||
|
||||
createMenu(WidgetContextMenu, menuOptions);
|
||||
_closeContextMenu = () => {
|
||||
this.setState({ menuDisplayed: false });
|
||||
};
|
||||
|
||||
render() {
|
||||
|
@ -601,7 +573,7 @@ export default class AppTile extends React.Component {
|
|||
|
||||
// Don't render widget if it is in the process of being deleted
|
||||
if (this.state.deleting) {
|
||||
return <div></div>;
|
||||
return <div />;
|
||||
}
|
||||
|
||||
// Note that there is advice saying allow-scripts shouldn't be used with allow-same-origin
|
||||
|
@ -656,7 +628,7 @@ export default class AppTile extends React.Component {
|
|||
{ this.state.loading && loadingElement }
|
||||
<iframe
|
||||
allow={iframeFeatures}
|
||||
ref="appFrame"
|
||||
ref={this._appFrame}
|
||||
src={this._getSafeUrl()}
|
||||
allowFullScreen={true}
|
||||
sandbox={sandboxFlags}
|
||||
|
@ -697,10 +669,34 @@ export default class AppTile extends React.Component {
|
|||
mx_AppTileMenuBar_expanded: this.props.show,
|
||||
});
|
||||
|
||||
return (
|
||||
let contextMenu;
|
||||
if (this.state.menuDisplayed) {
|
||||
const elementRect = this._contextMenuButton.current.getBoundingClientRect();
|
||||
|
||||
const canUserModify = this._canUserModify();
|
||||
const showEditButton = Boolean(this._scalarClient && canUserModify);
|
||||
const showDeleteButton = (this.props.showDelete === undefined || this.props.showDelete) && canUserModify;
|
||||
const showPictureSnapshotButton = this._hasCapability('m.capability.screenshot') && this.props.show;
|
||||
|
||||
const WidgetContextMenu = sdk.getComponent('views.context_menus.WidgetContextMenu');
|
||||
contextMenu = (
|
||||
<ContextMenu {...aboveLeftOf(elementRect, null)} onFinished={this._closeContextMenu}>
|
||||
<WidgetContextMenu
|
||||
onRevokeClicked={this._onRevokeClicked}
|
||||
onEditClicked={showEditButton ? this._onEditClick : undefined}
|
||||
onDeleteClicked={showDeleteButton ? this._onDeleteClick : undefined}
|
||||
onSnapshotClicked={showPictureSnapshotButton ? this._onSnapshotClick : undefined}
|
||||
onReloadClicked={this.props.showReload ? this._onReloadWidgetClick : undefined}
|
||||
onFinished={this._closeContextMenu}
|
||||
/>
|
||||
</ContextMenu>
|
||||
);
|
||||
}
|
||||
|
||||
return <React.Fragment>
|
||||
<div className={appTileClass} id={this.props.id}>
|
||||
{ this.props.showMenubar &&
|
||||
<div ref="menu_bar" className={menuBarClasses} onClick={this.onClickMenuBar}>
|
||||
<div ref={this._menu_bar} className={menuBarClasses} onClick={this.onClickMenuBar}>
|
||||
<span className="mx_AppTileMenuBarTitle" style={{pointerEvents: (this.props.handleMinimisePointerEvents ? 'all' : false)}}>
|
||||
{ /* Minimise widget */ }
|
||||
{ showMinimiseButton && <AccessibleButton
|
||||
|
@ -725,20 +721,24 @@ export default class AppTile extends React.Component {
|
|||
onClick={this._onPopoutWidgetClick}
|
||||
/> }
|
||||
{ /* Context menu */ }
|
||||
{ <AccessibleButton
|
||||
{ <ContextMenuButton
|
||||
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_menu"
|
||||
title={_t('More options')}
|
||||
label={_t('More options')}
|
||||
isExpanded={this.state.menuDisplayed}
|
||||
inputRef={this._contextMenuButton}
|
||||
onClick={this._onContextMenuClick}
|
||||
/> }
|
||||
</span>
|
||||
</div> }
|
||||
{ appTileBody }
|
||||
</div>
|
||||
);
|
||||
|
||||
{ contextMenu }
|
||||
</React.Fragment>;
|
||||
}
|
||||
}
|
||||
|
||||
AppTile.displayName ='AppTile';
|
||||
AppTile.displayName = 'AppTile';
|
||||
|
||||
AppTile.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
|
|
|
@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, {createRef} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import createReactClass from 'create-react-class';
|
||||
import {Key} from "../../../Keyboard";
|
||||
|
@ -65,7 +65,7 @@ module.exports = createReactClass({
|
|||
componentWillReceiveProps: function(nextProps) {
|
||||
if (nextProps.initialValue !== this.props.initialValue) {
|
||||
this.value = nextProps.initialValue;
|
||||
if (this.refs.editable_div) {
|
||||
if (this._editable_div.current) {
|
||||
this.showPlaceholder(!this.value);
|
||||
}
|
||||
}
|
||||
|
@ -76,24 +76,27 @@ module.exports = createReactClass({
|
|||
// as React doesn't play nice with contentEditable.
|
||||
this.value = '';
|
||||
this.placeholder = false;
|
||||
|
||||
this._editable_div = createRef();
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.value = this.props.initialValue;
|
||||
if (this.refs.editable_div) {
|
||||
if (this._editable_div.current) {
|
||||
this.showPlaceholder(!this.value);
|
||||
}
|
||||
},
|
||||
|
||||
showPlaceholder: function(show) {
|
||||
if (show) {
|
||||
this.refs.editable_div.textContent = this.props.placeholder;
|
||||
this.refs.editable_div.setAttribute("class", this.props.className + " " + this.props.placeholderClassName);
|
||||
this._editable_div.current.textContent = this.props.placeholder;
|
||||
this._editable_div.current.setAttribute("class", this.props.className
|
||||
+ " " + this.props.placeholderClassName);
|
||||
this.placeholder = true;
|
||||
this.value = '';
|
||||
} else {
|
||||
this.refs.editable_div.textContent = this.value;
|
||||
this.refs.editable_div.setAttribute("class", this.props.className);
|
||||
this._editable_div.current.textContent = this.value;
|
||||
this._editable_div.current.setAttribute("class", this.props.className);
|
||||
this.placeholder = false;
|
||||
}
|
||||
},
|
||||
|
@ -120,7 +123,7 @@ module.exports = createReactClass({
|
|||
this.value = this.props.initialValue;
|
||||
this.showPlaceholder(!this.value);
|
||||
this.onValueChanged(false);
|
||||
this.refs.editable_div.blur();
|
||||
this._editable_div.current.blur();
|
||||
},
|
||||
|
||||
onValueChanged: function(shouldSubmit) {
|
||||
|
@ -219,7 +222,7 @@ module.exports = createReactClass({
|
|||
</div>;
|
||||
} else {
|
||||
// show the content editable div, but manually manage its contents as react and contentEditable don't play nice together
|
||||
editableEl = <div ref="editable_div"
|
||||
editableEl = <div ref={this._editable_div}
|
||||
contentEditable={true}
|
||||
className={className}
|
||||
onKeyDown={this.onKeyDown}
|
||||
|
|
|
@ -90,7 +90,7 @@ function isInLowerLeftHalf(x, y, rect) {
|
|||
* tooltip along one edge of the target.
|
||||
*/
|
||||
export default class InteractiveTooltip extends React.Component {
|
||||
propTypes: {
|
||||
static propTypes = {
|
||||
// Content to show in the tooltip
|
||||
content: PropTypes.node.isRequired,
|
||||
// Function to call when visibility of the tooltip changes
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
Copyright 2017 New Vector Ltd.
|
||||
Copyright 2018 Michael Telatynski <7t3chguy@gmail.com>
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -15,20 +16,21 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, {createRef} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import createReactClass from 'create-react-class';
|
||||
import classNames from 'classnames';
|
||||
import { MatrixClient } from 'matrix-js-sdk';
|
||||
import sdk from '../../../index';
|
||||
import dis from '../../../dispatcher';
|
||||
import {_t} from '../../../languageHandler';
|
||||
import { isOnlyCtrlOrCmdIgnoreShiftKeyEvent } from '../../../Keyboard';
|
||||
import * as ContextualMenu from '../../structures/ContextualMenu';
|
||||
import * as FormattingUtils from '../../../utils/FormattingUtils';
|
||||
|
||||
import FlairStore from '../../../stores/FlairStore';
|
||||
import GroupStore from '../../../stores/GroupStore';
|
||||
import TagOrderStore from '../../../stores/TagOrderStore';
|
||||
import {ContextMenu, ContextMenuButton, toRightOf} from "../../structures/ContextMenu";
|
||||
|
||||
// A class for a child of TagPanel (possibly wrapped in a DNDTagTile) that represents
|
||||
// a thing to click on for the user to filter the visible rooms in the RoomList to:
|
||||
|
@ -58,6 +60,8 @@ export default createReactClass({
|
|||
},
|
||||
|
||||
componentDidMount() {
|
||||
this._contextMenuButton = createRef();
|
||||
|
||||
this.unmounted = false;
|
||||
if (this.props.tag[0] === '+') {
|
||||
FlairStore.addListener('updateGroupProfile', this._onFlairStoreUpdated);
|
||||
|
@ -107,56 +111,35 @@ export default createReactClass({
|
|||
}
|
||||
},
|
||||
|
||||
_openContextMenu: function(x, y, chevronOffset) {
|
||||
// Hide the (...) immediately
|
||||
this.setState({ hover: false });
|
||||
|
||||
const TagTileContextMenu = sdk.getComponent('context_menus.TagTileContextMenu');
|
||||
ContextualMenu.createMenu(TagTileContextMenu, {
|
||||
chevronOffset: chevronOffset,
|
||||
left: x,
|
||||
top: y,
|
||||
tag: this.props.tag,
|
||||
onFinished: () => {
|
||||
this.setState({ menuDisplayed: false });
|
||||
},
|
||||
});
|
||||
this.setState({ menuDisplayed: true });
|
||||
},
|
||||
|
||||
onContextButtonClick: function(e) {
|
||||
e.preventDefault();
|
||||
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.right + window.pageXOffset + 3;
|
||||
const chevronOffset = 12;
|
||||
let y = (elementRect.top + (elementRect.height / 2) + window.pageYOffset);
|
||||
y = y - (chevronOffset + 8); // where 8 is half the height of the chevron
|
||||
|
||||
this._openContextMenu(x, y, chevronOffset);
|
||||
},
|
||||
|
||||
onContextMenu: function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const chevronOffset = 12;
|
||||
this._openContextMenu(e.clientX, e.clientY - (chevronOffset + 8), chevronOffset);
|
||||
},
|
||||
|
||||
onMouseOver: function() {
|
||||
console.log("DEBUG onMouseOver");
|
||||
this.setState({hover: true});
|
||||
},
|
||||
|
||||
onMouseOut: function() {
|
||||
console.log("DEBUG onMouseOut");
|
||||
this.setState({hover: false});
|
||||
},
|
||||
|
||||
openMenu: function(e) {
|
||||
// Prevent the TagTile onClick event firing as well
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
this.setState({
|
||||
menuDisplayed: true,
|
||||
hover: false,
|
||||
});
|
||||
},
|
||||
|
||||
closeMenu: function() {
|
||||
this.setState({
|
||||
menuDisplayed: false,
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
const Tooltip = sdk.getComponent('elements.Tooltip');
|
||||
const profile = this.state.profile || {};
|
||||
const name = profile.name || this.props.tag;
|
||||
|
@ -184,23 +167,46 @@ export default createReactClass({
|
|||
const tip = this.state.hover ?
|
||||
<Tooltip className="mx_TagTile_tooltip" label={name} /> :
|
||||
<div />;
|
||||
// FIXME: this ought to use AccessibleButton for a11y but that causes onMouseOut/onMouseOver to fire too much
|
||||
const contextButton = this.state.hover || this.state.menuDisplayed ?
|
||||
<div className="mx_TagTile_context_button" onClick={this.onContextButtonClick}>
|
||||
<div className="mx_TagTile_context_button" onClick={this.openMenu} ref={this._contextMenuButton}>
|
||||
{ "\u00B7\u00B7\u00B7" }
|
||||
</div> : <div />;
|
||||
return <AccessibleButton className={className} onClick={this.onClick} onContextMenu={this.onContextMenu}>
|
||||
<div className="mx_TagTile_avatar" onMouseOver={this.onMouseOver} onMouseOut={this.onMouseOut}>
|
||||
<BaseAvatar
|
||||
name={name}
|
||||
idName={this.props.tag}
|
||||
url={httpUrl}
|
||||
width={avatarHeight}
|
||||
height={avatarHeight}
|
||||
/>
|
||||
{ tip }
|
||||
{ contextButton }
|
||||
{ badgeElement }
|
||||
</div>
|
||||
</AccessibleButton>;
|
||||
</div> : <div ref={this._contextMenuButton} />;
|
||||
|
||||
let contextMenu;
|
||||
if (this.state.menuDisplayed) {
|
||||
const elementRect = this._contextMenuButton.current.getBoundingClientRect();
|
||||
const TagTileContextMenu = sdk.getComponent('context_menus.TagTileContextMenu');
|
||||
contextMenu = (
|
||||
<ContextMenu {...toRightOf(elementRect)} onFinished={this.closeMenu}>
|
||||
<TagTileContextMenu tag={this.props.tag} onFinished={this.closeMenu} />
|
||||
</ContextMenu>
|
||||
);
|
||||
}
|
||||
|
||||
return <React.Fragment>
|
||||
<ContextMenuButton
|
||||
className={className}
|
||||
onClick={this.onClick}
|
||||
onContextMenu={this.openMenu}
|
||||
label={_t("Options")}
|
||||
isExpanded={this.state.menuDisplayed}
|
||||
>
|
||||
<div className="mx_TagTile_avatar" onMouseOver={this.onMouseOver} onMouseOut={this.onMouseOut}>
|
||||
<BaseAvatar
|
||||
name={name}
|
||||
idName={this.props.tag}
|
||||
url={httpUrl}
|
||||
width={avatarHeight}
|
||||
height={avatarHeight}
|
||||
/>
|
||||
{ tip }
|
||||
{ contextButton }
|
||||
{ badgeElement }
|
||||
</div>
|
||||
</ContextMenuButton>
|
||||
|
||||
{ contextMenu }
|
||||
</React.Fragment>;
|
||||
},
|
||||
});
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, {createRef} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import createReactClass from 'create-react-class';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
@ -34,6 +34,10 @@ module.exports = createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
UNSAFE_componentWillMount: function() {
|
||||
this._user_id_input = createRef();
|
||||
},
|
||||
|
||||
addUser: function(user_id) {
|
||||
if (this.props.selected_users.indexOf(user_id == -1)) {
|
||||
this.props.onChange(this.props.selected_users.concat([user_id]));
|
||||
|
@ -47,20 +51,20 @@ module.exports = createReactClass({
|
|||
},
|
||||
|
||||
onAddUserId: function() {
|
||||
this.addUser(this.refs.user_id_input.value);
|
||||
this.refs.user_id_input.value = "";
|
||||
this.addUser(this._user_id_input.current.value);
|
||||
this._user_id_input.current.value = "";
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const self = this;
|
||||
return (
|
||||
<div>
|
||||
<ul className="mx_UserSelector_UserIdList" ref="list">
|
||||
<ul className="mx_UserSelector_UserIdList">
|
||||
{ this.props.selected_users.map(function(user_id, i) {
|
||||
return <li key={user_id}>{ user_id } - <span onClick={function() {self.removeUser(user_id);}}>X</span></li>;
|
||||
}) }
|
||||
</ul>
|
||||
<input type="text" ref="user_id_input" defaultValue="" className="mx_UserSelector_userIdInput" placeholder={_t("ex. @bob:example.com")} />
|
||||
<input type="text" ref={this._user_id_input} defaultValue="" className="mx_UserSelector_userIdInput" placeholder={_t("ex. @bob:example.com")} />
|
||||
<button onClick={this.onAddUserId} className="mx_UserSelector_AddUserId">
|
||||
{ _t("Add User") }
|
||||
</button>
|
||||
|
|
|
@ -62,7 +62,7 @@ EMOJIBASE.forEach(emoji => {
|
|||
DATA_BY_CATEGORY[categoryId].push(emoji);
|
||||
}
|
||||
// This is used as the string to match the query against when filtering emojis.
|
||||
emoji.filterString = `${emoji.annotation}\n${emoji.shortcodes.join('\n')}}\n${emoji.emoticon || ''}`;
|
||||
emoji.filterString = `${emoji.annotation}\n${emoji.shortcodes.join('\n')}}\n${emoji.emoticon || ''}`.toLowerCase();
|
||||
});
|
||||
|
||||
export const CATEGORY_HEADER_HEIGHT = 22;
|
||||
|
@ -201,6 +201,7 @@ class EmojiPicker extends React.Component {
|
|||
}
|
||||
|
||||
onChangeFilter(filter) {
|
||||
filter = filter.toLowerCase(); // filter is case insensitive stored lower-case
|
||||
for (const cat of this.categories) {
|
||||
let emojis;
|
||||
// If the new filter string includes the old filter string, we don't have to re-filter the whole dataset.
|
||||
|
|
|
@ -23,7 +23,6 @@ class ReactionPicker extends React.Component {
|
|||
static propTypes = {
|
||||
mxEvent: PropTypes.object.isRequired,
|
||||
onFinished: PropTypes.func.isRequired,
|
||||
closeMenu: PropTypes.func.isRequired,
|
||||
reactions: PropTypes.object,
|
||||
};
|
||||
|
||||
|
@ -89,7 +88,6 @@ class ReactionPicker extends React.Component {
|
|||
|
||||
onChoose(reaction) {
|
||||
this.componentWillUnmount();
|
||||
this.props.closeMenu();
|
||||
this.props.onFinished();
|
||||
const myReactions = this.getReactions();
|
||||
if (myReactions.hasOwnProperty(reaction)) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
Copyright 2017, 2018 New Vector Ltd
|
||||
Copyright 2018 Michael Telatynski <7t3chguy@gmail.com>
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -21,11 +22,12 @@ import createReactClass from 'create-react-class';
|
|||
import { MatrixClient } from 'matrix-js-sdk';
|
||||
import sdk from '../../../index';
|
||||
import dis from '../../../dispatcher';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import {_t} from '../../../languageHandler';
|
||||
import classNames from 'classnames';
|
||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||
import {createMenu} from "../../structures/ContextualMenu";
|
||||
import {ContextMenu, ContextMenuButton, toRightOf} from "../../structures/ContextMenu";
|
||||
|
||||
// XXX this class copies a lot from RoomTile.js
|
||||
export default createReactClass({
|
||||
displayName: 'GroupInviteTile',
|
||||
|
||||
|
@ -69,54 +71,49 @@ export default createReactClass({
|
|||
});
|
||||
},
|
||||
|
||||
_showContextMenu: function(x, y, chevronOffset) {
|
||||
const GroupInviteTileContextMenu = sdk.getComponent('context_menus.GroupInviteTileContextMenu');
|
||||
|
||||
createMenu(GroupInviteTileContextMenu, {
|
||||
chevronOffset,
|
||||
left: x,
|
||||
top: y,
|
||||
group: this.props.group,
|
||||
onFinished: () => {
|
||||
this.setState({ menuDisplayed: false });
|
||||
},
|
||||
});
|
||||
this.setState({ menuDisplayed: true });
|
||||
},
|
||||
|
||||
onContextMenu: function(e) {
|
||||
// Prevent the RoomTile onClick event firing as well
|
||||
e.preventDefault();
|
||||
_showContextMenu: function(boundingClientRect) {
|
||||
// Only allow non-guests to access the context menu
|
||||
if (MatrixClientPeg.get().isGuest()) return;
|
||||
|
||||
const chevronOffset = 12;
|
||||
this._showContextMenu(e.clientX, e.clientY - (chevronOffset + 8), chevronOffset);
|
||||
},
|
||||
|
||||
onBadgeClicked: function(e) {
|
||||
// Prevent the RoomTile onClick event firing as well
|
||||
e.stopPropagation();
|
||||
// Only allow non-guests to access the context menu
|
||||
if (MatrixClientPeg.get().isGuest()) return;
|
||||
const state = {
|
||||
contextMenuPosition: boundingClientRect,
|
||||
};
|
||||
|
||||
// If the badge is clicked, then no longer show tooltip
|
||||
if (this.props.collapsed) {
|
||||
this.setState({ hover: false });
|
||||
state.hover = false;
|
||||
}
|
||||
|
||||
const elementRect = e.target.getBoundingClientRect();
|
||||
this.setState(state);
|
||||
},
|
||||
|
||||
// The window X and Y offsets are to adjust position when zoomed in to page
|
||||
const x = elementRect.right + window.pageXOffset + 3;
|
||||
const chevronOffset = 12;
|
||||
let y = (elementRect.top + (elementRect.height / 2) + window.pageYOffset);
|
||||
y = y - (chevronOffset + 8); // where 8 is half the height of the chevron
|
||||
onContextMenuButtonClick: function(e) {
|
||||
// Prevent the RoomTile onClick event firing as well
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
this._showContextMenu(x, y, chevronOffset);
|
||||
this._showContextMenu(e.target.getBoundingClientRect());
|
||||
},
|
||||
|
||||
onContextMenu: function(e) {
|
||||
// Prevent the native context menu
|
||||
e.preventDefault();
|
||||
|
||||
this._showContextMenu({
|
||||
right: e.clientX,
|
||||
top: e.clientY,
|
||||
height: 0,
|
||||
});
|
||||
},
|
||||
|
||||
closeMenu: function() {
|
||||
this.setState({
|
||||
contextMenuPosition: null,
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||
|
||||
const groupName = this.props.group.name || this.props.group.groupId;
|
||||
|
@ -125,21 +122,31 @@ export default createReactClass({
|
|||
|
||||
const av = <BaseAvatar name={groupName} width={24} height={24} url={httpAvatarUrl} />;
|
||||
|
||||
const isMenuDisplayed = Boolean(this.state.contextMenuPosition);
|
||||
const nameClasses = classNames('mx_RoomTile_name mx_RoomTile_invite mx_RoomTile_badgeShown', {
|
||||
'mx_RoomTile_badgeShown': this.state.badgeHover || this.state.menuDisplayed,
|
||||
'mx_RoomTile_badgeShown': this.state.badgeHover || isMenuDisplayed,
|
||||
});
|
||||
|
||||
const label = <div title={this.props.group.groupId} className={nameClasses} dir="auto">
|
||||
{ groupName }
|
||||
</div>;
|
||||
|
||||
const badgeEllipsis = this.state.badgeHover || this.state.menuDisplayed;
|
||||
const badgeEllipsis = this.state.badgeHover || isMenuDisplayed;
|
||||
const badgeClasses = classNames('mx_RoomTile_badge mx_RoomTile_highlight', {
|
||||
'mx_RoomTile_badgeButton': badgeEllipsis,
|
||||
});
|
||||
|
||||
const badgeContent = badgeEllipsis ? '\u00B7\u00B7\u00B7' : '!';
|
||||
const badge = <div className={badgeClasses} onClick={this.onBadgeClicked}>{ badgeContent }</div>;
|
||||
const badge = (
|
||||
<ContextMenuButton
|
||||
className={badgeClasses}
|
||||
onClick={this.onContextMenuButtonClick}
|
||||
label={_t("Options")}
|
||||
isExpanded={isMenuDisplayed}
|
||||
>
|
||||
{ badgeContent }
|
||||
</ContextMenuButton>
|
||||
);
|
||||
|
||||
let tooltip;
|
||||
if (this.props.collapsed && this.state.hover) {
|
||||
|
@ -148,17 +155,28 @@ export default createReactClass({
|
|||
}
|
||||
|
||||
const classes = classNames('mx_RoomTile mx_RoomTile_highlight', {
|
||||
'mx_RoomTile_menuDisplayed': this.state.menuDisplayed,
|
||||
'mx_RoomTile_menuDisplayed': isMenuDisplayed,
|
||||
'mx_RoomTile_selected': this.state.selected,
|
||||
'mx_GroupInviteTile': true,
|
||||
});
|
||||
|
||||
return (
|
||||
<AccessibleButton className={classes}
|
||||
onClick={this.onClick}
|
||||
onMouseEnter={this.onMouseEnter}
|
||||
onMouseLeave={this.onMouseLeave}
|
||||
onContextMenu={this.onContextMenu}
|
||||
let contextMenu;
|
||||
if (isMenuDisplayed) {
|
||||
const GroupInviteTileContextMenu = sdk.getComponent('context_menus.GroupInviteTileContextMenu');
|
||||
contextMenu = (
|
||||
<ContextMenu {...toRightOf(this.state.contextMenuPosition)} onFinished={this.closeMenu}>
|
||||
<GroupInviteTileContextMenu group={this.props.group} onFinished={this.closeMenu} />
|
||||
</ContextMenu>
|
||||
);
|
||||
}
|
||||
|
||||
return <React.Fragment>
|
||||
<AccessibleButton
|
||||
className={classes}
|
||||
onClick={this.onClick}
|
||||
onMouseEnter={this.onMouseEnter}
|
||||
onMouseLeave={this.onMouseLeave}
|
||||
onContextMenu={this.onContextMenu}
|
||||
>
|
||||
<div className="mx_RoomTile_avatar">
|
||||
{ av }
|
||||
|
@ -169,6 +187,8 @@ export default createReactClass({
|
|||
</div>
|
||||
{ tooltip }
|
||||
</AccessibleButton>
|
||||
);
|
||||
|
||||
{ contextMenu }
|
||||
</React.Fragment>;
|
||||
},
|
||||
});
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, {createRef} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import * as HtmlUtils from '../../../HtmlUtils';
|
||||
import { editBodyDiffToHtml } from '../../../utils/MessageDiffUtils';
|
||||
|
@ -51,6 +51,8 @@ export default class EditHistoryMessage extends React.PureComponent {
|
|||
}
|
||||
const canRedact = room.currentState.maySendRedactionForEvent(event, userId);
|
||||
this.state = {canRedact, sendStatus: event.getAssociatedStatus()};
|
||||
|
||||
this._content = createRef();
|
||||
}
|
||||
|
||||
_onAssociatedStatusChanged = () => {
|
||||
|
@ -78,8 +80,8 @@ export default class EditHistoryMessage extends React.PureComponent {
|
|||
|
||||
pillifyLinks() {
|
||||
// not present for redacted events
|
||||
if (this.refs.content) {
|
||||
pillifyLinks(this.refs.content.children, this.props.mxEvent);
|
||||
if (this._content.current) {
|
||||
pillifyLinks(this._content.current.children, this.props.mxEvent);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,9 +104,9 @@ export default class EditHistoryMessage extends React.PureComponent {
|
|||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
// hide the button when already redacted
|
||||
let redactButton;
|
||||
if (!this.props.mxEvent.isRedacted() && !this.props.isBaseEvent) {
|
||||
if (!this.props.mxEvent.isRedacted() && !this.props.isBaseEvent && this.state.canRedact) {
|
||||
redactButton = (
|
||||
<AccessibleButton onClick={this._onRedactClick} disabled={!this.state.canRedact}>
|
||||
<AccessibleButton onClick={this._onRedactClick}>
|
||||
{_t("Remove")}
|
||||
</AccessibleButton>
|
||||
);
|
||||
|
@ -140,13 +142,13 @@ export default class EditHistoryMessage extends React.PureComponent {
|
|||
if (mxEvent.getContent().msgtype === "m.emote") {
|
||||
const name = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender();
|
||||
contentContainer = (
|
||||
<div className="mx_EventTile_content" ref="content">*
|
||||
<div className="mx_EventTile_content" ref={this._content}>*
|
||||
<span className="mx_MEmoteBody_sender">{ name }</span>
|
||||
{contentElements}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
contentContainer = <div className="mx_EventTile_content" ref="content">{contentElements}</div>;
|
||||
contentContainer = <div className="mx_EventTile_content" ref={this._content}>{contentElements}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -80,7 +80,7 @@ export default class MAudioBody extends React.Component {
|
|||
|
||||
if (this.state.error !== null) {
|
||||
return (
|
||||
<span className="mx_MAudioBody" ref="body">
|
||||
<span className="mx_MAudioBody">
|
||||
<img src={require("../../../../res/img/warning.svg")} width="16" height="16" />
|
||||
{ _t("Error decrypting audio") }
|
||||
</span>
|
||||
|
|
|
@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, {createRef} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import createReactClass from 'create-react-class';
|
||||
import filesize from 'filesize';
|
||||
|
@ -251,6 +251,12 @@ module.exports = createReactClass({
|
|||
return MatrixClientPeg.get().mxcUrlToHttp(content.url);
|
||||
},
|
||||
|
||||
UNSAFE_componentWillMount: function() {
|
||||
this._iframe = createRef();
|
||||
this._dummyLink = createRef();
|
||||
this._downloadImage = createRef();
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
// Add this to the list of mounted components to receive notifications
|
||||
// when the tint changes.
|
||||
|
@ -272,17 +278,17 @@ module.exports = createReactClass({
|
|||
|
||||
tint: function() {
|
||||
// Update our tinted copy of require("../../../../res/img/download.svg")
|
||||
if (this.refs.downloadImage) {
|
||||
this.refs.downloadImage.src = tintedDownloadImageURL;
|
||||
if (this._downloadImage.current) {
|
||||
this._downloadImage.current.src = tintedDownloadImageURL;
|
||||
}
|
||||
if (this.refs.iframe) {
|
||||
if (this._iframe.current) {
|
||||
// If the attachment is encrypted then the download image
|
||||
// will be inside the iframe so we wont be able to update
|
||||
// it directly.
|
||||
this.refs.iframe.contentWindow.postMessage({
|
||||
this._iframe.current.contentWindow.postMessage({
|
||||
code: remoteSetTint.toString(),
|
||||
imgSrc: tintedDownloadImageURL,
|
||||
style: computedStyle(this.refs.dummyLink),
|
||||
style: computedStyle(this._dummyLink.current),
|
||||
}, "*");
|
||||
}
|
||||
},
|
||||
|
@ -325,7 +331,7 @@ module.exports = createReactClass({
|
|||
};
|
||||
|
||||
return (
|
||||
<span className="mx_MFileBody" ref="body">
|
||||
<span className="mx_MFileBody">
|
||||
<div className="mx_MFileBody_download">
|
||||
<a href="javascript:void(0)" onClick={decrypt}>
|
||||
{ _t("Decrypt %(text)s", { text: text }) }
|
||||
|
@ -340,7 +346,7 @@ module.exports = createReactClass({
|
|||
ev.target.contentWindow.postMessage({
|
||||
code: remoteRender.toString(),
|
||||
imgSrc: tintedDownloadImageURL,
|
||||
style: computedStyle(this.refs.dummyLink),
|
||||
style: computedStyle(this._dummyLink.current),
|
||||
blob: this.state.decryptedBlob,
|
||||
// Set a download attribute for encrypted files so that the file
|
||||
// will have the correct name when the user tries to download it.
|
||||
|
@ -367,9 +373,9 @@ module.exports = createReactClass({
|
|||
* We'll use it to learn how the download link
|
||||
* would have been styled if it was rendered inline.
|
||||
*/ }
|
||||
<a ref="dummyLink" />
|
||||
<a ref={this._dummyLink} />
|
||||
</div>
|
||||
<iframe src={renderer_url} onLoad={onIframeLoad} ref="iframe" />
|
||||
<iframe src={renderer_url} onLoad={onIframeLoad} ref={this._iframe} />
|
||||
</div>
|
||||
</span>
|
||||
);
|
||||
|
@ -439,7 +445,7 @@ module.exports = createReactClass({
|
|||
<span className="mx_MFileBody">
|
||||
<div className="mx_MFileBody_download">
|
||||
<a {...downloadProps}>
|
||||
<img src={tintedDownloadImageURL} width="12" height="14" ref="downloadImage" />
|
||||
<img src={tintedDownloadImageURL} width="12" height="14" ref={this._downloadImage} />
|
||||
{ _t("Download %(text)s", { text: text }) }
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -16,7 +16,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, {createRef} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { MatrixClient } from 'matrix-js-sdk';
|
||||
|
||||
|
@ -65,6 +65,8 @@ export default class MImageBody extends React.Component {
|
|||
hover: false,
|
||||
showImage: SettingsStore.getValue("showImages"),
|
||||
};
|
||||
|
||||
this._image = createRef();
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
|
@ -158,8 +160,8 @@ export default class MImageBody extends React.Component {
|
|||
|
||||
let loadedImageDimensions;
|
||||
|
||||
if (this.refs.image) {
|
||||
const { naturalWidth, naturalHeight } = this.refs.image;
|
||||
if (this._image.current) {
|
||||
const { naturalWidth, naturalHeight } = this._image.current;
|
||||
// this is only used as a fallback in case content.info.w/h is missing
|
||||
loadedImageDimensions = { naturalWidth, naturalHeight };
|
||||
}
|
||||
|
@ -342,7 +344,7 @@ export default class MImageBody extends React.Component {
|
|||
imageElement = <HiddenImagePlaceholder />;
|
||||
} else {
|
||||
imageElement = (
|
||||
<img style={{display: 'none'}} src={thumbUrl} ref="image"
|
||||
<img style={{display: 'none'}} src={thumbUrl} ref={this._image}
|
||||
alt={content.body}
|
||||
onError={this.onImageError}
|
||||
onLoad={this.onImageLoad}
|
||||
|
@ -385,7 +387,7 @@ export default class MImageBody extends React.Component {
|
|||
// which has the same width as the timeline
|
||||
// mx_MImageBody_thumbnail resizes img to exactly container size
|
||||
img = (
|
||||
<img className="mx_MImageBody_thumbnail" src={thumbUrl} ref="image"
|
||||
<img className="mx_MImageBody_thumbnail" src={thumbUrl} ref={this._image}
|
||||
style={{ maxWidth: maxWidth + "px" }}
|
||||
alt={content.body}
|
||||
onError={this.onImageError}
|
||||
|
@ -459,7 +461,7 @@ export default class MImageBody extends React.Component {
|
|||
|
||||
if (this.state.error !== null) {
|
||||
return (
|
||||
<span className="mx_MImageBody" ref="body">
|
||||
<span className="mx_MImageBody">
|
||||
<img src={require("../../../../res/img/warning.svg")} width="16" height="16" />
|
||||
{ _t("Error decrypting image") }
|
||||
</span>
|
||||
|
@ -477,7 +479,7 @@ export default class MImageBody extends React.Component {
|
|||
const thumbnail = this._messageContent(contentUrl, thumbUrl, content);
|
||||
const fileBody = this.getFileBody();
|
||||
|
||||
return <span className="mx_MImageBody" ref="body">
|
||||
return <span className="mx_MImageBody">
|
||||
{ thumbnail }
|
||||
{ fileBody }
|
||||
</span>;
|
||||
|
|
|
@ -132,7 +132,7 @@ module.exports = createReactClass({
|
|||
|
||||
if (this.state.error !== null) {
|
||||
return (
|
||||
<span className="mx_MVideoBody" ref="body">
|
||||
<span className="mx_MVideoBody">
|
||||
<img src={require("../../../../res/img/warning.svg")} width="16" height="16" />
|
||||
{ _t("Error decrypting video") }
|
||||
</span>
|
||||
|
@ -144,8 +144,8 @@ module.exports = createReactClass({
|
|||
// The attachment is decrypted in componentDidMount.
|
||||
// For now add an img tag with a spinner.
|
||||
return (
|
||||
<span className="mx_MVideoBody" ref="body">
|
||||
<div className="mx_MImageBody_thumbnail mx_MImageBody_thumbnail_spinner" ref="image">
|
||||
<span className="mx_MVideoBody">
|
||||
<div className="mx_MImageBody_thumbnail mx_MImageBody_thumbnail_spinner">
|
||||
<img src={require("../../../../res/img/spinner.gif")} alt={content.body} width="16" height="16" />
|
||||
</div>
|
||||
</span>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
Copyright 2019 New Vector Ltd
|
||||
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -15,17 +16,96 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, {useEffect} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import sdk from '../../../index';
|
||||
import dis from '../../../dispatcher';
|
||||
import Modal from '../../../Modal';
|
||||
import { createMenu } from '../../structures/ContextualMenu';
|
||||
import {aboveLeftOf, ContextMenu, ContextMenuButton, useContextMenu} from '../../structures/ContextMenu';
|
||||
import { isContentActionable, canEditContent } from '../../../utils/EventUtils';
|
||||
import {RoomContext} from "../../structures/RoomView";
|
||||
|
||||
const OptionsButton = ({mxEvent, getTile, getReplyThread, permalinkCreator, onFocusChange}) => {
|
||||
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
|
||||
useEffect(() => {
|
||||
onFocusChange(menuDisplayed);
|
||||
}, [onFocusChange, menuDisplayed]);
|
||||
|
||||
let contextMenu;
|
||||
if (menuDisplayed) {
|
||||
const MessageContextMenu = sdk.getComponent('context_menus.MessageContextMenu');
|
||||
|
||||
const tile = getTile && getTile();
|
||||
const replyThread = getReplyThread && getReplyThread();
|
||||
|
||||
const onCryptoClick = () => {
|
||||
Modal.createTrackedDialogAsync('Encrypted Event Dialog', '',
|
||||
import('../../../async-components/views/dialogs/EncryptedEventDialog'),
|
||||
{event: mxEvent},
|
||||
);
|
||||
};
|
||||
|
||||
let e2eInfoCallback = null;
|
||||
if (mxEvent.isEncrypted()) {
|
||||
e2eInfoCallback = onCryptoClick;
|
||||
}
|
||||
|
||||
const buttonRect = button.current.getBoundingClientRect();
|
||||
contextMenu = <ContextMenu {...aboveLeftOf(buttonRect)} onFinished={closeMenu}>
|
||||
<MessageContextMenu
|
||||
mxEvent={mxEvent}
|
||||
permalinkCreator={permalinkCreator}
|
||||
eventTileOps={tile && tile.getEventTileOps ? tile.getEventTileOps() : undefined}
|
||||
collapseReplyThread={replyThread && replyThread.canCollapse() ? replyThread.collapse : undefined}
|
||||
e2eInfoCallback={e2eInfoCallback}
|
||||
onFinished={closeMenu}
|
||||
/>
|
||||
</ContextMenu>;
|
||||
}
|
||||
|
||||
return <React.Fragment>
|
||||
<ContextMenuButton
|
||||
className="mx_MessageActionBar_maskButton mx_MessageActionBar_optionsButton"
|
||||
label={_t("Options")}
|
||||
onClick={openMenu}
|
||||
isExpanded={menuDisplayed}
|
||||
inputRef={button}
|
||||
/>
|
||||
|
||||
{ contextMenu }
|
||||
</React.Fragment>;
|
||||
};
|
||||
|
||||
const ReactButton = ({mxEvent, reactions, onFocusChange}) => {
|
||||
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
|
||||
useEffect(() => {
|
||||
onFocusChange(menuDisplayed);
|
||||
}, [onFocusChange, menuDisplayed]);
|
||||
|
||||
let contextMenu;
|
||||
if (menuDisplayed) {
|
||||
const buttonRect = button.current.getBoundingClientRect();
|
||||
const ReactionPicker = sdk.getComponent('emojipicker.ReactionPicker');
|
||||
contextMenu = <ContextMenu {...aboveLeftOf(buttonRect)} onFinished={closeMenu} catchTab={false}>
|
||||
<ReactionPicker mxEvent={mxEvent} reactions={reactions} onFinished={closeMenu} />
|
||||
</ContextMenu>;
|
||||
}
|
||||
|
||||
return <React.Fragment>
|
||||
<ContextMenuButton
|
||||
className="mx_MessageActionBar_maskButton mx_MessageActionBar_reactButton"
|
||||
label={_t("React")}
|
||||
onClick={openMenu}
|
||||
isExpanded={menuDisplayed}
|
||||
inputRef={button}
|
||||
/>
|
||||
|
||||
{ contextMenu }
|
||||
</React.Fragment>;
|
||||
};
|
||||
|
||||
export default class MessageActionBar extends React.PureComponent {
|
||||
static propTypes = {
|
||||
mxEvent: PropTypes.object.isRequired,
|
||||
|
@ -62,14 +142,6 @@ export default class MessageActionBar extends React.PureComponent {
|
|||
this.props.onFocusChange(focused);
|
||||
};
|
||||
|
||||
onCryptoClick = () => {
|
||||
const event = this.props.mxEvent;
|
||||
Modal.createTrackedDialogAsync('Encrypted Event Dialog', '',
|
||||
import('../../../async-components/views/dialogs/EncryptedEventDialog'),
|
||||
{event},
|
||||
);
|
||||
};
|
||||
|
||||
onReplyClick = (ev) => {
|
||||
dis.dispatch({
|
||||
action: 'reply_to_event',
|
||||
|
@ -84,71 +156,6 @@ export default class MessageActionBar extends React.PureComponent {
|
|||
});
|
||||
};
|
||||
|
||||
getMenuOptions = (ev) => {
|
||||
const menuOptions = {};
|
||||
const buttonRect = ev.target.getBoundingClientRect();
|
||||
// The window X and Y offsets are to adjust position when zoomed in to page
|
||||
const buttonRight = buttonRect.right + window.pageXOffset;
|
||||
const buttonBottom = buttonRect.bottom + window.pageYOffset;
|
||||
const buttonTop = buttonRect.top + window.pageYOffset;
|
||||
// Align the right edge of the menu to the right edge of the button
|
||||
menuOptions.right = window.innerWidth - buttonRight;
|
||||
// Align the menu vertically on whichever side of the button has more
|
||||
// space available.
|
||||
if (buttonBottom < window.innerHeight / 2) {
|
||||
menuOptions.top = buttonBottom;
|
||||
} else {
|
||||
menuOptions.bottom = window.innerHeight - buttonTop;
|
||||
}
|
||||
return menuOptions;
|
||||
};
|
||||
|
||||
onReactClick = (ev) => {
|
||||
const ReactionPicker = sdk.getComponent('emojipicker.ReactionPicker');
|
||||
|
||||
const menuOptions = {
|
||||
...this.getMenuOptions(ev),
|
||||
mxEvent: this.props.mxEvent,
|
||||
reactions: this.props.reactions,
|
||||
chevronFace: "none",
|
||||
onFinished: () => this.onFocusChange(false),
|
||||
};
|
||||
|
||||
createMenu(ReactionPicker, menuOptions);
|
||||
|
||||
this.onFocusChange(true);
|
||||
};
|
||||
|
||||
onOptionsClick = (ev) => {
|
||||
const MessageContextMenu = sdk.getComponent('context_menus.MessageContextMenu');
|
||||
|
||||
const { getTile, getReplyThread } = this.props;
|
||||
const tile = getTile && getTile();
|
||||
const replyThread = getReplyThread && getReplyThread();
|
||||
|
||||
let e2eInfoCallback = null;
|
||||
if (this.props.mxEvent.isEncrypted()) {
|
||||
e2eInfoCallback = () => this.onCryptoClick();
|
||||
}
|
||||
|
||||
const menuOptions = {
|
||||
...this.getMenuOptions(ev),
|
||||
mxEvent: this.props.mxEvent,
|
||||
chevronFace: "none",
|
||||
permalinkCreator: this.props.permalinkCreator,
|
||||
eventTileOps: tile && tile.getEventTileOps ? tile.getEventTileOps() : undefined,
|
||||
collapseReplyThread: replyThread && replyThread.canCollapse() ? replyThread.collapse : undefined,
|
||||
e2eInfoCallback: e2eInfoCallback,
|
||||
onFinished: () => {
|
||||
this.onFocusChange(false);
|
||||
},
|
||||
};
|
||||
|
||||
createMenu(MessageContextMenu, menuOptions);
|
||||
|
||||
this.onFocusChange(true);
|
||||
};
|
||||
|
||||
render() {
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
|
||||
|
@ -158,11 +165,9 @@ export default class MessageActionBar extends React.PureComponent {
|
|||
|
||||
if (isContentActionable(this.props.mxEvent)) {
|
||||
if (this.context.room.canReact) {
|
||||
reactButton = <AccessibleButton
|
||||
className="mx_MessageActionBar_maskButton mx_MessageActionBar_reactButton"
|
||||
title={_t("React")}
|
||||
onClick={this.onReactClick}
|
||||
/>;
|
||||
reactButton = (
|
||||
<ReactButton mxEvent={this.props.mxEvent} reactions={this.props.reactions} onFocusChange={this.onFocusChange} />
|
||||
);
|
||||
}
|
||||
if (this.context.room.canReply) {
|
||||
replyButton = <AccessibleButton
|
||||
|
@ -185,11 +190,12 @@ export default class MessageActionBar extends React.PureComponent {
|
|||
{reactButton}
|
||||
{replyButton}
|
||||
{editButton}
|
||||
<AccessibleButton
|
||||
className="mx_MessageActionBar_maskButton mx_MessageActionBar_optionsButton"
|
||||
title={_t("Options")}
|
||||
onClick={this.onOptionsClick}
|
||||
aria-haspopup={true}
|
||||
<OptionsButton
|
||||
mxEvent={this.props.mxEvent}
|
||||
getReplyThread={this.props.getReplyThread}
|
||||
getTile={this.props.getTile}
|
||||
permalinkCreator={this.props.permalinkCreator}
|
||||
onFocusChange={this.onFocusChange}
|
||||
/>
|
||||
</div>;
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, {createRef} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import createReactClass from 'create-react-class';
|
||||
import sdk from '../../../index';
|
||||
|
@ -47,8 +47,12 @@ module.exports = createReactClass({
|
|||
maxImageHeight: PropTypes.number,
|
||||
},
|
||||
|
||||
UNSAFE_componentWillMount: function() {
|
||||
this._body = createRef();
|
||||
},
|
||||
|
||||
getEventTileOps: function() {
|
||||
return this.refs.body && this.refs.body.getEventTileOps ? this.refs.body.getEventTileOps() : null;
|
||||
return this._body.current && this._body.current.getEventTileOps ? this._body.current.getEventTileOps() : null;
|
||||
},
|
||||
|
||||
onTileUpdate: function() {
|
||||
|
@ -103,7 +107,8 @@ module.exports = createReactClass({
|
|||
}
|
||||
|
||||
return <BodyType
|
||||
ref="body" mxEvent={this.props.mxEvent}
|
||||
ref={this._body}
|
||||
mxEvent={this.props.mxEvent}
|
||||
highlights={this.props.highlights}
|
||||
highlightLink={this.props.highlightLink}
|
||||
showUrlPreview={this.props.showUrlPreview}
|
||||
|
|
|
@ -149,7 +149,11 @@ export default class ReactionsRow extends React.PureComponent {
|
|||
</a>;
|
||||
}
|
||||
|
||||
return <div className="mx_ReactionsRow">
|
||||
return <div
|
||||
className="mx_ReactionsRow"
|
||||
role="toolbar"
|
||||
aria-label={_t("Reactions")}
|
||||
>
|
||||
{items}
|
||||
{showAllButton}
|
||||
</div>;
|
||||
|
|
|
@ -20,6 +20,8 @@ import classNames from 'classnames';
|
|||
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { formatCommaSeparatedList } from '../../../utils/FormattingUtils';
|
||||
|
||||
export default class ReactionsRowButton extends React.PureComponent {
|
||||
static propTypes = {
|
||||
|
@ -79,7 +81,7 @@ export default class ReactionsRowButton extends React.PureComponent {
|
|||
render() {
|
||||
const ReactionsRowButtonTooltip =
|
||||
sdk.getComponent('messages.ReactionsRowButtonTooltip');
|
||||
const { content, count, reactionEvents, myReactionEvent } = this.props;
|
||||
const { mxEvent, content, count, reactionEvents, myReactionEvent } = this.props;
|
||||
|
||||
const classes = classNames({
|
||||
mx_ReactionsRowButton: true,
|
||||
|
@ -96,18 +98,48 @@ export default class ReactionsRowButton extends React.PureComponent {
|
|||
/>;
|
||||
}
|
||||
|
||||
return <span className={classes}
|
||||
const room = MatrixClientPeg.get().getRoom(mxEvent.getRoomId());
|
||||
let label;
|
||||
if (room) {
|
||||
const senders = [];
|
||||
for (const reactionEvent of reactionEvents) {
|
||||
const member = room.getMember(reactionEvent.getSender());
|
||||
const name = member ? member.name : reactionEvent.getSender();
|
||||
senders.push(name);
|
||||
}
|
||||
label = _t(
|
||||
"<reactors/><reactedWith> reacted with %(content)s</reactedWith>",
|
||||
{
|
||||
content,
|
||||
},
|
||||
{
|
||||
reactors: () => {
|
||||
return formatCommaSeparatedList(senders, 6);
|
||||
},
|
||||
reactedWith: (sub) => {
|
||||
if (!content) {
|
||||
return null;
|
||||
}
|
||||
return sub;
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
return <AccessibleButton className={classes}
|
||||
aria-label={label}
|
||||
onClick={this.onClick}
|
||||
onMouseOver={this.onMouseOver}
|
||||
onMouseOut={this.onMouseOut}
|
||||
>
|
||||
<span className="mx_ReactionsRowButton_content">
|
||||
<span className="mx_ReactionsRowButton_content" aria-hidden="true">
|
||||
{content}
|
||||
</span>
|
||||
<span className="mx_ReactionsRowButton_count">
|
||||
<span className="mx_ReactionsRowButton_count" aria-hidden="true">
|
||||
{count}
|
||||
</span>
|
||||
{tooltip}
|
||||
</span>;
|
||||
</AccessibleButton>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, {createRef} from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import createReactClass from 'create-react-class';
|
||||
|
@ -27,12 +27,13 @@ import sdk from '../../../index';
|
|||
import Modal from '../../../Modal';
|
||||
import dis from '../../../dispatcher';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import * as ContextualMenu from '../../structures/ContextualMenu';
|
||||
import * as ContextMenu from '../../structures/ContextMenu';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import ReplyThread from "../elements/ReplyThread";
|
||||
import {pillifyLinks} from '../../../utils/pillify';
|
||||
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
|
||||
import {isPermalinkHost} from "../../../utils/permalinks/Permalinks";
|
||||
import {toRightOf} from "../../structures/ContextMenu";
|
||||
|
||||
module.exports = createReactClass({
|
||||
displayName: 'TextualBody',
|
||||
|
@ -85,6 +86,10 @@ module.exports = createReactClass({
|
|||
return successful;
|
||||
},
|
||||
|
||||
UNSAFE_componentWillMount: function() {
|
||||
this._content = createRef();
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this._unmounted = false;
|
||||
if (!this.props.editState) {
|
||||
|
@ -93,13 +98,13 @@ module.exports = createReactClass({
|
|||
},
|
||||
|
||||
_applyFormatting() {
|
||||
this.activateSpoilers(this.refs.content.children);
|
||||
this.activateSpoilers(this._content.current.children);
|
||||
|
||||
// pillifyLinks BEFORE linkifyElement because plain room/user URLs in the composer
|
||||
// are still sent as plaintext URLs. If these are ever pillified in the composer,
|
||||
// we should be pillify them here by doing the linkifying BEFORE the pillifying.
|
||||
pillifyLinks(this.refs.content.children, this.props.mxEvent);
|
||||
HtmlUtils.linkifyElement(this.refs.content);
|
||||
pillifyLinks(this._content.current.children, this.props.mxEvent);
|
||||
HtmlUtils.linkifyElement(this._content.current);
|
||||
this.calculateUrlPreview();
|
||||
|
||||
if (this.props.mxEvent.getContent().format === "org.matrix.custom.html") {
|
||||
|
@ -162,7 +167,7 @@ module.exports = createReactClass({
|
|||
//console.info("calculateUrlPreview: ShowUrlPreview for %s is %s", this.props.mxEvent.getId(), this.props.showUrlPreview);
|
||||
|
||||
if (this.props.showUrlPreview) {
|
||||
let links = this.findLinks(this.refs.content.children);
|
||||
let links = this.findLinks(this._content.current.children);
|
||||
if (links.length) {
|
||||
// de-dup the links (but preserve ordering)
|
||||
const seen = new Set();
|
||||
|
@ -272,18 +277,12 @@ module.exports = createReactClass({
|
|||
const copyCode = button.parentNode.getElementsByTagName("code")[0];
|
||||
const successful = this.copyToClipboard(copyCode.textContent);
|
||||
|
||||
const GenericTextContextMenu = sdk.getComponent('context_menus.GenericTextContextMenu');
|
||||
const buttonRect = e.target.getBoundingClientRect();
|
||||
|
||||
// The window X and Y offsets are to adjust position when zoomed in to page
|
||||
const x = buttonRect.right + window.pageXOffset;
|
||||
const y = (buttonRect.top + (buttonRect.height / 2) + window.pageYOffset) - 19;
|
||||
const {close} = ContextualMenu.createMenu(GenericTextContextMenu, {
|
||||
chevronOffset: 10,
|
||||
left: x,
|
||||
top: y,
|
||||
const GenericTextContextMenu = sdk.getComponent('context_menus.GenericTextContextMenu');
|
||||
const {close} = ContextMenu.createMenu(GenericTextContextMenu, {
|
||||
...toRightOf(buttonRect, 2),
|
||||
message: successful ? _t('Copied!') : _t('Failed to copy'),
|
||||
}, false);
|
||||
});
|
||||
e.target.onmouseleave = close;
|
||||
};
|
||||
|
||||
|
@ -332,7 +331,7 @@ module.exports = createReactClass({
|
|||
},
|
||||
|
||||
getInnerText: () => {
|
||||
return this.refs.content.innerText;
|
||||
return this._content.current.innerText;
|
||||
},
|
||||
};
|
||||
},
|
||||
|
@ -406,13 +405,18 @@ module.exports = createReactClass({
|
|||
label={_t("Edited at %(date)s. Click to view edits.", {date: dateString})}
|
||||
/>;
|
||||
}
|
||||
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
return (
|
||||
<div
|
||||
key="editedMarker" className="mx_EventTile_edited"
|
||||
<AccessibleButton
|
||||
key="editedMarker"
|
||||
className="mx_EventTile_edited"
|
||||
onClick={this._openHistoryDialog}
|
||||
onMouseEnter={this._onMouseEnterEditedMarker}
|
||||
onMouseLeave={this._onMouseLeaveEditedMarker}
|
||||
>{editedTooltip}<span>{`(${_t("edited")})`}</span></div>
|
||||
>
|
||||
{ editedTooltip }<span>{`(${_t("edited")})`}</span>
|
||||
</AccessibleButton>
|
||||
);
|
||||
},
|
||||
|
||||
|
@ -457,7 +461,7 @@ module.exports = createReactClass({
|
|||
case "m.emote":
|
||||
const name = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender();
|
||||
return (
|
||||
<span ref="content" className="mx_MEmoteBody mx_EventTile_content">
|
||||
<span ref={this._content} className="mx_MEmoteBody mx_EventTile_content">
|
||||
*
|
||||
<span
|
||||
className="mx_MEmoteBody_sender"
|
||||
|
@ -472,14 +476,14 @@ module.exports = createReactClass({
|
|||
);
|
||||
case "m.notice":
|
||||
return (
|
||||
<span ref="content" className="mx_MNoticeBody mx_EventTile_content">
|
||||
<span ref={this._content} className="mx_MNoticeBody mx_EventTile_content">
|
||||
{ body }
|
||||
{ widgets }
|
||||
</span>
|
||||
);
|
||||
default: // including "m.text"
|
||||
return (
|
||||
<span ref="content" className="mx_MTextBody mx_EventTile_content">
|
||||
<span ref={this._content} className="mx_MTextBody mx_EventTile_content">
|
||||
{ body }
|
||||
{ widgets }
|
||||
</span>
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, {createRef} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {_t} from "../../../languageHandler";
|
||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||
|
@ -58,13 +58,15 @@ export default class RoomProfileSettings extends React.Component {
|
|||
canSetTopic: room.currentState.maySendStateEvent('m.room.topic', client.getUserId()),
|
||||
canSetAvatar: room.currentState.maySendStateEvent('m.room.avatar', client.getUserId()),
|
||||
};
|
||||
|
||||
this._avatarUpload = createRef();
|
||||
}
|
||||
|
||||
_uploadAvatar = (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
this.refs.avatarUpload.click();
|
||||
this._avatarUpload.current.click();
|
||||
};
|
||||
|
||||
_saveProfile = async (e) => {
|
||||
|
@ -178,7 +180,7 @@ export default class RoomProfileSettings extends React.Component {
|
|||
|
||||
return (
|
||||
<form onSubmit={this._saveProfile} autoComplete="off" noValidate={true}>
|
||||
<input type="file" ref="avatarUpload" className="mx_ProfileSettings_avatarUpload"
|
||||
<input type="file" ref={this._avatarUpload} className="mx_ProfileSettings_avatarUpload"
|
||||
onChange={this._onAvatarChanged} accept="image/*" />
|
||||
<div className="mx_ProfileSettings_profile">
|
||||
<div className="mx_ProfileSettings_controls">
|
||||
|
|
|
@ -188,14 +188,15 @@ module.exports = createReactClass({
|
|||
}
|
||||
|
||||
const callView = (
|
||||
<CallView ref="callView" room={this.props.room}
|
||||
<CallView
|
||||
room={this.props.room}
|
||||
ConferenceHandler={this.props.conferenceHandler}
|
||||
onResize={this.props.onResize}
|
||||
maxVideoHeight={this.props.maxHeight}
|
||||
/>
|
||||
);
|
||||
|
||||
const appsDrawer = <AppsDrawer ref="appsDrawer"
|
||||
const appsDrawer = <AppsDrawer
|
||||
room={this.props.room}
|
||||
userId={this.props.userId}
|
||||
maxHeight={this.props.maxHeight}
|
||||
|
|
|
@ -19,7 +19,7 @@ limitations under the License.
|
|||
|
||||
import ReplyThread from "../elements/ReplyThread";
|
||||
|
||||
import React from 'react';
|
||||
import React, {createRef} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import createReactClass from 'create-react-class';
|
||||
const classNames = require("classnames");
|
||||
|
@ -224,6 +224,9 @@ module.exports = createReactClass({
|
|||
// don't do RR animations until we are mounted
|
||||
this._suppressReadReceiptAnimation = true;
|
||||
this._verifyEvent(this.props.mxEvent);
|
||||
|
||||
this._tile = createRef();
|
||||
this._replyThread = createRef();
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
|
@ -494,6 +497,9 @@ module.exports = createReactClass({
|
|||
if (ev.status === EventStatus.NOT_SENT) {
|
||||
return;
|
||||
}
|
||||
if (ev.isState()) {
|
||||
return; // we expect this to be unencrypted
|
||||
}
|
||||
// if the event is not encrypted, but it's an e2e room, show the open padlock
|
||||
return <E2ePadlockUnencrypted {...props} />;
|
||||
}
|
||||
|
@ -509,11 +515,11 @@ module.exports = createReactClass({
|
|||
},
|
||||
|
||||
getTile() {
|
||||
return this.refs.tile;
|
||||
return this._tile.current;
|
||||
},
|
||||
|
||||
getReplyThread() {
|
||||
return this.refs.replyThread;
|
||||
return this._replyThread.current;
|
||||
},
|
||||
|
||||
getReactions() {
|
||||
|
@ -745,7 +751,7 @@ module.exports = createReactClass({
|
|||
</a>
|
||||
</div>
|
||||
<div className="mx_EventTile_line">
|
||||
<EventTileType ref="tile"
|
||||
<EventTileType ref={this._tile}
|
||||
mxEvent={this.props.mxEvent}
|
||||
highlights={this.props.highlights}
|
||||
highlightLink={this.props.highlightLink}
|
||||
|
@ -759,7 +765,7 @@ module.exports = createReactClass({
|
|||
return (
|
||||
<div className={classes}>
|
||||
<div className="mx_EventTile_line">
|
||||
<EventTileType ref="tile"
|
||||
<EventTileType ref={this._tile}
|
||||
mxEvent={this.props.mxEvent}
|
||||
highlights={this.props.highlights}
|
||||
highlightLink={this.props.highlightLink}
|
||||
|
@ -789,7 +795,7 @@ module.exports = createReactClass({
|
|||
this.props.mxEvent,
|
||||
this.props.onHeightChanged,
|
||||
this.props.permalinkCreator,
|
||||
'replyThread',
|
||||
this._replyThread,
|
||||
);
|
||||
}
|
||||
return (
|
||||
|
@ -802,7 +808,7 @@ module.exports = createReactClass({
|
|||
</a>
|
||||
{ !isBubbleMessage && this._renderE2EPadlock() }
|
||||
{ thread }
|
||||
<EventTileType ref="tile"
|
||||
<EventTileType ref={this._tile}
|
||||
mxEvent={this.props.mxEvent}
|
||||
highlights={this.props.highlights}
|
||||
highlightLink={this.props.highlightLink}
|
||||
|
@ -817,7 +823,7 @@ module.exports = createReactClass({
|
|||
this.props.mxEvent,
|
||||
this.props.onHeightChanged,
|
||||
this.props.permalinkCreator,
|
||||
'replyThread',
|
||||
this._replyThread,
|
||||
);
|
||||
// tab-index=-1 to allow it to be focusable but do not add tab stop for it, primarily for screen readers
|
||||
return (
|
||||
|
@ -836,7 +842,7 @@ module.exports = createReactClass({
|
|||
</a>
|
||||
{ !isBubbleMessage && this._renderE2EPadlock() }
|
||||
{ thread }
|
||||
<EventTileType ref="tile"
|
||||
<EventTileType ref={this._tile}
|
||||
mxEvent={this.props.mxEvent}
|
||||
replacingEventId={this.props.replacingEventId}
|
||||
editState={this.props.editState}
|
||||
|
@ -887,7 +893,7 @@ module.exports.haveTileForEvent = function(e) {
|
|||
|
||||
function E2ePadlockUndecryptable(props) {
|
||||
return (
|
||||
<E2ePadlock title={_t("Undecryptable")} icon="undecryptable" {...props} />
|
||||
<E2ePadlock title={_t("This message cannot be decrypted")} icon="undecryptable" {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -899,19 +905,57 @@ function E2ePadlockUnverified(props) {
|
|||
|
||||
function E2ePadlockUnencrypted(props) {
|
||||
return (
|
||||
<E2ePadlock title={_t("Unencrypted message")} icon="unencrypted" {...props} />
|
||||
<E2ePadlock title={_t("Unencrypted")} icon="unencrypted" {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
function E2ePadlock(props) {
|
||||
if (SettingsStore.getValue("alwaysShowEncryptionIcons")) {
|
||||
return (<div
|
||||
className={`mx_EventTile_e2eIcon mx_EventTile_e2eIcon_${props.icon}`}
|
||||
title={props.title} onClick={props.onClick} />);
|
||||
} else {
|
||||
return (<div
|
||||
className={`mx_EventTile_e2eIcon mx_EventTile_e2eIcon_hidden mx_EventTile_e2eIcon_${props.icon}`}
|
||||
onClick={props.onClick} />);
|
||||
class E2ePadlock extends React.Component {
|
||||
static propTypes = {
|
||||
icon: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
hover: false,
|
||||
};
|
||||
}
|
||||
|
||||
onClick = (e) => {
|
||||
if (this.props.onClick) this.props.onClick(e);
|
||||
};
|
||||
|
||||
onHoverStart = () => {
|
||||
this.setState({hover: true});
|
||||
};
|
||||
|
||||
onHoverEnd = () => {
|
||||
this.setState({hover: false});
|
||||
};
|
||||
|
||||
render() {
|
||||
let tooltip = null;
|
||||
if (this.state.hover) {
|
||||
const Tooltip = sdk.getComponent("elements.Tooltip");
|
||||
tooltip = <Tooltip className="mx_EventTile_e2eIcon_tooltip" label={this.props.title} dir="auto" />;
|
||||
}
|
||||
|
||||
let classes = `mx_EventTile_e2eIcon mx_EventTile_e2eIcon_${this.props.icon}`;
|
||||
if (!SettingsStore.getValue("alwaysShowEncryptionIcons")) {
|
||||
classes += ' mx_EventTile_e2eIcon_hidden';
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes}
|
||||
onClick={this.onClick}
|
||||
onMouseEnter={this.onHoverStart}
|
||||
onMouseLeave={this.onHoverEnd}
|
||||
>{tooltip}</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, {createRef} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import createReactClass from 'create-react-class';
|
||||
import { linkifyElement } from '../../../HtmlUtils';
|
||||
|
@ -54,17 +54,19 @@ module.exports = createReactClass({
|
|||
}, (error)=>{
|
||||
console.error("Failed to get URL preview: " + error);
|
||||
});
|
||||
|
||||
this._description = createRef();
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
if (this.refs.description) {
|
||||
linkifyElement(this.refs.description);
|
||||
if (this._description.current) {
|
||||
linkifyElement(this._description.current);
|
||||
}
|
||||
},
|
||||
|
||||
componentDidUpdate: function() {
|
||||
if (this.refs.description) {
|
||||
linkifyElement(this.refs.description);
|
||||
if (this._description.current) {
|
||||
linkifyElement(this._description.current);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -129,7 +131,7 @@ module.exports = createReactClass({
|
|||
<div className="mx_LinkPreviewWidget_caption">
|
||||
<div className="mx_LinkPreviewWidget_title"><a href={this.props.link} target="_blank" rel="noopener">{ p["og:title"] }</a></div>
|
||||
<div className="mx_LinkPreviewWidget_siteName">{ p["og:site_name"] ? (" - " + p["og:site_name"]) : null }</div>
|
||||
<div className="mx_LinkPreviewWidget_description" ref="description">
|
||||
<div className="mx_LinkPreviewWidget_description" ref={this._description}>
|
||||
{ p["og:description"] }
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -14,7 +14,7 @@ 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 React, {createRef} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import CallHandler from '../../../CallHandler';
|
||||
|
@ -111,6 +111,8 @@ class UploadButton extends React.Component {
|
|||
super(props, context);
|
||||
this.onUploadClick = this.onUploadClick.bind(this);
|
||||
this.onUploadFileInputChange = this.onUploadFileInputChange.bind(this);
|
||||
|
||||
this._uploadInput = createRef();
|
||||
}
|
||||
|
||||
onUploadClick(ev) {
|
||||
|
@ -118,7 +120,7 @@ class UploadButton extends React.Component {
|
|||
dis.dispatch({action: 'require_registration'});
|
||||
return;
|
||||
}
|
||||
this.refs.uploadInput.click();
|
||||
this._uploadInput.current.click();
|
||||
}
|
||||
|
||||
onUploadFileInputChange(ev) {
|
||||
|
@ -150,7 +152,9 @@ class UploadButton extends React.Component {
|
|||
onClick={this.onUploadClick}
|
||||
title={_t('Upload file')}
|
||||
>
|
||||
<input ref="uploadInput" type="file"
|
||||
<input
|
||||
ref={this._uploadInput}
|
||||
type="file"
|
||||
style={uploadInputStyle}
|
||||
multiple
|
||||
onChange={this.onUploadFileInputChange}
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, {createRef} from "react";
|
||||
import dis from "../../../dispatcher";
|
||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
|
||||
|
@ -45,6 +45,8 @@ export default class RoomBreadcrumbs extends React.Component {
|
|||
// The room IDs we're waiting to come down the Room handler and when we
|
||||
// started waiting for them. Used to track a room over an upgrade/autojoin.
|
||||
this._waitingRoomQueue = [/* { roomId, addedTs } */];
|
||||
|
||||
this._scroller = createRef();
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
|
@ -284,8 +286,8 @@ export default class RoomBreadcrumbs extends React.Component {
|
|||
}
|
||||
this.setState({rooms});
|
||||
|
||||
if (this.refs.scroller) {
|
||||
this.refs.scroller.moveToOrigin();
|
||||
if (this._scroller.current) {
|
||||
this._scroller.current.moveToOrigin();
|
||||
}
|
||||
|
||||
// We don't track room aesthetics (badges, membership, etc) over the wire so we
|
||||
|
@ -390,7 +392,7 @@ export default class RoomBreadcrumbs extends React.Component {
|
|||
return (
|
||||
<div role="toolbar" aria-label={_t("Recent rooms")}>
|
||||
<IndicatorScrollbar
|
||||
ref="scroller"
|
||||
ref={this._scroller}
|
||||
className="mx_RoomBreadcrumbs"
|
||||
trackHorizontalOverflow={true}
|
||||
verticalScrollsHorizontally={true}
|
||||
|
|
|
@ -55,7 +55,7 @@ export default createReactClass({
|
|||
if (rows.length === 0) {
|
||||
rooms = <i>{ _t('No rooms to show') }</i>;
|
||||
} else {
|
||||
rooms = <table ref="directory_table" className="mx_RoomDirectory_table">
|
||||
rooms = <table className="mx_RoomDirectory_table">
|
||||
<tbody>
|
||||
{ this.getRows() }
|
||||
</tbody>
|
||||
|
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import sdk from '../../../index';
|
||||
import React from 'react';
|
||||
import React, {createRef} from 'react';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { linkifyElement } from '../../../HtmlUtils';
|
||||
import { ContentRepo } from 'matrix-js-sdk';
|
||||
|
@ -49,11 +49,15 @@ export default createReactClass({
|
|||
},
|
||||
|
||||
_linkifyTopic: function() {
|
||||
if (this.refs.topic) {
|
||||
linkifyElement(this.refs.topic);
|
||||
if (this._topic.current) {
|
||||
linkifyElement(this._topic.current);
|
||||
}
|
||||
},
|
||||
|
||||
UNSAFE_componentWillMount: function() {
|
||||
this._topic = createRef();
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this._linkifyTopic();
|
||||
},
|
||||
|
@ -104,7 +108,7 @@ export default createReactClass({
|
|||
<td className="mx_RoomDirectory_roomDescription">
|
||||
<div className="mx_RoomDirectory_name">{ name }</div>
|
||||
{ perms }
|
||||
<div className="mx_RoomDirectory_topic" ref="topic" onClick={this.onTopicClick}>
|
||||
<div className="mx_RoomDirectory_topic" ref={this._topic} onClick={this.onTopicClick}>
|
||||
{ room.topic }
|
||||
</div>
|
||||
<div className="mx_RoomDirectory_alias">{ getDisplayAliasForRoom(room) }</div>
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, {createRef} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import createReactClass from 'create-react-class';
|
||||
import classNames from 'classnames';
|
||||
|
@ -56,6 +56,10 @@ module.exports = createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
UNSAFE_componentWillMount: function() {
|
||||
this._topic = createRef();
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
const cli = MatrixClientPeg.get();
|
||||
cli.on("RoomState.events", this._onRoomStateEvents);
|
||||
|
@ -70,8 +74,8 @@ module.exports = createReactClass({
|
|||
},
|
||||
|
||||
componentDidUpdate: function() {
|
||||
if (this.refs.topic) {
|
||||
linkifyElement(this.refs.topic);
|
||||
if (this._topic.current) {
|
||||
linkifyElement(this._topic.current);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -204,7 +208,7 @@ module.exports = createReactClass({
|
|||
}
|
||||
}
|
||||
const topicElement =
|
||||
<div className="mx_RoomHeader_topic" ref="topic" title={topic} dir="auto">{ topic }</div>;
|
||||
<div className="mx_RoomHeader_topic" ref={this._topic} title={topic} dir="auto">{ topic }</div>;
|
||||
const avatarSize = 28;
|
||||
let roomAvatar;
|
||||
if (this.props.room) {
|
||||
|
|
|
@ -65,14 +65,14 @@ module.exports = createReactClass({
|
|||
|
||||
return (
|
||||
<div className="mx_RoomHeader_name">
|
||||
<EditableText ref="editor"
|
||||
className="mx_RoomHeader_nametext mx_RoomHeader_editable"
|
||||
placeholderClassName="mx_RoomHeader_placeholder"
|
||||
placeholder={this._placeholderName}
|
||||
blurToCancel={false}
|
||||
initialValue={this.state.name}
|
||||
onValueChanged={this._onValueChanged}
|
||||
dir="auto" />
|
||||
<EditableText
|
||||
className="mx_RoomHeader_nametext mx_RoomHeader_editable"
|
||||
placeholderClassName="mx_RoomHeader_placeholder"
|
||||
placeholder={this._placeholderName}
|
||||
blurToCancel={false}
|
||||
initialValue={this.state.name}
|
||||
onValueChanged={this._onValueChanged}
|
||||
dir="auto" />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -451,16 +451,21 @@ module.exports = createReactClass({
|
|||
if (isDM) {
|
||||
title = _t("Do you want to chat with %(user)s?",
|
||||
{ user: inviteMember.name });
|
||||
subTitle = [
|
||||
avatar,
|
||||
_t("<userName/> wants to chat", {}, {userName: () => inviterElement}),
|
||||
];
|
||||
primaryActionLabel = _t("Start chatting");
|
||||
} else {
|
||||
title = _t("Do you want to join %(roomName)s?",
|
||||
{ roomName: this._roomName() });
|
||||
subTitle = [
|
||||
avatar,
|
||||
_t("<userName/> invited you", {}, {userName: () => inviterElement}),
|
||||
];
|
||||
primaryActionLabel = _t("Accept");
|
||||
}
|
||||
subTitle = [
|
||||
avatar,
|
||||
_t("<userName/> invited you", {}, {userName: () => inviterElement}),
|
||||
];
|
||||
|
||||
primaryActionLabel = _t("Accept");
|
||||
primaryActionHandler = this.props.onJoinClick;
|
||||
secondaryActionLabel = _t("Reject");
|
||||
secondaryActionHandler = this.props.onRejectClick;
|
||||
|
|
|
@ -17,7 +17,6 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import createReactClass from 'create-react-class';
|
||||
|
@ -26,10 +25,9 @@ import dis from '../../../dispatcher';
|
|||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import DMRoomMap from '../../../utils/DMRoomMap';
|
||||
import sdk from '../../../index';
|
||||
import {createMenu} from '../../structures/ContextualMenu';
|
||||
import {ContextMenu, ContextMenuButton, toRightOf} from '../../structures/ContextMenu';
|
||||
import * as RoomNotifs from '../../../RoomNotifs';
|
||||
import * as FormattingUtils from '../../../utils/FormattingUtils';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import ActiveRoomObserver from '../../../ActiveRoomObserver';
|
||||
import RoomViewStore from '../../../stores/RoomViewStore';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
|
@ -61,7 +59,7 @@ module.exports = createReactClass({
|
|||
return ({
|
||||
hover: false,
|
||||
badgeHover: false,
|
||||
menuDisplayed: false,
|
||||
contextMenuPosition: null, // DOM bounding box, null if non-shown
|
||||
roomName: this.props.room.name,
|
||||
notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId),
|
||||
notificationCount: this.props.room.getUnreadNotificationCount(),
|
||||
|
@ -229,32 +227,6 @@ module.exports = createReactClass({
|
|||
this.badgeOnMouseLeave();
|
||||
},
|
||||
|
||||
_showContextMenu: function(x, y, chevronOffset) {
|
||||
const RoomTileContextMenu = sdk.getComponent('context_menus.RoomTileContextMenu');
|
||||
|
||||
createMenu(RoomTileContextMenu, {
|
||||
chevronOffset,
|
||||
left: x,
|
||||
top: y,
|
||||
room: this.props.room,
|
||||
onFinished: () => {
|
||||
this.setState({ menuDisplayed: false });
|
||||
this.props.refreshSubList();
|
||||
},
|
||||
});
|
||||
this.setState({ menuDisplayed: true });
|
||||
},
|
||||
|
||||
onContextMenu: function(e) {
|
||||
// Prevent the RoomTile onClick event firing as well
|
||||
e.preventDefault();
|
||||
// Only allow non-guests to access the context menu
|
||||
if (MatrixClientPeg.get().isGuest()) return;
|
||||
|
||||
const chevronOffset = 12;
|
||||
this._showContextMenu(e.clientX, e.clientY - (chevronOffset + 8), chevronOffset);
|
||||
},
|
||||
|
||||
badgeOnMouseEnter: function() {
|
||||
// Only allow non-guests to access the context menu
|
||||
// and only change it if it needs to change
|
||||
|
@ -267,26 +239,46 @@ module.exports = createReactClass({
|
|||
this.setState( { badgeHover: false } );
|
||||
},
|
||||
|
||||
onOpenMenu: function(e) {
|
||||
// Prevent the RoomTile onClick event firing as well
|
||||
e.stopPropagation();
|
||||
_showContextMenu: function(boundingClientRect) {
|
||||
// Only allow non-guests to access the context menu
|
||||
if (MatrixClientPeg.get().isGuest()) return;
|
||||
|
||||
const state = {
|
||||
contextMenuPosition: boundingClientRect,
|
||||
};
|
||||
|
||||
// If the badge is clicked, then no longer show tooltip
|
||||
if (this.props.collapsed) {
|
||||
this.setState({ hover: false });
|
||||
state.hover = false;
|
||||
}
|
||||
|
||||
const elementRect = e.target.getBoundingClientRect();
|
||||
this.setState(state);
|
||||
},
|
||||
|
||||
// The window X and Y offsets are to adjust position when zoomed in to page
|
||||
const x = elementRect.right + window.pageXOffset + 3;
|
||||
const chevronOffset = 12;
|
||||
let y = (elementRect.top + (elementRect.height / 2) + window.pageYOffset);
|
||||
y = y - (chevronOffset + 8); // where 8 is half the height of the chevron
|
||||
onContextMenuButtonClick: function(e) {
|
||||
// Prevent the RoomTile onClick event firing as well
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
this._showContextMenu(x, y, chevronOffset);
|
||||
this._showContextMenu(e.target.getBoundingClientRect());
|
||||
},
|
||||
|
||||
onContextMenu: function(e) {
|
||||
// Prevent the native context menu
|
||||
e.preventDefault();
|
||||
|
||||
this._showContextMenu({
|
||||
right: e.clientX,
|
||||
top: e.clientY,
|
||||
height: 0,
|
||||
});
|
||||
},
|
||||
|
||||
closeMenu: function() {
|
||||
this.setState({
|
||||
contextMenuPosition: null,
|
||||
});
|
||||
this.props.refreshSubList();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
@ -303,6 +295,8 @@ module.exports = createReactClass({
|
|||
subtext = this.state.statusMessage;
|
||||
}
|
||||
|
||||
const isMenuDisplayed = Boolean(this.state.contextMenuPosition);
|
||||
|
||||
const classes = classNames({
|
||||
'mx_RoomTile': true,
|
||||
'mx_RoomTile_selected': this.state.selected,
|
||||
|
@ -310,7 +304,7 @@ module.exports = createReactClass({
|
|||
'mx_RoomTile_unreadNotify': notifBadges,
|
||||
'mx_RoomTile_highlight': mentionBadges,
|
||||
'mx_RoomTile_invited': isInvite,
|
||||
'mx_RoomTile_menuDisplayed': this.state.menuDisplayed,
|
||||
'mx_RoomTile_menuDisplayed': isMenuDisplayed,
|
||||
'mx_RoomTile_noBadges': !badges,
|
||||
'mx_RoomTile_transparent': this.props.transparent,
|
||||
'mx_RoomTile_hasSubtext': subtext && !this.props.collapsed,
|
||||
|
@ -322,7 +316,7 @@ module.exports = createReactClass({
|
|||
|
||||
const badgeClasses = classNames({
|
||||
'mx_RoomTile_badge': true,
|
||||
'mx_RoomTile_badgeButton': this.state.badgeHover || this.state.menuDisplayed,
|
||||
'mx_RoomTile_badgeButton': this.state.badgeHover || isMenuDisplayed,
|
||||
});
|
||||
|
||||
let name = this.state.roomName;
|
||||
|
@ -344,7 +338,7 @@ module.exports = createReactClass({
|
|||
const nameClasses = classNames({
|
||||
'mx_RoomTile_name': true,
|
||||
'mx_RoomTile_invite': this.props.isInvite,
|
||||
'mx_RoomTile_badgeShown': badges || this.state.badgeHover || this.state.menuDisplayed,
|
||||
'mx_RoomTile_badgeShown': badges || this.state.badgeHover || isMenuDisplayed,
|
||||
});
|
||||
|
||||
subtextLabel = subtext ? <span className="mx_RoomTile_subtext">{ subtext }</span> : null;
|
||||
|
@ -360,9 +354,17 @@ module.exports = createReactClass({
|
|||
// incomingCallBox = <IncomingCallBox incomingCall={ this.props.incomingCall }/>;
|
||||
//}
|
||||
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
|
||||
let contextMenuButton;
|
||||
if (!MatrixClientPeg.get().isGuest()) {
|
||||
contextMenuButton = <AccessibleButton className="mx_RoomTile_menuButton" onClick={this.onOpenMenu} />;
|
||||
contextMenuButton = (
|
||||
<ContextMenuButton
|
||||
className="mx_RoomTile_menuButton"
|
||||
label={_t("Options")}
|
||||
isExpanded={isMenuDisplayed}
|
||||
onClick={this.onContextMenuButtonClick} />
|
||||
);
|
||||
}
|
||||
|
||||
const RoomAvatar = sdk.getComponent('avatars.RoomAvatar');
|
||||
|
@ -393,32 +395,47 @@ module.exports = createReactClass({
|
|||
ariaLabel += " " + _t("Unread messages.");
|
||||
}
|
||||
|
||||
return <AccessibleButton tabIndex="0"
|
||||
className={classes}
|
||||
onClick={this.onClick}
|
||||
onMouseEnter={this.onMouseEnter}
|
||||
onMouseLeave={this.onMouseLeave}
|
||||
onContextMenu={this.onContextMenu}
|
||||
aria-label={ariaLabel}
|
||||
aria-selected={this.state.selected}
|
||||
role="treeitem"
|
||||
>
|
||||
<div className={avatarClasses}>
|
||||
<div className="mx_RoomTile_avatar_container">
|
||||
<RoomAvatar room={this.props.room} width={24} height={24} />
|
||||
{ dmIndicator }
|
||||
let contextMenu;
|
||||
if (isMenuDisplayed) {
|
||||
const RoomTileContextMenu = sdk.getComponent('context_menus.RoomTileContextMenu');
|
||||
contextMenu = (
|
||||
<ContextMenu {...toRightOf(this.state.contextMenuPosition)} onFinished={this.closeMenu}>
|
||||
<RoomTileContextMenu room={this.props.room} onFinished={this.closeMenu} />
|
||||
</ContextMenu>
|
||||
);
|
||||
}
|
||||
|
||||
return <React.Fragment>
|
||||
<AccessibleButton
|
||||
tabIndex="0"
|
||||
className={classes}
|
||||
onClick={this.onClick}
|
||||
onMouseEnter={this.onMouseEnter}
|
||||
onMouseLeave={this.onMouseLeave}
|
||||
onContextMenu={this.onContextMenu}
|
||||
aria-label={ariaLabel}
|
||||
aria-selected={this.state.selected}
|
||||
role="treeitem"
|
||||
>
|
||||
<div className={avatarClasses}>
|
||||
<div className="mx_RoomTile_avatar_container">
|
||||
<RoomAvatar room={this.props.room} width={24} height={24} />
|
||||
{ dmIndicator }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mx_RoomTile_nameContainer">
|
||||
<div className="mx_RoomTile_labelContainer">
|
||||
{ label }
|
||||
{ subtextLabel }
|
||||
<div className="mx_RoomTile_nameContainer">
|
||||
<div className="mx_RoomTile_labelContainer">
|
||||
{ label }
|
||||
{ subtextLabel }
|
||||
</div>
|
||||
{ contextMenuButton }
|
||||
{ badge }
|
||||
</div>
|
||||
{ contextMenuButton }
|
||||
{ badge }
|
||||
</div>
|
||||
{ /* { incomingCallBox } */ }
|
||||
{ tooltip }
|
||||
</AccessibleButton>;
|
||||
{ /* { incomingCallBox } */ }
|
||||
{ tooltip }
|
||||
</AccessibleButton>
|
||||
|
||||
{ contextMenu }
|
||||
</React.Fragment>;
|
||||
},
|
||||
});
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, {createRef} from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
const classNames = require('classnames');
|
||||
const AccessibleButton = require('../../../components/views/elements/AccessibleButton');
|
||||
|
@ -29,6 +29,10 @@ module.exports = createReactClass({
|
|||
});
|
||||
},
|
||||
|
||||
UNSAFE_componentWillMount: function() {
|
||||
this._search_term = createRef();
|
||||
},
|
||||
|
||||
onThisRoomClick: function() {
|
||||
this.setState({ scope: 'Room' }, () => this._searchIfQuery());
|
||||
},
|
||||
|
@ -47,29 +51,41 @@ module.exports = createReactClass({
|
|||
},
|
||||
|
||||
_searchIfQuery: function() {
|
||||
if (this.refs.search_term.value) {
|
||||
if (this._search_term.current.value) {
|
||||
this.onSearch();
|
||||
}
|
||||
},
|
||||
|
||||
onSearch: function() {
|
||||
this.props.onSearch(this.refs.search_term.value, this.state.scope);
|
||||
this.props.onSearch(this._search_term.current.value, this.state.scope);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const searchButtonClasses = classNames({ mx_SearchBar_searchButton: true, mx_SearchBar_searching: this.props.searchInProgress });
|
||||
const thisRoomClasses = classNames({ mx_SearchBar_button: true, mx_SearchBar_unselected: this.state.scope !== 'Room' });
|
||||
const allRoomsClasses = classNames({ mx_SearchBar_button: true, mx_SearchBar_unselected: this.state.scope !== 'All' });
|
||||
const searchButtonClasses = classNames("mx_SearchBar_searchButton", {
|
||||
mx_SearchBar_searching: this.props.searchInProgress,
|
||||
});
|
||||
const thisRoomClasses = classNames("mx_SearchBar_button", {
|
||||
mx_SearchBar_unselected: this.state.scope !== 'Room',
|
||||
});
|
||||
const allRoomsClasses = classNames("mx_SearchBar_button", {
|
||||
mx_SearchBar_unselected: this.state.scope !== 'All',
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="mx_SearchBar">
|
||||
<AccessibleButton className={ thisRoomClasses } onClick={this.onThisRoomClick}>{_t("This Room")}</AccessibleButton>
|
||||
<AccessibleButton className={ allRoomsClasses } onClick={this.onAllRoomsClick}>{_t("All Rooms")}</AccessibleButton>
|
||||
<div className="mx_SearchBar_input mx_textinput">
|
||||
<input ref="search_term" type="text" autoFocus={true} placeholder={_t("Search…")} onKeyDown={this.onSearchChange} />
|
||||
<AccessibleButton className={ searchButtonClasses } onClick={this.onSearch}></AccessibleButton>
|
||||
<div className="mx_SearchBar_buttons" role="radiogroup">
|
||||
<AccessibleButton className={ thisRoomClasses } onClick={this.onThisRoomClick} aria-checked={this.state.scope === 'Room'} role="radio">
|
||||
{_t("This Room")}
|
||||
</AccessibleButton>
|
||||
<AccessibleButton className={ allRoomsClasses } onClick={this.onAllRoomsClick} aria-checked={this.state.scope === 'All'} role="radio">
|
||||
{_t("All Rooms")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
<AccessibleButton className="mx_SearchBar_cancel" onClick={this.props.onCancelClick}></AccessibleButton>
|
||||
<div className="mx_SearchBar_input mx_textinput">
|
||||
<input ref={this._search_term} type="text" autoFocus={true} placeholder={_t("Search…")} onKeyDown={this.onSearchChange} />
|
||||
<AccessibleButton className={ searchButtonClasses } onClick={this.onSearch} />
|
||||
</div>
|
||||
<AccessibleButton className="mx_SearchBar_cancel" onClick={this.props.onCancelClick} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -14,12 +14,11 @@ 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 React, {createRef} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { _t, _td } from '../../../languageHandler';
|
||||
import CallHandler from '../../../CallHandler';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import Modal from '../../../Modal';
|
||||
import sdk from '../../../index';
|
||||
import dis from '../../../dispatcher';
|
||||
import RoomViewStore from '../../../stores/RoomViewStore';
|
||||
|
@ -27,7 +26,6 @@ import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
|
|||
import Stickerpicker from './Stickerpicker';
|
||||
import { makeRoomPermalink } from '../../../utils/permalinks/Permalinks';
|
||||
import ContentMessages from '../../../ContentMessages';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import E2EIcon from './E2EIcon';
|
||||
|
||||
|
@ -143,6 +141,8 @@ class UploadButton extends React.Component {
|
|||
super(props, context);
|
||||
this.onUploadClick = this.onUploadClick.bind(this);
|
||||
this.onUploadFileInputChange = this.onUploadFileInputChange.bind(this);
|
||||
|
||||
this._uploadInput = createRef();
|
||||
}
|
||||
|
||||
onUploadClick(ev) {
|
||||
|
@ -150,7 +150,7 @@ class UploadButton extends React.Component {
|
|||
dis.dispatch({action: 'require_registration'});
|
||||
return;
|
||||
}
|
||||
this.refs.uploadInput.click();
|
||||
this._uploadInput.current.click();
|
||||
}
|
||||
|
||||
onUploadFileInputChange(ev) {
|
||||
|
@ -182,7 +182,7 @@ class UploadButton extends React.Component {
|
|||
onClick={this.onUploadClick}
|
||||
title={_t('Upload file')}
|
||||
>
|
||||
<input ref="uploadInput" type="file"
|
||||
<input ref={this._uploadInput} type="file"
|
||||
style={uploadInputStyle}
|
||||
multiple
|
||||
onChange={this.onUploadFileInputChange}
|
||||
|
|
|
@ -25,6 +25,7 @@ import ActiveWidgetStore from '../../../stores/ActiveWidgetStore';
|
|||
import PersistedElement from "../elements/PersistedElement";
|
||||
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import {ContextMenu} from "../../structures/ContextMenu";
|
||||
|
||||
const widgetType = 'm.stickerpicker';
|
||||
|
||||
|
@ -371,26 +372,8 @@ export default class Stickerpicker extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const ContextualMenu = sdk.getComponent('structures.ContextualMenu');
|
||||
const GenericElementContextMenu = sdk.getComponent('context_menus.GenericElementContextMenu');
|
||||
let stickerPicker;
|
||||
let stickersButton;
|
||||
|
||||
const stickerPicker = <ContextualMenu
|
||||
elementClass={GenericElementContextMenu}
|
||||
chevronOffset={this.state.stickerPickerChevronOffset}
|
||||
chevronFace={'bottom'}
|
||||
left={this.state.stickerPickerX}
|
||||
top={this.state.stickerPickerY}
|
||||
menuWidth={this.popoverWidth}
|
||||
menuHeight={this.popoverHeight}
|
||||
element={this._getStickerpickerContent()}
|
||||
onFinished={this._onFinished}
|
||||
menuPaddingTop={0}
|
||||
menuPaddingLeft={0}
|
||||
menuPaddingRight={0}
|
||||
zIndex={STICKERPICKER_Z_INDEX}
|
||||
/>;
|
||||
|
||||
if (this.state.showStickers) {
|
||||
// Show hide-stickers button
|
||||
stickersButton =
|
||||
|
@ -402,6 +385,23 @@ export default class Stickerpicker extends React.Component {
|
|||
title={_t("Hide Stickers")}
|
||||
>
|
||||
</AccessibleButton>;
|
||||
|
||||
const GenericElementContextMenu = sdk.getComponent('context_menus.GenericElementContextMenu');
|
||||
stickerPicker = <ContextMenu
|
||||
chevronOffset={this.state.stickerPickerChevronOffset}
|
||||
chevronFace="bottom"
|
||||
left={this.state.stickerPickerX}
|
||||
top={this.state.stickerPickerY}
|
||||
menuWidth={this.popoverWidth}
|
||||
menuHeight={this.popoverHeight}
|
||||
onFinished={this._onFinished}
|
||||
menuPaddingTop={0}
|
||||
menuPaddingLeft={0}
|
||||
menuPaddingRight={0}
|
||||
zIndex={STICKERPICKER_Z_INDEX}
|
||||
>
|
||||
<GenericElementContextMenu element={this._getStickerpickerContent()} onResize={this._onFinished} />
|
||||
</ContextMenu>;
|
||||
} else {
|
||||
// Show show-stickers button
|
||||
stickersButton =
|
||||
|
@ -415,8 +415,8 @@ export default class Stickerpicker extends React.Component {
|
|||
</AccessibleButton>;
|
||||
}
|
||||
return <React.Fragment>
|
||||
{stickersButton}
|
||||
{this.state.showStickers && stickerPicker}
|
||||
{ stickersButton }
|
||||
{ stickerPicker }
|
||||
</React.Fragment>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, {createRef} from 'react';
|
||||
import {_t} from "../../../languageHandler";
|
||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||
import Field from "../elements/Field";
|
||||
|
@ -48,13 +48,15 @@ export default class ProfileSettings extends React.Component {
|
|||
avatarFile: null,
|
||||
enableProfileSave: false,
|
||||
};
|
||||
|
||||
this._avatarUpload = createRef();
|
||||
}
|
||||
|
||||
_uploadAvatar = (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
this.refs.avatarUpload.click();
|
||||
this._avatarUpload.current.click();
|
||||
};
|
||||
|
||||
_saveProfile = async (e) => {
|
||||
|
@ -156,7 +158,7 @@ export default class ProfileSettings extends React.Component {
|
|||
|
||||
return (
|
||||
<form onSubmit={this._saveProfile} autoComplete="off" noValidate={true}>
|
||||
<input type="file" ref="avatarUpload" className="mx_ProfileSettings_avatarUpload"
|
||||
<input type="file" ref={this._avatarUpload} className="mx_ProfileSettings_avatarUpload"
|
||||
onChange={this._onAvatarChanged} accept="image/*" />
|
||||
<div className="mx_ProfileSettings_profile">
|
||||
<div className="mx_ProfileSettings_controls">
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, {createRef} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {_t} from "../../../../../languageHandler";
|
||||
import MatrixClientPeg from "../../../../../MatrixClientPeg";
|
||||
|
@ -44,13 +44,15 @@ export default class NotificationsSettingsTab extends React.Component {
|
|||
}
|
||||
this.setState({currentSound: soundData.name || soundData.url});
|
||||
});
|
||||
|
||||
this._soundUpload = createRef();
|
||||
}
|
||||
|
||||
async _triggerUploader(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
this.refs.soundUpload.click();
|
||||
this._soundUpload.current.click();
|
||||
}
|
||||
|
||||
async _onSoundUploadChanged(e) {
|
||||
|
@ -157,7 +159,7 @@ export default class NotificationsSettingsTab extends React.Component {
|
|||
<div>
|
||||
<h3>{_t("Set a new custom sound")}</h3>
|
||||
<form autoComplete="off" noValidate={true}>
|
||||
<input ref="soundUpload" className="mx_NotificationSound_soundUpload" type="file" onChange={this._onSoundUploadChanged.bind(this)} accept="audio/*" />
|
||||
<input ref={this._soundUpload} className="mx_NotificationSound_soundUpload" type="file" onChange={this._onSoundUploadChanged.bind(this)} accept="audio/*" />
|
||||
</form>
|
||||
|
||||
{currentUploadedFile}
|
||||
|
|
|
@ -49,8 +49,6 @@ export default class GeneralUserSettingsTab extends React.Component {
|
|||
|
||||
this.state = {
|
||||
language: languageHandler.getCurrentLanguage(),
|
||||
theme: SettingsStore.getValueAt(SettingLevel.ACCOUNT, "theme"),
|
||||
useSystemTheme: SettingsStore.getValueAt(SettingLevel.DEVICE, "use_system_theme"),
|
||||
haveIdServer: Boolean(MatrixClientPeg.get().getIdentityServerUrl()),
|
||||
serverSupportsSeparateAddAndBind: null,
|
||||
idServerHasUnsignedTerms: false,
|
||||
|
@ -62,6 +60,7 @@ export default class GeneralUserSettingsTab extends React.Component {
|
|||
},
|
||||
emails: [],
|
||||
msisdns: [],
|
||||
...this._calculateThemeState(),
|
||||
};
|
||||
|
||||
this.dispatcherRef = dis.register(this._onAction);
|
||||
|
@ -80,6 +79,39 @@ export default class GeneralUserSettingsTab extends React.Component {
|
|||
dis.unregister(this.dispatcherRef);
|
||||
}
|
||||
|
||||
_calculateThemeState() {
|
||||
// We have to mirror the logic from ThemeWatcher.getEffectiveTheme so we
|
||||
// show the right values for things.
|
||||
|
||||
const themeChoice = SettingsStore.getValueAt(SettingLevel.ACCOUNT, "theme");
|
||||
const systemThemeExplicit = SettingsStore.getValueAt(
|
||||
SettingLevel.DEVICE, "use_system_theme", null, false, true);
|
||||
const themeExplicit = SettingsStore.getValueAt(
|
||||
SettingLevel.DEVICE, "theme", null, false, true);
|
||||
|
||||
// If the user has enabled system theme matching, use that.
|
||||
if (systemThemeExplicit) {
|
||||
return {
|
||||
theme: themeChoice,
|
||||
useSystemTheme: true,
|
||||
};
|
||||
}
|
||||
|
||||
// If the user has set a theme explicitly, use that (no system theme matching)
|
||||
if (themeExplicit) {
|
||||
return {
|
||||
theme: themeChoice,
|
||||
useSystemTheme: false,
|
||||
};
|
||||
}
|
||||
|
||||
// Otherwise assume the defaults for the settings
|
||||
return {
|
||||
theme: themeChoice,
|
||||
useSystemTheme: SettingsStore.getValueAt(SettingLevel.DEVICE, "use_system_theme"),
|
||||
};
|
||||
}
|
||||
|
||||
_onAction = (payload) => {
|
||||
if (payload.action === 'id_server_changed') {
|
||||
this.setState({haveIdServer: Boolean(MatrixClientPeg.get().getIdentityServerUrl())});
|
||||
|
@ -89,11 +121,11 @@ export default class GeneralUserSettingsTab extends React.Component {
|
|||
|
||||
_onEmailsChange = (emails) => {
|
||||
this.setState({ emails });
|
||||
}
|
||||
};
|
||||
|
||||
_onMsisdnsChange = (msisdns) => {
|
||||
this.setState({ msisdns });
|
||||
}
|
||||
};
|
||||
|
||||
async _getThreepidState() {
|
||||
const cli = MatrixClientPeg.get();
|
||||
|
@ -193,9 +225,9 @@ export default class GeneralUserSettingsTab extends React.Component {
|
|||
|
||||
_onUseSystemThemeChanged = (checked) => {
|
||||
this.setState({useSystemTheme: checked});
|
||||
SettingsStore.setValue("use_system_theme", null, SettingLevel.DEVICE, checked);
|
||||
dis.dispatch({action: 'recheck_theme'});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
_onPasswordChangeError = (err) => {
|
||||
// TODO: Figure out a design that doesn't involve replacing the current dialog
|
||||
|
@ -307,12 +339,15 @@ export default class GeneralUserSettingsTab extends React.Component {
|
|||
|
||||
_renderThemeSection() {
|
||||
const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag");
|
||||
const LabelledToggleSwitch = sdk.getComponent("views.elements.LabelledToggleSwitch");
|
||||
|
||||
const themeWatcher = new ThemeWatcher();
|
||||
let systemThemeSection;
|
||||
if (themeWatcher.isSystemThemeSupported()) {
|
||||
systemThemeSection = <div>
|
||||
<SettingsFlag name="use_system_theme" level={SettingLevel.DEVICE}
|
||||
<LabelledToggleSwitch
|
||||
value={this.state.useSystemTheme}
|
||||
label={SettingsStore.getDisplayName("use_system_theme")}
|
||||
onChange={this._onUseSystemThemeChanged}
|
||||
/>
|
||||
</div>;
|
||||
|
|
|
@ -13,7 +13,7 @@ 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 React, {createRef} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import createReactClass from 'create-react-class';
|
||||
import dis from '../../../dispatcher';
|
||||
|
@ -56,6 +56,10 @@ module.exports = createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
UNSAFE_componentWillMount: function() {
|
||||
this._video = createRef();
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
this.showCall();
|
||||
|
@ -128,7 +132,7 @@ module.exports = createReactClass({
|
|||
},
|
||||
|
||||
getVideoView: function() {
|
||||
return this.refs.video;
|
||||
return this._video.current;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
@ -147,7 +151,9 @@ module.exports = createReactClass({
|
|||
|
||||
return (
|
||||
<div>
|
||||
<VideoView ref="video" onClick={this.props.onClick}
|
||||
<VideoView
|
||||
ref={this._video}
|
||||
onClick={this.props.onClick}
|
||||
onResize={this.props.onResize}
|
||||
maxHeight={this.props.maxVideoHeight}
|
||||
/>
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, {createRef} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import createReactClass from 'create-react-class';
|
||||
|
||||
|
@ -30,12 +30,16 @@ module.exports = createReactClass({
|
|||
onResize: PropTypes.func,
|
||||
},
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
this._vid = createRef();
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
this.refs.vid.addEventListener('resize', this.onResize);
|
||||
this._vid.current.addEventListener('resize', this.onResize);
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
this.refs.vid.removeEventListener('resize', this.onResize);
|
||||
this._vid.current.removeEventListener('resize', this.onResize);
|
||||
},
|
||||
|
||||
onResize: function(e) {
|
||||
|
@ -46,7 +50,7 @@ module.exports = createReactClass({
|
|||
|
||||
render: function() {
|
||||
return (
|
||||
<video ref="vid" style={{maxHeight: this.props.maxHeight}}>
|
||||
<video ref={this._vid} style={{maxHeight: this.props.maxHeight}}>
|
||||
</video>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, {createRef} from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import createReactClass from 'create-react-class';
|
||||
|
@ -49,6 +49,11 @@ module.exports = createReactClass({
|
|||
onResize: PropTypes.func,
|
||||
},
|
||||
|
||||
UNSAFE_componentWillMount: function() {
|
||||
this._local = createRef();
|
||||
this._remote = createRef();
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
},
|
||||
|
@ -58,7 +63,7 @@ module.exports = createReactClass({
|
|||
},
|
||||
|
||||
getRemoteVideoElement: function() {
|
||||
return ReactDOM.findDOMNode(this.refs.remote);
|
||||
return ReactDOM.findDOMNode(this._remote.current);
|
||||
},
|
||||
|
||||
getRemoteAudioElement: function() {
|
||||
|
@ -74,7 +79,7 @@ module.exports = createReactClass({
|
|||
},
|
||||
|
||||
getLocalVideoElement: function() {
|
||||
return ReactDOM.findDOMNode(this.refs.local);
|
||||
return ReactDOM.findDOMNode(this._local.current);
|
||||
},
|
||||
|
||||
setContainer: function(c) {
|
||||
|
@ -125,11 +130,11 @@ module.exports = createReactClass({
|
|||
return (
|
||||
<div className="mx_VideoView" ref={this.setContainer} onClick={this.props.onClick}>
|
||||
<div className="mx_VideoView_remoteVideoFeed">
|
||||
<VideoFeed ref="remote" onResize={this.props.onResize}
|
||||
<VideoFeed ref={this._remote} onResize={this.props.onResize}
|
||||
maxHeight={maxVideoHeight} />
|
||||
</div>
|
||||
<div className={localVideoFeedClasses}>
|
||||
<VideoFeed ref="local" />
|
||||
<VideoFeed ref={this._local} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue