You've already forked matrix-react-sdk
							
							
				mirror of
				https://github.com/matrix-org/matrix-react-sdk.git
				synced 2025-11-03 00:33:22 +03:00 
			
		
		
		
	Merge pull request #6752 from matrix-org/revert-6682-gsouquet/compact-composer-18533
Revert "Create narrow mode for Composer"
This commit is contained in:
		@@ -237,15 +237,6 @@ limitations under the License.
 | 
				
			|||||||
    mask-image: url('$(res)/img/element-icons/room/composer/sticker.svg');
 | 
					    mask-image: url('$(res)/img/element-icons/room/composer/sticker.svg');
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.mx_MessageComposer_buttonMenu::before {
 | 
					 | 
				
			||||||
    mask-image: url('$(res)/img/image-view/more.svg');
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.mx_MessageComposer_closeButtonMenu::before {
 | 
					 | 
				
			||||||
    transform: rotate(90deg);
 | 
					 | 
				
			||||||
    transform-origin: center;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.mx_MessageComposer_sendMessage {
 | 
					.mx_MessageComposer_sendMessage {
 | 
				
			||||||
    cursor: pointer;
 | 
					    cursor: pointer;
 | 
				
			||||||
    position: relative;
 | 
					    position: relative;
 | 
				
			||||||
@@ -365,8 +356,3 @@ limitations under the License.
 | 
				
			|||||||
        margin-right: 0;
 | 
					        margin-right: 0;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
.mx_MessageComposer_Menu .mx_CallContextMenu_item {
 | 
					 | 
				
			||||||
    display: flex;
 | 
					 | 
				
			||||||
    align-items: center;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
limitations under the License.
 | 
					limitations under the License.
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
import React, { createRef } from 'react';
 | 
					import React from 'react';
 | 
				
			||||||
import classNames from 'classnames';
 | 
					import classNames from 'classnames';
 | 
				
			||||||
import { _t } from '../../../languageHandler';
 | 
					import { _t } from '../../../languageHandler';
 | 
				
			||||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
 | 
					import { MatrixClientPeg } from '../../../MatrixClientPeg';
 | 
				
			||||||
@@ -27,13 +27,7 @@ import { makeRoomPermalink, RoomPermalinkCreator } from '../../../utils/permalin
 | 
				
			|||||||
import ContentMessages from '../../../ContentMessages';
 | 
					import ContentMessages from '../../../ContentMessages';
 | 
				
			||||||
import E2EIcon from './E2EIcon';
 | 
					import E2EIcon from './E2EIcon';
 | 
				
			||||||
import SettingsStore from "../../../settings/SettingsStore";
 | 
					import SettingsStore from "../../../settings/SettingsStore";
 | 
				
			||||||
import {
 | 
					import { aboveLeftOf, ContextMenu, ContextMenuTooltipButton, useContextMenu } from "../../structures/ContextMenu";
 | 
				
			||||||
    aboveLeftOf,
 | 
					 | 
				
			||||||
    ContextMenu,
 | 
					 | 
				
			||||||
    ContextMenuTooltipButton,
 | 
					 | 
				
			||||||
    useContextMenu,
 | 
					 | 
				
			||||||
    MenuItem,
 | 
					 | 
				
			||||||
} from "../../structures/ContextMenu";
 | 
					 | 
				
			||||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
 | 
					import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
 | 
				
			||||||
import ReplyPreview from "./ReplyPreview";
 | 
					import ReplyPreview from "./ReplyPreview";
 | 
				
			||||||
import { UIFeature } from "../../../settings/UIFeature";
 | 
					import { UIFeature } from "../../../settings/UIFeature";
 | 
				
			||||||
@@ -51,9 +45,6 @@ import { Action } from "../../../dispatcher/actions";
 | 
				
			|||||||
import EditorModel from "../../../editor/model";
 | 
					import EditorModel from "../../../editor/model";
 | 
				
			||||||
import EmojiPicker from '../emojipicker/EmojiPicker';
 | 
					import EmojiPicker from '../emojipicker/EmojiPicker';
 | 
				
			||||||
import MemberStatusMessageAvatar from "../avatars/MemberStatusMessageAvatar";
 | 
					import MemberStatusMessageAvatar from "../avatars/MemberStatusMessageAvatar";
 | 
				
			||||||
import UIStore, { UI_EVENTS } from '../../../stores/UIStore';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const NARROW_MODE_BREAKPOINT = 500;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IComposerAvatarProps {
 | 
					interface IComposerAvatarProps {
 | 
				
			||||||
    me: object;
 | 
					    me: object;
 | 
				
			||||||
@@ -80,13 +71,13 @@ function SendButton(props: ISendButtonProps) {
 | 
				
			|||||||
    );
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const EmojiButton = ({ addEmoji, menuPosition }) => {
 | 
					const EmojiButton = ({ addEmoji }) => {
 | 
				
			||||||
    const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
 | 
					    const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let contextMenu;
 | 
					    let contextMenu;
 | 
				
			||||||
    if (menuDisplayed) {
 | 
					    if (menuDisplayed) {
 | 
				
			||||||
        const position = menuPosition ?? aboveLeftOf(button.current.getBoundingClientRect());
 | 
					        const buttonRect = button.current.getBoundingClientRect();
 | 
				
			||||||
        contextMenu = <ContextMenu {...position} onFinished={closeMenu} managed={false}>
 | 
					        contextMenu = <ContextMenu {...aboveLeftOf(buttonRect)} onFinished={closeMenu} managed={false}>
 | 
				
			||||||
            <EmojiPicker onChoose={addEmoji} showQuickReactions={true} />
 | 
					            <EmojiPicker onChoose={addEmoji} showQuickReactions={true} />
 | 
				
			||||||
        </ContextMenu>;
 | 
					        </ContextMenu>;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -205,9 +196,6 @@ interface IState {
 | 
				
			|||||||
    haveRecording: boolean;
 | 
					    haveRecording: boolean;
 | 
				
			||||||
    recordingTimeLeftSeconds?: number;
 | 
					    recordingTimeLeftSeconds?: number;
 | 
				
			||||||
    me?: RoomMember;
 | 
					    me?: RoomMember;
 | 
				
			||||||
    narrowMode?: boolean;
 | 
					 | 
				
			||||||
    isMenuOpen: boolean;
 | 
					 | 
				
			||||||
    showStickers: boolean;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@replaceableComponent("views.rooms.MessageComposer")
 | 
					@replaceableComponent("views.rooms.MessageComposer")
 | 
				
			||||||
@@ -215,7 +203,6 @@ export default class MessageComposer extends React.Component<IProps, IState> {
 | 
				
			|||||||
    private dispatcherRef: string;
 | 
					    private dispatcherRef: string;
 | 
				
			||||||
    private messageComposerInput: SendMessageComposer;
 | 
					    private messageComposerInput: SendMessageComposer;
 | 
				
			||||||
    private voiceRecordingButton: VoiceRecordComposerTile;
 | 
					    private voiceRecordingButton: VoiceRecordComposerTile;
 | 
				
			||||||
    private ref: React.RefObject<HTMLDivElement> = createRef();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static defaultProps = {
 | 
					    static defaultProps = {
 | 
				
			||||||
        replyInThread: false,
 | 
					        replyInThread: false,
 | 
				
			||||||
@@ -233,8 +220,6 @@ export default class MessageComposer extends React.Component<IProps, IState> {
 | 
				
			|||||||
            isComposerEmpty: true,
 | 
					            isComposerEmpty: true,
 | 
				
			||||||
            haveRecording: false,
 | 
					            haveRecording: false,
 | 
				
			||||||
            recordingTimeLeftSeconds: null, // when set to a number, shows a toast
 | 
					            recordingTimeLeftSeconds: null, // when set to a number, shows a toast
 | 
				
			||||||
            isMenuOpen: false,
 | 
					 | 
				
			||||||
            showStickers: false,
 | 
					 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -242,21 +227,8 @@ export default class MessageComposer extends React.Component<IProps, IState> {
 | 
				
			|||||||
        this.dispatcherRef = dis.register(this.onAction);
 | 
					        this.dispatcherRef = dis.register(this.onAction);
 | 
				
			||||||
        MatrixClientPeg.get().on("RoomState.events", this.onRoomStateEvents);
 | 
					        MatrixClientPeg.get().on("RoomState.events", this.onRoomStateEvents);
 | 
				
			||||||
        this.waitForOwnMember();
 | 
					        this.waitForOwnMember();
 | 
				
			||||||
        UIStore.instance.trackElementDimensions("MessageComposer", this.ref.current);
 | 
					 | 
				
			||||||
        UIStore.instance.on("MessageComposer", this.onResize);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private onResize = (type: UI_EVENTS, entry: ResizeObserverEntry) => {
 | 
					 | 
				
			||||||
        if (type === UI_EVENTS.Resize) {
 | 
					 | 
				
			||||||
            const narrowMode = entry.contentRect.width <= NARROW_MODE_BREAKPOINT;
 | 
					 | 
				
			||||||
            this.setState({
 | 
					 | 
				
			||||||
                narrowMode,
 | 
					 | 
				
			||||||
                isMenuOpen: !narrowMode ? false : this.state.isMenuOpen,
 | 
					 | 
				
			||||||
                showStickers: false,
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private onAction = (payload: ActionPayload) => {
 | 
					    private onAction = (payload: ActionPayload) => {
 | 
				
			||||||
        if (payload.action === 'reply_to_event') {
 | 
					        if (payload.action === 'reply_to_event') {
 | 
				
			||||||
            // add a timeout for the reply preview to be rendered, so
 | 
					            // add a timeout for the reply preview to be rendered, so
 | 
				
			||||||
@@ -291,8 +263,6 @@ export default class MessageComposer extends React.Component<IProps, IState> {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        VoiceRecordingStore.instance.off(UPDATE_EVENT, this.onVoiceStoreUpdate);
 | 
					        VoiceRecordingStore.instance.off(UPDATE_EVENT, this.onVoiceStoreUpdate);
 | 
				
			||||||
        dis.unregister(this.dispatcherRef);
 | 
					        dis.unregister(this.dispatcherRef);
 | 
				
			||||||
        UIStore.instance.stopTrackingElementDimensions("MessageComposer");
 | 
					 | 
				
			||||||
        UIStore.instance.removeListener("MessageComposer", this.onResize);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private onRoomStateEvents = (ev, state) => {
 | 
					    private onRoomStateEvents = (ev, state) => {
 | 
				
			||||||
@@ -399,96 +369,6 @@ export default class MessageComposer extends React.Component<IProps, IState> {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private shouldShowStickerPicker = (): boolean => {
 | 
					 | 
				
			||||||
        return SettingsStore.getValue(UIFeature.Widgets)
 | 
					 | 
				
			||||||
        && SettingsStore.getValue("MessageComposerInput.showStickersButton")
 | 
					 | 
				
			||||||
        && !this.state.haveRecording;
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private showStickers = (showStickers: boolean) => {
 | 
					 | 
				
			||||||
        this.setState({ showStickers });
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private toggleButtonMenu = (): void => {
 | 
					 | 
				
			||||||
        this.setState({
 | 
					 | 
				
			||||||
            isMenuOpen: !this.state.isMenuOpen,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private renderButtons(menuPosition): JSX.Element | JSX.Element[] {
 | 
					 | 
				
			||||||
        const buttons = new Map<string, JSX.Element>();
 | 
					 | 
				
			||||||
        if (!this.state.haveRecording) {
 | 
					 | 
				
			||||||
            buttons.set(
 | 
					 | 
				
			||||||
                _t("Send File"),
 | 
					 | 
				
			||||||
                <UploadButton key="controls_upload" roomId={this.props.room.roomId} />,
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
            buttons.set(
 | 
					 | 
				
			||||||
                _t("Show Emojis"),
 | 
					 | 
				
			||||||
                <EmojiButton key="emoji_button" addEmoji={this.addEmoji} menuPosition={menuPosition} />,
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (this.shouldShowStickerPicker()) {
 | 
					 | 
				
			||||||
            buttons.set(
 | 
					 | 
				
			||||||
                _t("Show Stickers"),
 | 
					 | 
				
			||||||
                <AccessibleTooltipButton
 | 
					 | 
				
			||||||
                    id='stickersButton'
 | 
					 | 
				
			||||||
                    key="controls_stickers"
 | 
					 | 
				
			||||||
                    className="mx_MessageComposer_button mx_MessageComposer_stickers"
 | 
					 | 
				
			||||||
                    onClick={() => this.showStickers(!this.state.showStickers)}
 | 
					 | 
				
			||||||
                    title={this.state.showStickers ? _t("Hide Stickers") : _t("Show Stickers")}
 | 
					 | 
				
			||||||
                />,
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (!this.state.haveRecording && !this.state.narrowMode) {
 | 
					 | 
				
			||||||
            buttons.set(
 | 
					 | 
				
			||||||
                _t("Send voice message"),
 | 
					 | 
				
			||||||
                <AccessibleTooltipButton
 | 
					 | 
				
			||||||
                    className="mx_MessageComposer_button mx_MessageComposer_voiceMessage"
 | 
					 | 
				
			||||||
                    onClick={() => this.voiceRecordingButton?.onRecordStartEndClick()}
 | 
					 | 
				
			||||||
                    title={_t("Send voice message")}
 | 
					 | 
				
			||||||
                />,
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (!this.state.narrowMode) {
 | 
					 | 
				
			||||||
            return Array.from(buttons.values());
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            const classnames = classNames({
 | 
					 | 
				
			||||||
                mx_MessageComposer_button: true,
 | 
					 | 
				
			||||||
                mx_MessageComposer_buttonMenu: true,
 | 
					 | 
				
			||||||
                mx_MessageComposer_closeButtonMenu: this.state.isMenuOpen,
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return <>
 | 
					 | 
				
			||||||
                { buttons[0] }
 | 
					 | 
				
			||||||
                <AccessibleTooltipButton
 | 
					 | 
				
			||||||
                    className={classnames}
 | 
					 | 
				
			||||||
                    onClick={this.toggleButtonMenu}
 | 
					 | 
				
			||||||
                    title={_t("Composer menu")}
 | 
					 | 
				
			||||||
                    tooltip={false}
 | 
					 | 
				
			||||||
                />
 | 
					 | 
				
			||||||
                { this.state.isMenuOpen && (
 | 
					 | 
				
			||||||
                    <ContextMenu
 | 
					 | 
				
			||||||
                        onFinished={this.toggleButtonMenu}
 | 
					 | 
				
			||||||
                        {...menuPosition}
 | 
					 | 
				
			||||||
                        menuPaddingRight={10}
 | 
					 | 
				
			||||||
                        menuPaddingTop={5}
 | 
					 | 
				
			||||||
                        menuPaddingBottom={5}
 | 
					 | 
				
			||||||
                        menuWidth={150}
 | 
					 | 
				
			||||||
                        wrapperClassName="mx_MessageComposer_Menu"
 | 
					 | 
				
			||||||
                    >
 | 
					 | 
				
			||||||
                        { Array.from(buttons).slice(1).map(([label, button]) => (
 | 
					 | 
				
			||||||
                            <MenuItem className="mx_CallContextMenu_item" key={label} onClick={this.toggleButtonMenu}>
 | 
					 | 
				
			||||||
                                { button }
 | 
					 | 
				
			||||||
                                { label }
 | 
					 | 
				
			||||||
                            </MenuItem>
 | 
					 | 
				
			||||||
                        )) }
 | 
					 | 
				
			||||||
                    </ContextMenu>
 | 
					 | 
				
			||||||
                ) }
 | 
					 | 
				
			||||||
            </>;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    render() {
 | 
					    render() {
 | 
				
			||||||
        const controls = [
 | 
					        const controls = [
 | 
				
			||||||
            this.state.me && !this.props.compact ? <ComposerAvatar key="controls_avatar" me={this.state.me} /> : null,
 | 
					            this.state.me && !this.props.compact ? <ComposerAvatar key="controls_avatar" me={this.state.me} /> : null,
 | 
				
			||||||
@@ -497,12 +377,6 @@ export default class MessageComposer extends React.Component<IProps, IState> {
 | 
				
			|||||||
                null,
 | 
					                null,
 | 
				
			||||||
        ];
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let menuPosition;
 | 
					 | 
				
			||||||
        if (this.ref.current) {
 | 
					 | 
				
			||||||
            const contentRect = this.ref.current.getBoundingClientRect();
 | 
					 | 
				
			||||||
            menuPosition = aboveLeftOf(contentRect);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (!this.state.tombstone && this.state.canSendMessages) {
 | 
					        if (!this.state.tombstone && this.state.canSendMessages) {
 | 
				
			||||||
            controls.push(
 | 
					            controls.push(
 | 
				
			||||||
                <SendMessageComposer
 | 
					                <SendMessageComposer
 | 
				
			||||||
@@ -518,10 +392,33 @@ export default class MessageComposer extends React.Component<IProps, IState> {
 | 
				
			|||||||
                />,
 | 
					                />,
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!this.state.haveRecording) {
 | 
				
			||||||
 | 
					                controls.push(
 | 
				
			||||||
 | 
					                    <UploadButton key="controls_upload" roomId={this.props.room.roomId} />,
 | 
				
			||||||
 | 
					                    <EmojiButton key="emoji_button" addEmoji={this.addEmoji} />,
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (SettingsStore.getValue(UIFeature.Widgets) &&
 | 
				
			||||||
 | 
					                SettingsStore.getValue("MessageComposerInput.showStickersButton") &&
 | 
				
			||||||
 | 
					                !this.state.haveRecording) {
 | 
				
			||||||
 | 
					                controls.push(<Stickerpicker key="stickerpicker_controls_button" room={this.props.room} />);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            controls.push(<VoiceRecordComposerTile
 | 
					            controls.push(<VoiceRecordComposerTile
 | 
				
			||||||
                key="controls_voice_record"
 | 
					                key="controls_voice_record"
 | 
				
			||||||
                ref={c => this.voiceRecordingButton = c}
 | 
					                ref={c => this.voiceRecordingButton = c}
 | 
				
			||||||
                room={this.props.room} />);
 | 
					                room={this.props.room} />);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!this.state.isComposerEmpty || this.state.haveRecording) {
 | 
				
			||||||
 | 
					                controls.push(
 | 
				
			||||||
 | 
					                    <SendButton
 | 
				
			||||||
 | 
					                        key="controls_send"
 | 
				
			||||||
 | 
					                        onClick={this.sendMessage}
 | 
				
			||||||
 | 
					                        title={this.state.haveRecording ? _t("Send voice message") : undefined}
 | 
				
			||||||
 | 
					                    />,
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        } else if (this.state.tombstone) {
 | 
					        } else if (this.state.tombstone) {
 | 
				
			||||||
            const replacementRoomId = this.state.tombstone.getContent()['replacement_room'];
 | 
					            const replacementRoomId = this.state.tombstone.getContent()['replacement_room'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -562,15 +459,6 @@ export default class MessageComposer extends React.Component<IProps, IState> {
 | 
				
			|||||||
                yOffset={-50}
 | 
					                yOffset={-50}
 | 
				
			||||||
            />;
 | 
					            />;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        controls.push(
 | 
					 | 
				
			||||||
            <Stickerpicker
 | 
					 | 
				
			||||||
                room={this.props.room}
 | 
					 | 
				
			||||||
                showStickers={this.state.showStickers}
 | 
					 | 
				
			||||||
                setShowStickers={this.showStickers}
 | 
					 | 
				
			||||||
                menuPosition={menuPosition} />,
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const showSendButton = !this.state.isComposerEmpty || this.state.haveRecording;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const classes = classNames({
 | 
					        const classes = classNames({
 | 
				
			||||||
            "mx_MessageComposer": true,
 | 
					            "mx_MessageComposer": true,
 | 
				
			||||||
@@ -579,7 +467,7 @@ export default class MessageComposer extends React.Component<IProps, IState> {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return (
 | 
					        return (
 | 
				
			||||||
            <div className={classes} ref={this.ref}>
 | 
					            <div className={classes}>
 | 
				
			||||||
                { recordingTooltip }
 | 
					                { recordingTooltip }
 | 
				
			||||||
                <div className="mx_MessageComposer_wrapper">
 | 
					                <div className="mx_MessageComposer_wrapper">
 | 
				
			||||||
                    { this.props.showReplyPreview && (
 | 
					                    { this.props.showReplyPreview && (
 | 
				
			||||||
@@ -587,14 +475,6 @@ export default class MessageComposer extends React.Component<IProps, IState> {
 | 
				
			|||||||
                    ) }
 | 
					                    ) }
 | 
				
			||||||
                    <div className="mx_MessageComposer_row">
 | 
					                    <div className="mx_MessageComposer_row">
 | 
				
			||||||
                        { controls }
 | 
					                        { controls }
 | 
				
			||||||
                        { this.renderButtons(menuPosition) }
 | 
					 | 
				
			||||||
                        { showSendButton && (
 | 
					 | 
				
			||||||
                            <SendButton
 | 
					 | 
				
			||||||
                                key="controls_send"
 | 
					 | 
				
			||||||
                                onClick={this.sendMessage}
 | 
					 | 
				
			||||||
                                title={this.state.haveRecording ? _t("Send voice message") : undefined}
 | 
					 | 
				
			||||||
                            />
 | 
					 | 
				
			||||||
                        ) }
 | 
					 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
 | 
				
			|||||||
limitations under the License.
 | 
					limitations under the License.
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
import React from 'react';
 | 
					import React from 'react';
 | 
				
			||||||
 | 
					import classNames from 'classnames';
 | 
				
			||||||
import { Room } from 'matrix-js-sdk/src/models/room';
 | 
					import { Room } from 'matrix-js-sdk/src/models/room';
 | 
				
			||||||
import { _t, _td } from '../../../languageHandler';
 | 
					import { _t, _td } from '../../../languageHandler';
 | 
				
			||||||
import AppTile from '../elements/AppTile';
 | 
					import AppTile from '../elements/AppTile';
 | 
				
			||||||
@@ -26,6 +27,7 @@ import { IntegrationManagers } from "../../../integrations/IntegrationManagers";
 | 
				
			|||||||
import SettingsStore from "../../../settings/SettingsStore";
 | 
					import SettingsStore from "../../../settings/SettingsStore";
 | 
				
			||||||
import { ChevronFace, ContextMenu } from "../../structures/ContextMenu";
 | 
					import { ChevronFace, ContextMenu } from "../../structures/ContextMenu";
 | 
				
			||||||
import { WidgetType } from "../../../widgets/WidgetType";
 | 
					import { WidgetType } from "../../../widgets/WidgetType";
 | 
				
			||||||
 | 
					import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
 | 
				
			||||||
import { Action } from "../../../dispatcher/actions";
 | 
					import { Action } from "../../../dispatcher/actions";
 | 
				
			||||||
import { WidgetMessagingStore } from "../../../stores/widgets/WidgetMessagingStore";
 | 
					import { WidgetMessagingStore } from "../../../stores/widgets/WidgetMessagingStore";
 | 
				
			||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
 | 
					import { replaceableComponent } from "../../../utils/replaceableComponent";
 | 
				
			||||||
@@ -42,12 +44,10 @@ const PERSISTED_ELEMENT_KEY = "stickerPicker";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
interface IProps {
 | 
					interface IProps {
 | 
				
			||||||
    room: Room;
 | 
					    room: Room;
 | 
				
			||||||
    showStickers: boolean;
 | 
					 | 
				
			||||||
    menuPosition?: any;
 | 
					 | 
				
			||||||
    setShowStickers: (showStickers: boolean) => void;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IState {
 | 
					interface IState {
 | 
				
			||||||
 | 
					    showStickers: boolean;
 | 
				
			||||||
    imError: string;
 | 
					    imError: string;
 | 
				
			||||||
    stickerpickerX: number;
 | 
					    stickerpickerX: number;
 | 
				
			||||||
    stickerpickerY: number;
 | 
					    stickerpickerY: number;
 | 
				
			||||||
@@ -72,6 +72,7 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
 | 
				
			|||||||
    constructor(props: IProps) {
 | 
					    constructor(props: IProps) {
 | 
				
			||||||
        super(props);
 | 
					        super(props);
 | 
				
			||||||
        this.state = {
 | 
					        this.state = {
 | 
				
			||||||
 | 
					            showStickers: false,
 | 
				
			||||||
            imError: null,
 | 
					            imError: null,
 | 
				
			||||||
            stickerpickerX: null,
 | 
					            stickerpickerX: null,
 | 
				
			||||||
            stickerpickerY: null,
 | 
					            stickerpickerY: null,
 | 
				
			||||||
@@ -113,7 +114,7 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
 | 
				
			|||||||
            console.warn('No widget ID specified, not disabling assets');
 | 
					            console.warn('No widget ID specified, not disabling assets');
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.props.setShowStickers(false);
 | 
					        this.setState({ showStickers: false });
 | 
				
			||||||
        WidgetUtils.removeStickerpickerWidgets().then(() => {
 | 
					        WidgetUtils.removeStickerpickerWidgets().then(() => {
 | 
				
			||||||
            this.forceUpdate();
 | 
					            this.forceUpdate();
 | 
				
			||||||
        }).catch((e) => {
 | 
					        }).catch((e) => {
 | 
				
			||||||
@@ -145,15 +146,15 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public componentDidUpdate(prevProps: IProps, prevState: IState): void {
 | 
					    public componentDidUpdate(prevProps: IProps, prevState: IState): void {
 | 
				
			||||||
        this.sendVisibilityToWidget(this.props.showStickers);
 | 
					        this.sendVisibilityToWidget(this.state.showStickers);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private imError(errorMsg: string, e: Error): void {
 | 
					    private imError(errorMsg: string, e: Error): void {
 | 
				
			||||||
        console.error(errorMsg, e);
 | 
					        console.error(errorMsg, e);
 | 
				
			||||||
        this.setState({
 | 
					        this.setState({
 | 
				
			||||||
 | 
					            showStickers: false,
 | 
				
			||||||
            imError: _t(errorMsg),
 | 
					            imError: _t(errorMsg),
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        this.props.setShowStickers(false);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private updateWidget = (): void => {
 | 
					    private updateWidget = (): void => {
 | 
				
			||||||
@@ -193,12 +194,12 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
 | 
				
			|||||||
                this.forceUpdate();
 | 
					                this.forceUpdate();
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
            case "stickerpicker_close":
 | 
					            case "stickerpicker_close":
 | 
				
			||||||
                this.props.setShowStickers(false);
 | 
					                this.setState({ showStickers: false });
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
            case Action.AfterRightPanelPhaseChange:
 | 
					            case Action.AfterRightPanelPhaseChange:
 | 
				
			||||||
            case "show_left_panel":
 | 
					            case "show_left_panel":
 | 
				
			||||||
            case "hide_left_panel":
 | 
					            case "hide_left_panel":
 | 
				
			||||||
                this.props.setShowStickers(false);
 | 
					                this.setState({ showStickers: false });
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
@@ -337,8 +338,8 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        const y = (buttonRect.top + (buttonRect.height / 2) + window.pageYOffset) - 19;
 | 
					        const y = (buttonRect.top + (buttonRect.height / 2) + window.pageYOffset) - 19;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.props.setShowStickers(true);
 | 
					 | 
				
			||||||
        this.setState({
 | 
					        this.setState({
 | 
				
			||||||
 | 
					            showStickers: true,
 | 
				
			||||||
            stickerpickerX: x,
 | 
					            stickerpickerX: x,
 | 
				
			||||||
            stickerpickerY: y,
 | 
					            stickerpickerY: y,
 | 
				
			||||||
            stickerpickerChevronOffset,
 | 
					            stickerpickerChevronOffset,
 | 
				
			||||||
@@ -350,8 +351,8 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
 | 
				
			|||||||
     * @param  {Event} ev Event that triggered the function call
 | 
					     * @param  {Event} ev Event that triggered the function call
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    private onHideStickersClick = (ev: React.MouseEvent): void => {
 | 
					    private onHideStickersClick = (ev: React.MouseEvent): void => {
 | 
				
			||||||
        if (this.props.showStickers) {
 | 
					        if (this.state.showStickers) {
 | 
				
			||||||
            this.props.setShowStickers(false);
 | 
					            this.setState({ showStickers: false });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -359,8 +360,8 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
 | 
				
			|||||||
     * Called when the window is resized
 | 
					     * Called when the window is resized
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    private onResize = (): void => {
 | 
					    private onResize = (): void => {
 | 
				
			||||||
        if (this.props.showStickers) {
 | 
					        if (this.state.showStickers) {
 | 
				
			||||||
            this.props.setShowStickers(false);
 | 
					            this.setState({ showStickers: false });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -368,8 +369,8 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
 | 
				
			|||||||
     * The stickers picker was hidden
 | 
					     * The stickers picker was hidden
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    private onFinished = (): void => {
 | 
					    private onFinished = (): void => {
 | 
				
			||||||
        if (this.props.showStickers) {
 | 
					        if (this.state.showStickers) {
 | 
				
			||||||
            this.props.setShowStickers(false);
 | 
					            this.setState({ showStickers: false });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -394,9 +395,26 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public render(): JSX.Element {
 | 
					    public render(): JSX.Element {
 | 
				
			||||||
        if (!this.props.showStickers) return null;
 | 
					        let stickerPicker;
 | 
				
			||||||
 | 
					        let stickersButton;
 | 
				
			||||||
 | 
					        const className = classNames(
 | 
				
			||||||
 | 
					            "mx_MessageComposer_button",
 | 
				
			||||||
 | 
					            "mx_MessageComposer_stickers",
 | 
				
			||||||
 | 
					            "mx_Stickers_hideStickers",
 | 
				
			||||||
 | 
					            "mx_MessageComposer_button_highlight",
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        if (this.state.showStickers) {
 | 
				
			||||||
 | 
					            // Show hide-stickers button
 | 
				
			||||||
 | 
					            stickersButton =
 | 
				
			||||||
 | 
					                <AccessibleButton
 | 
				
			||||||
 | 
					                    id='stickersButton'
 | 
				
			||||||
 | 
					                    key="controls_hide_stickers"
 | 
				
			||||||
 | 
					                    className={className}
 | 
				
			||||||
 | 
					                    onClick={this.onHideStickersClick}
 | 
				
			||||||
 | 
					                    title={_t("Hide Stickers")}
 | 
				
			||||||
 | 
					                />;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return <ContextMenu
 | 
					            stickerPicker = <ContextMenu
 | 
				
			||||||
                chevronOffset={this.state.stickerpickerChevronOffset}
 | 
					                chevronOffset={this.state.stickerpickerChevronOffset}
 | 
				
			||||||
                chevronFace={ChevronFace.Bottom}
 | 
					                chevronFace={ChevronFace.Bottom}
 | 
				
			||||||
                left={this.state.stickerpickerX}
 | 
					                left={this.state.stickerpickerX}
 | 
				
			||||||
@@ -408,9 +426,23 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
 | 
				
			|||||||
                menuPaddingLeft={0}
 | 
					                menuPaddingLeft={0}
 | 
				
			||||||
                menuPaddingRight={0}
 | 
					                menuPaddingRight={0}
 | 
				
			||||||
                zIndex={STICKERPICKER_Z_INDEX}
 | 
					                zIndex={STICKERPICKER_Z_INDEX}
 | 
				
			||||||
            {...this.props.menuPosition}
 | 
					 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
                <GenericElementContextMenu element={this.getStickerpickerContent()} onResize={this.onFinished} />
 | 
					                <GenericElementContextMenu element={this.getStickerpickerContent()} onResize={this.onFinished} />
 | 
				
			||||||
            </ContextMenu>;
 | 
					            </ContextMenu>;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            // Show show-stickers button
 | 
				
			||||||
 | 
					            stickersButton =
 | 
				
			||||||
 | 
					                <AccessibleTooltipButton
 | 
				
			||||||
 | 
					                    id='stickersButton'
 | 
				
			||||||
 | 
					                    key="controls_show_stickers"
 | 
				
			||||||
 | 
					                    className="mx_MessageComposer_button mx_MessageComposer_stickers"
 | 
				
			||||||
 | 
					                    onClick={this.onShowStickersClick}
 | 
				
			||||||
 | 
					                    title={_t("Show Stickers")}
 | 
				
			||||||
 | 
					                />;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return <React.Fragment>
 | 
				
			||||||
 | 
					            { stickersButton }
 | 
				
			||||||
 | 
					            { stickerPicker }
 | 
				
			||||||
 | 
					        </React.Fragment>;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,6 +20,7 @@ import React, { ReactNode } from "react";
 | 
				
			|||||||
import { IUpload, RecordingState, VoiceRecording } from "../../../audio/VoiceRecording";
 | 
					import { IUpload, RecordingState, VoiceRecording } from "../../../audio/VoiceRecording";
 | 
				
			||||||
import { Room } from "matrix-js-sdk/src/models/room";
 | 
					import { Room } from "matrix-js-sdk/src/models/room";
 | 
				
			||||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
 | 
					import { MatrixClientPeg } from "../../../MatrixClientPeg";
 | 
				
			||||||
 | 
					import classNames from "classnames";
 | 
				
			||||||
import LiveRecordingWaveform from "../audio_messages/LiveRecordingWaveform";
 | 
					import LiveRecordingWaveform from "../audio_messages/LiveRecordingWaveform";
 | 
				
			||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
 | 
					import { replaceableComponent } from "../../../utils/replaceableComponent";
 | 
				
			||||||
import LiveRecordingClock from "../audio_messages/LiveRecordingClock";
 | 
					import LiveRecordingClock from "../audio_messages/LiveRecordingClock";
 | 
				
			||||||
@@ -136,7 +137,7 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
 | 
				
			|||||||
        await this.disposeRecording();
 | 
					        await this.disposeRecording();
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public onRecordStartEndClick = async () => {
 | 
					    private onRecordStartEndClick = async () => {
 | 
				
			||||||
        if (this.state.recorder) {
 | 
					        if (this.state.recorder) {
 | 
				
			||||||
            await this.state.recorder.stop();
 | 
					            await this.state.recorder.stop();
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
@@ -214,23 +215,27 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public render(): ReactNode {
 | 
					    public render(): ReactNode {
 | 
				
			||||||
        if (!this.state.recordingPhase) return null;
 | 
					        let stopOrRecordBtn;
 | 
				
			||||||
 | 
					 | 
				
			||||||
        let stopBtn;
 | 
					 | 
				
			||||||
        let deleteButton;
 | 
					        let deleteButton;
 | 
				
			||||||
        if (this.state.recordingPhase === RecordingState.Started) {
 | 
					        if (!this.state.recordingPhase || this.state.recordingPhase === RecordingState.Started) {
 | 
				
			||||||
 | 
					            const classes = classNames({
 | 
				
			||||||
 | 
					                'mx_MessageComposer_button': !this.state.recorder,
 | 
				
			||||||
 | 
					                'mx_MessageComposer_voiceMessage': !this.state.recorder,
 | 
				
			||||||
 | 
					                'mx_VoiceRecordComposerTile_stop': this.state.recorder?.isRecording,
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let tooltip = _t("Send voice message");
 | 
					            let tooltip = _t("Send voice message");
 | 
				
			||||||
            if (!!this.state.recorder) {
 | 
					            if (!!this.state.recorder) {
 | 
				
			||||||
                tooltip = _t("Stop recording");
 | 
					                tooltip = _t("Stop recording");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            stopBtn = <AccessibleTooltipButton
 | 
					            stopOrRecordBtn = <AccessibleTooltipButton
 | 
				
			||||||
                className="mx_VoiceRecordComposerTile_stop"
 | 
					                className={classes}
 | 
				
			||||||
                onClick={this.onRecordStartEndClick}
 | 
					                onClick={this.onRecordStartEndClick}
 | 
				
			||||||
                title={tooltip}
 | 
					                title={tooltip}
 | 
				
			||||||
            />;
 | 
					            />;
 | 
				
			||||||
            if (this.state.recorder && !this.state.recorder?.isRecording) {
 | 
					            if (this.state.recorder && !this.state.recorder?.isRecording) {
 | 
				
			||||||
                stopBtn = null;
 | 
					                stopOrRecordBtn = null;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -259,10 +264,13 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
 | 
				
			|||||||
            </span>;
 | 
					            </span>;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // The record button (mic icon) is meant to be on the right edge, but we also want the
 | 
				
			||||||
 | 
					        // stop button to be left of the waveform area. Luckily, none of the surrounding UI is
 | 
				
			||||||
 | 
					        // rendered when we're not recording, so the record button ends up in the correct spot.
 | 
				
			||||||
        return (<>
 | 
					        return (<>
 | 
				
			||||||
            { uploadIndicator }
 | 
					            { uploadIndicator }
 | 
				
			||||||
            { deleteButton }
 | 
					            { deleteButton }
 | 
				
			||||||
            { stopBtn }
 | 
					            { stopOrRecordBtn }
 | 
				
			||||||
            { this.renderWaveformArea() }
 | 
					            { this.renderWaveformArea() }
 | 
				
			||||||
        </>);
 | 
					        </>);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1564,12 +1564,7 @@
 | 
				
			|||||||
    "Send a reply…": "Send a reply…",
 | 
					    "Send a reply…": "Send a reply…",
 | 
				
			||||||
    "Send an encrypted message…": "Send an encrypted message…",
 | 
					    "Send an encrypted message…": "Send an encrypted message…",
 | 
				
			||||||
    "Send a message…": "Send a message…",
 | 
					    "Send a message…": "Send a message…",
 | 
				
			||||||
    "Send File": "Send File",
 | 
					 | 
				
			||||||
    "Show Emojis": "Show Emojis",
 | 
					 | 
				
			||||||
    "Show Stickers": "Show Stickers",
 | 
					 | 
				
			||||||
    "Hide Stickers": "Hide Stickers",
 | 
					 | 
				
			||||||
    "Send voice message": "Send voice message",
 | 
					    "Send voice message": "Send voice message",
 | 
				
			||||||
    "Composer menu": "Composer menu",
 | 
					 | 
				
			||||||
    "The conversation continues here.": "The conversation continues here.",
 | 
					    "The conversation continues here.": "The conversation continues here.",
 | 
				
			||||||
    "This room has been replaced and is no longer active.": "This room has been replaced and is no longer active.",
 | 
					    "This room has been replaced and is no longer active.": "This room has been replaced and is no longer active.",
 | 
				
			||||||
    "You do not have permission to post to this room": "You do not have permission to post to this room",
 | 
					    "You do not have permission to post to this room": "You do not have permission to post to this room",
 | 
				
			||||||
@@ -1730,6 +1725,8 @@
 | 
				
			|||||||
    "You don't currently have any stickerpacks enabled": "You don't currently have any stickerpacks enabled",
 | 
					    "You don't currently have any stickerpacks enabled": "You don't currently have any stickerpacks enabled",
 | 
				
			||||||
    "Add some now": "Add some now",
 | 
					    "Add some now": "Add some now",
 | 
				
			||||||
    "Stickerpack": "Stickerpack",
 | 
					    "Stickerpack": "Stickerpack",
 | 
				
			||||||
 | 
					    "Hide Stickers": "Hide Stickers",
 | 
				
			||||||
 | 
					    "Show Stickers": "Show Stickers",
 | 
				
			||||||
    "Failed to revoke invite": "Failed to revoke invite",
 | 
					    "Failed to revoke invite": "Failed to revoke invite",
 | 
				
			||||||
    "Could not revoke the invite. The server may be experiencing a temporary problem or you do not have sufficient permissions to revoke the invite.": "Could not revoke the invite. The server may be experiencing a temporary problem or you do not have sufficient permissions to revoke the invite.",
 | 
					    "Could not revoke the invite. The server may be experiencing a temporary problem or you do not have sufficient permissions to revoke the invite.": "Could not revoke the invite. The server may be experiencing a temporary problem or you do not have sufficient permissions to revoke the invite.",
 | 
				
			||||||
    "Admin Tools": "Admin Tools",
 | 
					    "Admin Tools": "Admin Tools",
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user