@@ -19,23 +19,16 @@ import { MatrixEvent, IEventRelation } from "matrix-js-sdk/src/models/event";
1919import { Room } from "matrix-js-sdk/src/models/room" ;
2020import { RoomMember } from "matrix-js-sdk/src/models/room-member" ;
2121import { RelationType } from 'matrix-js-sdk/src/@types/event' ;
22- import { M_POLL_START } from "matrix-events-sdk" ;
2322
2423import { _t } from '../../../languageHandler' ;
2524import { MatrixClientPeg } from '../../../MatrixClientPeg' ;
2625import dis from '../../../dispatcher/dispatcher' ;
2726import { ActionPayload } from "../../../dispatcher/payloads" ;
2827import Stickerpicker from './Stickerpicker' ;
2928import { makeRoomPermalink , RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks' ;
30- import ContentMessages from '../../../ContentMessages' ;
3129import E2EIcon from './E2EIcon' ;
3230import SettingsStore from "../../../settings/SettingsStore" ;
33- import ContextMenu , {
34- aboveLeftOf ,
35- useContextMenu ,
36- MenuItem ,
37- AboveLeftOf ,
38- } from "../../structures/ContextMenu" ;
31+ import { aboveLeftOf , AboveLeftOf } from "../../structures/ContextMenu" ;
3932import AccessibleTooltipButton from "../elements/AccessibleTooltipButton" ;
4033import ReplyPreview from "./ReplyPreview" ;
4134import { UPDATE_EVENT } from "../../../stores/AsyncStore" ;
@@ -50,15 +43,10 @@ import SendMessageComposer, { SendMessageComposer as SendMessageComposerClass }
5043import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload" ;
5144import { Action } from "../../../dispatcher/actions" ;
5245import EditorModel from "../../../editor/model" ;
53- import EmojiPicker from '../emojipicker/EmojiPicker' ;
5446import UIStore , { UI_EVENTS } from '../../../stores/UIStore' ;
55- import Modal from "../../../Modal" ;
5647import RoomContext from '../../../contexts/RoomContext' ;
57- import ErrorDialog from "../dialogs/ErrorDialog" ;
58- import PollCreateDialog from "../elements/PollCreateDialog" ;
5948import { SettingUpdatedPayload } from "../../../dispatcher/payloads/SettingUpdatedPayload" ;
60- import { CollapsibleButton , ICollapsibleButtonProps } from './CollapsibleButton' ;
61- import LocationButton from '../location/LocationButton' ;
49+ import MessageComposerButtons from './MessageComposerButtons' ;
6250
6351let instanceCount = 0 ;
6452const NARROW_MODE_BREAKPOINT = 500 ;
@@ -78,164 +66,6 @@ function SendButton(props: ISendButtonProps) {
7866 ) ;
7967}
8068
81- interface IEmojiButtonProps extends Pick < ICollapsibleButtonProps , "narrowMode" > {
82- addEmoji : ( unicode : string ) => boolean ;
83- menuPosition : AboveLeftOf ;
84- }
85-
86- const EmojiButton : React . FC < IEmojiButtonProps > = ( { addEmoji, menuPosition, narrowMode } ) => {
87- const [ menuDisplayed , button , openMenu , closeMenu ] = useContextMenu ( ) ;
88-
89- let contextMenu ;
90- if ( menuDisplayed ) {
91- const position = menuPosition ?? aboveLeftOf ( button . current . getBoundingClientRect ( ) ) ;
92- contextMenu = < ContextMenu { ...position } onFinished = { closeMenu } managed = { false } >
93- < EmojiPicker onChoose = { addEmoji } showQuickReactions = { true } />
94- </ ContextMenu > ;
95- }
96-
97- const className = classNames (
98- "mx_MessageComposer_button" ,
99- "mx_MessageComposer_emoji" ,
100- {
101- "mx_MessageComposer_button_highlight" : menuDisplayed ,
102- } ,
103- ) ;
104-
105- // TODO: replace ContextMenuTooltipButton with a unified representation of
106- // the header buttons and the right panel buttons
107- return < React . Fragment >
108- < CollapsibleButton
109- className = { className }
110- onClick = { openMenu }
111- narrowMode = { narrowMode }
112- title = { _t ( "Add emoji" ) }
113- />
114-
115- { contextMenu }
116- </ React . Fragment > ;
117- } ;
118-
119- interface IUploadButtonProps {
120- roomId : string ;
121- relation ?: IEventRelation | null ;
122- }
123-
124- class UploadButton extends React . Component < IUploadButtonProps > {
125- private uploadInput = React . createRef < HTMLInputElement > ( ) ;
126- private dispatcherRef : string ;
127-
128- constructor ( props : IUploadButtonProps ) {
129- super ( props ) ;
130-
131- this . dispatcherRef = dis . register ( this . onAction ) ;
132- }
133-
134- componentWillUnmount ( ) {
135- dis . unregister ( this . dispatcherRef ) ;
136- }
137-
138- private onAction = ( payload : ActionPayload ) => {
139- if ( payload . action === "upload_file" ) {
140- this . onUploadClick ( ) ;
141- }
142- } ;
143-
144- private onUploadClick = ( ) => {
145- if ( MatrixClientPeg . get ( ) . isGuest ( ) ) {
146- dis . dispatch ( { action : 'require_registration' } ) ;
147- return ;
148- }
149- this . uploadInput . current . click ( ) ;
150- } ;
151-
152- private onUploadFileInputChange = ( ev : React . ChangeEvent < HTMLInputElement > ) => {
153- if ( ev . target . files . length === 0 ) return ;
154-
155- // take a copy so we can safely reset the value of the form control
156- // (Note it is a FileList: we can't use slice or sensible iteration).
157- const tfiles = [ ] ;
158- for ( let i = 0 ; i < ev . target . files . length ; ++ i ) {
159- tfiles . push ( ev . target . files [ i ] ) ;
160- }
161-
162- ContentMessages . sharedInstance ( ) . sendContentListToRoom (
163- tfiles ,
164- this . props . roomId ,
165- this . props . relation ,
166- MatrixClientPeg . get ( ) ,
167- this . context . timelineRenderingType ,
168- ) ;
169-
170- // This is the onChange handler for a file form control, but we're
171- // not keeping any state, so reset the value of the form control
172- // to empty.
173- // NB. we need to set 'value': the 'files' property is immutable.
174- ev . target . value = '' ;
175- } ;
176-
177- render ( ) {
178- const uploadInputStyle = { display : 'none' } ;
179- return (
180- < AccessibleTooltipButton
181- className = "mx_MessageComposer_button mx_MessageComposer_upload"
182- onClick = { this . onUploadClick }
183- title = { _t ( 'Upload file' ) }
184- >
185- < input
186- ref = { this . uploadInput }
187- type = "file"
188- style = { uploadInputStyle }
189- multiple
190- onChange = { this . onUploadFileInputChange }
191- />
192- </ AccessibleTooltipButton >
193- ) ;
194- }
195- }
196-
197- interface IPollButtonProps extends Pick < ICollapsibleButtonProps , "narrowMode" > {
198- room : Room ;
199- }
200-
201- class PollButton extends React . PureComponent < IPollButtonProps > {
202- private onCreateClick = ( ) => {
203- const canSend = this . props . room . currentState . maySendEvent (
204- M_POLL_START . name ,
205- MatrixClientPeg . get ( ) . getUserId ( ) ,
206- ) ;
207- if ( ! canSend ) {
208- Modal . createTrackedDialog ( 'Polls' , 'permissions error: cannot start' , ErrorDialog , {
209- title : _t ( "Permission Required" ) ,
210- description : _t ( "You do not have permission to start polls in this room." ) ,
211- } ) ;
212- } else {
213- Modal . createTrackedDialog (
214- 'Polls' ,
215- 'create' ,
216- PollCreateDialog ,
217- {
218- room : this . props . room ,
219- } ,
220- 'mx_CompoundDialog' ,
221- false , // isPriorityModal
222- true , // isStaticModal
223- ) ;
224- }
225- } ;
226-
227- render ( ) {
228- return (
229- < CollapsibleButton
230- className = "mx_MessageComposer_button mx_MessageComposer_poll"
231- onClick = { this . onCreateClick }
232- narrowMode = { this . props . narrowMode }
233- title = { _t ( "Create poll" ) }
234- />
235- ) ;
236- }
237- }
238-
23969interface IProps {
24070 room : Room ;
24171 resizeNotifier : ResizeNotifier ;
@@ -509,108 +339,6 @@ export default class MessageComposer extends React.Component<IProps, IState> {
509339 } ) ;
510340 } ;
511341
512- private renderButtons ( menuPosition : AboveLeftOf ) : JSX . Element | JSX . Element [ ] {
513- if ( this . state . haveRecording ) {
514- return [ ] ;
515- }
516-
517- let uploadButtonIndex = 0 ;
518- const buttons : JSX . Element [ ] = [ ] ;
519- buttons . push (
520- < PollButton
521- key = "polls"
522- room = { this . props . room }
523- narrowMode = { this . state . narrowMode }
524- /> ,
525- ) ;
526- uploadButtonIndex = buttons . length ;
527- buttons . push (
528- < UploadButton key = "controls_upload" roomId = { this . props . room . roomId } relation = { this . props . relation } /> ,
529- ) ;
530- if ( this . state . showLocationButton ) {
531- const sender = this . props . room . getMember (
532- MatrixClientPeg . get ( ) . getUserId ( ) ,
533- ) ;
534- buttons . push (
535- < LocationButton
536- key = "location"
537- roomId = { this . props . room . roomId }
538- sender = { sender }
539- menuPosition = { menuPosition }
540- narrowMode = { this . state . narrowMode }
541- /> ,
542- ) ;
543- }
544- buttons . push (
545- < EmojiButton key = "emoji_button" addEmoji = { this . addEmoji } menuPosition = { menuPosition } narrowMode = { this . state . narrowMode } /> ,
546- ) ;
547- if ( this . state . showStickersButton ) {
548- let title : string ;
549- if ( ! this . state . narrowMode ) {
550- title = this . state . isStickerPickerOpen ? _t ( "Hide Stickers" ) : _t ( "Show Stickers" ) ;
551- }
552-
553- buttons . push (
554- < AccessibleTooltipButton
555- id = 'stickersButton'
556- key = "controls_stickers"
557- className = "mx_MessageComposer_button mx_MessageComposer_stickers"
558- onClick = { ( ) => this . setStickerPickerOpen ( ! this . state . isStickerPickerOpen ) }
559- title = { title }
560- label = { this . state . narrowMode ? _t ( "Send a sticker" ) : null }
561- /> ,
562- ) ;
563- }
564-
565- // XXX: the recording UI does not work well in narrow mode, so we hide this button for now
566- if ( ! this . state . narrowMode ) {
567- buttons . push (
568- < CollapsibleButton
569- key = "voice_message_send"
570- className = "mx_MessageComposer_button mx_MessageComposer_voiceMessage"
571- onClick = { ( ) => this . voiceRecordingButton . current ?. onRecordStartEndClick ( ) }
572- title = { _t ( "Send voice message" ) }
573- narrowMode = { this . state . narrowMode }
574- /> ,
575- ) ;
576- }
577-
578- if ( ! this . state . narrowMode ) {
579- return buttons ;
580- }
581-
582- const classnames = classNames ( {
583- mx_MessageComposer_button : true ,
584- mx_MessageComposer_buttonMenu : true ,
585- mx_MessageComposer_closeButtonMenu : this . state . isMenuOpen ,
586- } ) ;
587-
588- // we render the uploadButton at top level as it is a very common interaction, splice it out of the rest
589- const [ uploadButton ] = buttons . splice ( uploadButtonIndex , 1 ) ;
590- return < >
591- { uploadButton }
592- < AccessibleTooltipButton
593- className = { classnames }
594- onClick = { this . toggleButtonMenu }
595- title = { _t ( "More options" ) }
596- tooltip = { false }
597- />
598- { this . state . isMenuOpen && (
599- < ContextMenu
600- onFinished = { this . toggleButtonMenu }
601- { ...menuPosition }
602- wrapperClassName = "mx_MessageComposer_Menu"
603- >
604- { buttons . map ( ( button , index ) => (
605- < MenuItem className = "mx_CallContextMenu_item" key = { index } onClick = { this . toggleButtonMenu } >
606- { button }
607- </ MenuItem >
608- ) ) }
609- </ ContextMenu >
610- ) }
611- </ > ;
612- }
613-
614342 render ( ) {
615343 const controls = [
616344 this . props . e2eStatus ?
@@ -717,7 +445,20 @@ export default class MessageComposer extends React.Component<IProps, IState> {
717445 permalinkCreator = { this . props . permalinkCreator } />
718446 < div className = "mx_MessageComposer_row" >
719447 { controls }
720- { this . renderButtons ( menuPosition ) }
448+ < MessageComposerButtons
449+ addEmoji = { this . addEmoji }
450+ haveRecording = { this . state . haveRecording }
451+ isMenuOpen = { this . state . isMenuOpen }
452+ isStickerPickerOpen = { this . state . isStickerPickerOpen }
453+ menuPosition = { menuPosition }
454+ narrowMode = { this . state . narrowMode }
455+ relation = { this . props . relation }
456+ onRecordStartEndClick = { ( ) => this . voiceRecordingButton . current ?. onRecordStartEndClick ( ) }
457+ setStickerPickerOpen = { this . setStickerPickerOpen }
458+ showLocationButton = { this . state . showLocationButton }
459+ showStickersButton = { this . state . showStickersButton }
460+ toggleButtonMenu = { this . toggleButtonMenu }
461+ />
721462 { showSendButton && (
722463 < SendButton
723464 key = "controls_send"
0 commit comments