Merge pull request #2883 from YaoiFangirl420/cleanup_message_composer
Cleanup message composer render() methodpull/21833/head
commit
9917bed942
|
@ -42,14 +42,159 @@ const formatButtonList = [
|
|||
_td("numbered-list"),
|
||||
];
|
||||
|
||||
function ComposerAvatar(props) {
|
||||
const MemberStatusMessageAvatar = sdk.getComponent('avatars.MemberStatusMessageAvatar');
|
||||
return <div className="mx_MessageComposer_avatar">
|
||||
<MemberStatusMessageAvatar member={props.me} width={24} height={24} />
|
||||
</div>;
|
||||
}
|
||||
|
||||
ComposerAvatar.propTypes = {
|
||||
me: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
function CallButton(props) {
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
const onVoiceCallClick = (ev) => {
|
||||
dis.dispatch({
|
||||
action: 'place_call',
|
||||
type: "voice",
|
||||
room_id: props.roomId,
|
||||
});
|
||||
};
|
||||
|
||||
return <AccessibleButton className="mx_MessageComposer_button mx_MessageComposer_voicecall"
|
||||
onClick={onVoiceCallClick}
|
||||
title={_t('Voice call')}
|
||||
/>
|
||||
}
|
||||
|
||||
CallButton.propTypes = {
|
||||
roomId: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
function VideoCallButton(props) {
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
const onCallClick = (ev) => {
|
||||
dis.dispatch({
|
||||
action: 'place_call',
|
||||
type: ev.shiftKey ? "screensharing" : "video",
|
||||
room_id: props.roomId,
|
||||
});
|
||||
};
|
||||
|
||||
return <AccessibleButton className="mx_MessageComposer_button mx_MessageComposer_videocall"
|
||||
onClick={onCallClick}
|
||||
title={_t('Video call')}
|
||||
/>;
|
||||
}
|
||||
|
||||
VideoCallButton.propTypes = {
|
||||
roomId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
function HangupButton(props) {
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
const onHangupClick = () => {
|
||||
const call = CallHandler.getCallForRoom(props.roomId);
|
||||
if (!call) {
|
||||
return;
|
||||
}
|
||||
dis.dispatch({
|
||||
action: 'hangup',
|
||||
// hangup the call for this room, which may not be the room in props
|
||||
// (e.g. conferences which will hangup the 1:1 room instead)
|
||||
room_id: call.roomId,
|
||||
});
|
||||
};
|
||||
return <AccessibleButton className="mx_MessageComposer_button mx_MessageComposer_hangup"
|
||||
onClick={onHangupClick}
|
||||
title={_t('Hangup')}
|
||||
/>;
|
||||
}
|
||||
|
||||
HangupButton.propTypes = {
|
||||
roomId: PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
function FormattingButton(props) {
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
return <AccessibleButton
|
||||
element="img"
|
||||
className="mx_MessageComposer_formatting"
|
||||
alt={_t("Show Text Formatting Toolbar")}
|
||||
title={_t("Show Text Formatting Toolbar")}
|
||||
src={require("../../../../res/img/button-text-formatting.svg")}
|
||||
style={{visibility: props.showFormatting ? 'hidden' : 'visible'}}
|
||||
onClick={props.onClickHandler}
|
||||
/>;
|
||||
}
|
||||
|
||||
FormattingButton.propTypes = {
|
||||
showFormatting: PropTypes.bool.isRequired,
|
||||
onClickHandler: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
class UploadButton extends React.Component {
|
||||
static propTypes = {
|
||||
roomId: PropTypes.string.isRequired,
|
||||
}
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.onUploadClick = this.onUploadClick.bind(this);
|
||||
this.onUploadFileInputChange = this.onUploadFileInputChange.bind(this);
|
||||
}
|
||||
|
||||
onUploadClick(ev) {
|
||||
if (MatrixClientPeg.get().isGuest()) {
|
||||
dis.dispatch({action: 'require_registration'});
|
||||
return;
|
||||
}
|
||||
this.refs.uploadInput.click();
|
||||
}
|
||||
|
||||
onUploadFileInputChange(ev) {
|
||||
if (ev.target.files.length === 0) return;
|
||||
|
||||
// take a copy so we can safely reset the value of the form control
|
||||
// (Note it is a FileList: we can't use slice or sesnible iteration).
|
||||
const tfiles = [];
|
||||
for (let i = 0; i < ev.target.files.length; ++i) {
|
||||
tfiles.push(ev.target.files[i]);
|
||||
}
|
||||
|
||||
ContentMessages.sharedInstance().sendContentListToRoom(
|
||||
tfiles, this.props.roomId, MatrixClientPeg.get(),
|
||||
);
|
||||
|
||||
// This is the onChange handler for a file form control, but we're
|
||||
// not keeping any state, so reset the value of the form control
|
||||
// to empty.
|
||||
// NB. we need to set 'value': the 'files' property is immutable.
|
||||
ev.target.value = '';
|
||||
}
|
||||
|
||||
render() {
|
||||
const uploadInputStyle = {display: 'none'};
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
return (
|
||||
<AccessibleButton className="mx_MessageComposer_button mx_MessageComposer_upload"
|
||||
onClick={this.onUploadClick}
|
||||
title={_t('Upload file')}
|
||||
>
|
||||
<input ref="uploadInput" type="file"
|
||||
style={uploadInputStyle}
|
||||
multiple
|
||||
onChange={this.onUploadFileInputChange}
|
||||
/>
|
||||
</AccessibleButton>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default class MessageComposer extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.onCallClick = this.onCallClick.bind(this);
|
||||
this.onHangupClick = this.onHangupClick.bind(this);
|
||||
this.onUploadClick = this.onUploadClick.bind(this);
|
||||
this._onUploadFileInputChange = this._onUploadFileInputChange.bind(this);
|
||||
this.onVoiceCallClick = this.onVoiceCallClick.bind(this);
|
||||
this._onAutocompleteConfirm = this._onAutocompleteConfirm.bind(this);
|
||||
this.onToggleFormattingClicked = this.onToggleFormattingClicked.bind(this);
|
||||
this.onToggleMarkdownClicked = this.onToggleMarkdownClicked.bind(this);
|
||||
|
@ -58,6 +203,8 @@ export default class MessageComposer extends React.Component {
|
|||
this._onRoomStateEvents = this._onRoomStateEvents.bind(this);
|
||||
this._onRoomViewStoreUpdate = this._onRoomViewStoreUpdate.bind(this);
|
||||
this._onTombstoneClick = this._onTombstoneClick.bind(this);
|
||||
this.renderPlaceholderText = this.renderPlaceholderText.bind(this);
|
||||
this.renderFormatBar = this.renderFormatBar.bind(this);
|
||||
|
||||
this.state = {
|
||||
inputState: {
|
||||
|
@ -136,65 +283,6 @@ export default class MessageComposer extends React.Component {
|
|||
this.setState({ isQuoting });
|
||||
}
|
||||
|
||||
onUploadClick(ev) {
|
||||
if (MatrixClientPeg.get().isGuest()) {
|
||||
dis.dispatch({action: 'require_registration'});
|
||||
return;
|
||||
}
|
||||
|
||||
this.refs.uploadInput.click();
|
||||
}
|
||||
|
||||
_onUploadFileInputChange(ev) {
|
||||
if (ev.target.files.length === 0) return;
|
||||
|
||||
// take a copy so we can safely reset the value of the form control
|
||||
// (Note it is a FileList: we can't use slice or sesnible iteration).
|
||||
const tfiles = [];
|
||||
for (let i = 0; i < ev.target.files.length; ++i) {
|
||||
tfiles.push(ev.target.files[i]);
|
||||
}
|
||||
|
||||
ContentMessages.sharedInstance().sendContentListToRoom(
|
||||
tfiles, this.props.room.roomId, MatrixClientPeg.get(),
|
||||
);
|
||||
|
||||
// This is the onChange handler for a file form control, but we're
|
||||
// not keeping any state, so reset the value of the form control
|
||||
// to empty.
|
||||
// NB. we need to set 'value': the 'files' property is immutable.
|
||||
ev.target.value = '';
|
||||
}
|
||||
|
||||
onHangupClick() {
|
||||
const call = CallHandler.getCallForRoom(this.props.room.roomId);
|
||||
//var call = CallHandler.getAnyActiveCall();
|
||||
if (!call) {
|
||||
return;
|
||||
}
|
||||
dis.dispatch({
|
||||
action: 'hangup',
|
||||
// hangup the call for this room, which may not be the room in props
|
||||
// (e.g. conferences which will hangup the 1:1 room instead)
|
||||
room_id: call.roomId,
|
||||
});
|
||||
}
|
||||
|
||||
onCallClick(ev) {
|
||||
dis.dispatch({
|
||||
action: 'place_call',
|
||||
type: ev.shiftKey ? "screensharing" : "video",
|
||||
room_id: this.props.room.roomId,
|
||||
});
|
||||
}
|
||||
|
||||
onVoiceCallClick(ev) {
|
||||
dis.dispatch({
|
||||
action: 'place_call',
|
||||
type: "voice",
|
||||
room_id: this.props.room.roomId,
|
||||
});
|
||||
}
|
||||
|
||||
onInputStateChanged(inputState) {
|
||||
// Merge the new input state with old to support partial updates
|
||||
|
@ -245,119 +333,94 @@ export default class MessageComposer extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const uploadInputStyle = {display: 'none'};
|
||||
const MemberStatusMessageAvatar = sdk.getComponent('avatars.MemberStatusMessageAvatar');
|
||||
const MessageComposerInput = sdk.getComponent("rooms.MessageComposerInput");
|
||||
|
||||
const controls = [];
|
||||
|
||||
if (this.state.me) {
|
||||
controls.push(
|
||||
<div key="controls_avatar" className="mx_MessageComposer_avatar">
|
||||
<MemberStatusMessageAvatar member={this.state.me} width={24} height={24} />
|
||||
</div>,
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.e2eStatus) {
|
||||
controls.push(<E2EIcon
|
||||
status={this.props.e2eStatus}
|
||||
key="e2eIcon"
|
||||
className="mx_MessageComposer_e2eIcon" />
|
||||
);
|
||||
}
|
||||
|
||||
let callButton;
|
||||
let videoCallButton;
|
||||
let hangupButton;
|
||||
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
// Call buttons
|
||||
if (this.props.callState && this.props.callState !== 'ended') {
|
||||
hangupButton =
|
||||
<AccessibleButton className="mx_MessageComposer_button mx_MessageComposer_hangup"
|
||||
key="controls_hangup"
|
||||
onClick={this.onHangupClick}
|
||||
title={_t('Hangup')}
|
||||
>
|
||||
</AccessibleButton>;
|
||||
renderPlaceholderText() {
|
||||
const roomIsEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId);
|
||||
if (this.state.isQuoting) {
|
||||
if (roomIsEncrypted) {
|
||||
return _t('Send an encrypted reply…');
|
||||
} else {
|
||||
return _t('Send a reply (unencrypted)…');
|
||||
}
|
||||
} else {
|
||||
callButton =
|
||||
<AccessibleButton className="mx_MessageComposer_button mx_MessageComposer_voicecall"
|
||||
key="controls_call"
|
||||
onClick={this.onVoiceCallClick}
|
||||
title={_t('Voice call')}
|
||||
>
|
||||
</AccessibleButton>;
|
||||
videoCallButton =
|
||||
<AccessibleButton className="mx_MessageComposer_button mx_MessageComposer_videocall"
|
||||
key="controls_videocall"
|
||||
onClick={this.onCallClick}
|
||||
title={_t('Video call')}
|
||||
>
|
||||
</AccessibleButton>;
|
||||
if (roomIsEncrypted) {
|
||||
return _t('Send an encrypted message…');
|
||||
} else {
|
||||
return _t('Send a message (unencrypted)…');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
renderFormatBar() {
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
const {marks, blockType} = this.state.inputState;
|
||||
const formatButtons = formatButtonList.map((name) => {
|
||||
// special-case to match the md serializer and the special-case in MessageComposerInput.js
|
||||
const markName = name === 'inline-code' ? 'code' : name;
|
||||
const active = marks.some(mark => mark.type === markName) || blockType === name;
|
||||
const suffix = active ? '-on' : '';
|
||||
const onFormatButtonClicked = this.onFormatButtonClicked.bind(this, name);
|
||||
const className = 'mx_MessageComposer_format_button mx_filterFlipColor';
|
||||
return (
|
||||
<img className={className}
|
||||
title={_t(name)}
|
||||
onMouseDown={onFormatButtonClicked}
|
||||
key={name}
|
||||
src={require(`../../../../res/img/button-text-${name}${suffix}.svg`)}
|
||||
height="17"
|
||||
/>
|
||||
);
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="mx_MessageComposer_formatbar_wrapper">
|
||||
<div className="mx_MessageComposer_formatbar">
|
||||
{ formatButtons }
|
||||
<div style={{ flex: 1 }}></div>
|
||||
<AccessibleButton
|
||||
className="mx_MessageComposer_formatbar_markdown mx_MessageComposer_markdownDisabled"
|
||||
onClick={this.onToggleMarkdownClicked}
|
||||
title={_t("Markdown is disabled")}
|
||||
/>
|
||||
<AccessibleButton element="img" title={_t("Hide Text Formatting Toolbar")}
|
||||
onClick={this.onToggleFormattingClicked}
|
||||
className="mx_MessageComposer_formatbar_cancel mx_filterFlipColor"
|
||||
src={require("../../../../res/img/icon-text-cancel.svg")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const controls = [
|
||||
this.state.me ? <ComposerAvatar key="controls_avatar" me={this.state.me} /> : null,
|
||||
this.props.e2eStatus ? <E2EIcon key="e2eIcon" status={this.props.e2eStatus} className="mx_MessageComposer_e2eIcon" /> : null,
|
||||
];
|
||||
|
||||
if (!this.state.tombstone && this.state.canSendMessages) {
|
||||
// This also currently includes the call buttons. Really we should
|
||||
// check separately for whether we can call, but this is slightly
|
||||
// complex because of conference calls.
|
||||
const uploadButton = (
|
||||
<AccessibleButton className="mx_MessageComposer_button mx_MessageComposer_upload"
|
||||
key="controls_upload"
|
||||
onClick={this.onUploadClick}
|
||||
title={_t('Upload file')}
|
||||
>
|
||||
<input ref="uploadInput" type="file"
|
||||
style={uploadInputStyle}
|
||||
multiple
|
||||
onChange={this._onUploadFileInputChange} />
|
||||
</AccessibleButton>
|
||||
);
|
||||
|
||||
const formattingButton = this.state.inputState.isRichTextEnabled ? (
|
||||
<AccessibleButton element="img" className="mx_MessageComposer_formatting"
|
||||
alt={_t("Show Text Formatting Toolbar")}
|
||||
title={_t("Show Text Formatting Toolbar")}
|
||||
src={require("../../../../res/img/button-text-formatting.svg")}
|
||||
onClick={this.onToggleFormattingClicked}
|
||||
style={{visibility: this.state.showFormatting ? 'hidden' : 'visible'}}
|
||||
key="controls_formatting" />
|
||||
) : null;
|
||||
|
||||
const roomIsEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId);
|
||||
let placeholderText;
|
||||
if (this.state.isQuoting) {
|
||||
if (roomIsEncrypted) {
|
||||
placeholderText = _t('Send an encrypted reply…');
|
||||
} else {
|
||||
placeholderText = _t('Send a reply (unencrypted)…');
|
||||
}
|
||||
} else {
|
||||
if (roomIsEncrypted) {
|
||||
placeholderText = _t('Send an encrypted message…');
|
||||
} else {
|
||||
placeholderText = _t('Send a message (unencrypted)…');
|
||||
}
|
||||
}
|
||||
|
||||
const stickerpickerButton = <Stickerpicker key='stickerpicker_controls_button' room={this.props.room} />;
|
||||
const MessageComposerInput = sdk.getComponent("rooms.MessageComposerInput");
|
||||
const showFormattingButton = this.state.inputState.isRichTextEnabled;
|
||||
const callInProgress = this.props.callState && this.props.callState !== 'ended';
|
||||
|
||||
controls.push(
|
||||
<MessageComposerInput
|
||||
ref={(c) => this.messageComposerInput = c}
|
||||
key="controls_input"
|
||||
room={this.props.room}
|
||||
placeholder={placeholderText}
|
||||
placeholder={this.renderPlaceholderText()}
|
||||
onInputStateChanged={this.onInputStateChanged}
|
||||
permalinkCreator={this.props.permalinkCreator} />,
|
||||
formattingButton,
|
||||
stickerpickerButton,
|
||||
uploadButton,
|
||||
hangupButton,
|
||||
callButton,
|
||||
videoCallButton,
|
||||
showFormattingButton ? <FormattingButton key="controls_formatting"
|
||||
showFormatting={this.state.showFormatting} onClickHandler={this.onToggleFormattingClicked} /> : null,
|
||||
<Stickerpicker key='stickerpicker_controls_button' room={this.props.room} />,
|
||||
<UploadButton key="controls_upload" roomId={this.props.room.roomId} />,
|
||||
callInProgress ? <HangupButton key="controls_hangup" roomId={this.props.room.roomId} /> : null,
|
||||
callInProgress ? null : <CallButton key="controls_call" roomId={this.props.room.roomId} />,
|
||||
callInProgress ? null : <VideoCallButton key="controls_videocall" roomId={this.props.room.roomId} />,
|
||||
);
|
||||
} else if (this.state.tombstone) {
|
||||
const replacementRoomId = this.state.tombstone.getContent()['replacement_room'];
|
||||
|
@ -388,42 +451,7 @@ export default class MessageComposer extends React.Component {
|
|||
);
|
||||
}
|
||||
|
||||
let formatBar;
|
||||
if (this.state.showFormatting && this.state.inputState.isRichTextEnabled) {
|
||||
const {marks, blockType} = this.state.inputState;
|
||||
const formatButtons = formatButtonList.map((name) => {
|
||||
// special-case to match the md serializer and the special-case in MessageComposerInput.js
|
||||
const markName = name === 'inline-code' ? 'code' : name;
|
||||
const active = marks.some(mark => mark.type === markName) || blockType === name;
|
||||
const suffix = active ? '-on' : '';
|
||||
const onFormatButtonClicked = this.onFormatButtonClicked.bind(this, name);
|
||||
const className = 'mx_MessageComposer_format_button mx_filterFlipColor';
|
||||
return <img className={className}
|
||||
title={_t(name)}
|
||||
onMouseDown={onFormatButtonClicked}
|
||||
key={name}
|
||||
src={require(`../../../../res/img/button-text-${name}${suffix}.svg`)}
|
||||
height="17" />;
|
||||
},
|
||||
);
|
||||
|
||||
formatBar =
|
||||
<div className="mx_MessageComposer_formatbar_wrapper">
|
||||
<div className="mx_MessageComposer_formatbar">
|
||||
{ formatButtons }
|
||||
<div style={{ flex: 1 }}></div>
|
||||
<AccessibleButton className="mx_MessageComposer_formatbar_markdown mx_MessageComposer_markdownDisabled"
|
||||
onClick={this.onToggleMarkdownClicked}
|
||||
title={_t("Markdown is disabled")}
|
||||
/>
|
||||
<AccessibleButton element="img" title={_t("Hide Text Formatting Toolbar")}
|
||||
onClick={this.onToggleFormattingClicked}
|
||||
className="mx_MessageComposer_formatbar_cancel mx_filterFlipColor"
|
||||
src={require("../../../../res/img/icon-text-cancel.svg")}
|
||||
/>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
const showFormatBar = this.state.showFormatting && this.state.inputState.isRichTextEnabled;
|
||||
|
||||
const wrapperClasses = classNames({
|
||||
mx_MessageComposer_wrapper: true,
|
||||
|
@ -436,7 +464,7 @@ export default class MessageComposer extends React.Component {
|
|||
{ controls }
|
||||
</div>
|
||||
</div>
|
||||
{ formatBar }
|
||||
{ showFormatBar ? this.renderFormatBar() : null }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue