Merge pull request #5858 from matrix-org/t3chguy/fix/12740

This commit is contained in:
Michael Telatynski 2021-06-18 08:22:21 +01:00 committed by GitHub
commit d9caa3533b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 243 additions and 151 deletions

View file

@ -39,6 +39,7 @@ import {haveTileForEvent} from "../views/rooms/EventTile";
import { UIFeature } from "../../settings/UIFeature";
import { replaceableComponent } from "../../utils/replaceableComponent";
import { arrayFastClone } from "../../utils/arrays";
import { Action } from "../../dispatcher/actions";
const PAGINATE_SIZE = 20;
const INITIAL_SIZE = 20;
@ -439,10 +440,12 @@ class TimelinePanel extends React.Component {
};
onAction = payload => {
if (payload.action === 'ignore_state_changed') {
switch (payload.action) {
case "ignore_state_changed":
this.forceUpdate();
}
if (payload.action === "edit_event") {
break;
case "edit_event": {
const editState = payload.event ? new EditorStateTransfer(payload.event) : null;
this.setState({editState}, () => {
if (payload.event && this._messagePanel.current) {
@ -451,9 +454,28 @@ class TimelinePanel extends React.Component {
);
}
});
break;
}
if (payload.action === "scroll_to_bottom") {
case Action.ComposerInsert: {
// re-dispatch to the correct composer
if (this.state.editState) {
dis.dispatch({
...payload,
action: "edit_composer_insert",
});
} else {
dis.dispatch({
...payload,
action: "send_composer_insert",
});
}
break;
}
case "scroll_to_bottom":
this.jumpToLiveTimeline();
break;
}
};

View file

@ -33,6 +33,8 @@ import { EventType } from "matrix-js-sdk/src/@types/event";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { ReadPinsEventId } from "../right_panel/PinnedMessagesCard";
import ForwardDialog from "../dialogs/ForwardDialog";
import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
import { Action } from "../../../dispatcher/actions";
export function canCancel(eventStatus) {
return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT;
@ -199,8 +201,8 @@ export default class MessageContextMenu extends React.Component {
};
onQuoteClick = () => {
dis.dispatch({
action: 'quote',
dis.dispatch<ComposerInsertPayload>({
action: Action.ComposerInsert,
event: this.props.mxEvent,
});
this.closeMenu();

View file

@ -37,6 +37,8 @@ import {copyPlaintext} from "../../../utils/strings";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import UIStore from "../../../stores/UIStore";
import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
import { Action } from "../../../dispatcher/actions";
@replaceableComponent("views.messages.TextualBody")
export default class TextualBody extends React.Component {
@ -390,9 +392,9 @@ export default class TextualBody extends React.Component {
onEmoteSenderClick = event => {
const mxEvent = this.props.mxEvent;
dis.dispatch({
action: 'insert_mention',
user_id: mxEvent.getSender(),
dis.dispatch<ComposerInsertPayload>({
action: Action.ComposerInsert,
userId: mxEvent.getSender(),
});
};

View file

@ -68,6 +68,7 @@ import RoomAvatar from "../avatars/RoomAvatar";
import RoomName from "../elements/RoomName";
import { mediaFromMxc } from "../../../customisations/Media";
import UIStore from "../../../stores/UIStore";
import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
export interface IDevice {
deviceId: string;
@ -368,9 +369,9 @@ const UserOptionsSection: React.FC<{
};
const onInsertPillButton = function() {
dis.dispatch({
action: 'insert_mention',
user_id: member.userId,
dis.dispatch<ComposerInsertPayload>({
action: Action.ComposerInsert,
userId: member.userId,
});
};

View file

@ -18,6 +18,7 @@ limitations under the License.
import classNames from 'classnames';
import React, { createRef, ClipboardEvent } from 'react';
import { Room } from 'matrix-js-sdk/src/models/room';
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
import EMOTICON_REGEX from 'emojibase-regex/emoticon';
import EditorModel from '../../../editor/model';
@ -32,7 +33,7 @@ import {
import { getCaretOffsetAndText, getRangeForSelection } from '../../../editor/dom';
import Autocomplete, { generateCompletionDomId } from '../rooms/Autocomplete';
import { getAutoCompleteCreator } from '../../../editor/parts';
import {parsePlainTextMessage} from '../../../editor/deserialize';
import { parseEvent, parsePlainTextMessage } from '../../../editor/deserialize';
import { renderModel } from '../../../editor/render';
import TypingStore from "../../../stores/TypingStore";
import SettingsStore from "../../../settings/SettingsStore";
@ -716,4 +717,48 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
focus() {
this.editorRef.current.focus();
}
public insertMention(userId: string) {
const {model} = this.props;
const {partCreator} = model;
const member = this.props.room.getMember(userId);
const displayName = member ?
member.rawDisplayName : userId;
const caret = this.getCaret();
const position = model.positionForOffset(caret.offset, caret.atNodeEnd);
// Insert suffix only if the caret is at the start of the composer
const parts = partCreator.createMentionParts(caret.offset === 0, displayName, userId);
model.transform(() => {
const addedLen = model.insert(parts, position);
return model.positionForOffset(caret.offset + addedLen, true);
});
// refocus on composer, as we just clicked "Mention"
this.focus();
}
public insertQuotedMessage(event: MatrixEvent) {
const {model} = this.props;
const {partCreator} = model;
const quoteParts = parseEvent(event, partCreator, {isQuotedMessage: true});
// add two newlines
quoteParts.push(partCreator.newline());
quoteParts.push(partCreator.newline());
model.transform(() => {
const addedLen = model.insert(quoteParts, model.positionForOffset(0));
return model.positionForOffset(addedLen, true);
});
// refocus on composer, as we just clicked "Quote"
this.focus();
}
public insertPlaintext(text: string) {
const {model} = this.props;
const {partCreator} = model;
const caret = this.getCaret();
const position = model.positionForOffset(caret.offset, caret.atNodeEnd);
model.transform(() => {
const addedLen = model.insert([partCreator.plain(text)], position);
return model.positionForOffset(caret.offset + addedLen, true);
});
}
}

View file

@ -124,6 +124,7 @@ export default class EditMessageComposer extends React.Component {
};
this._createEditorModel();
window.addEventListener("beforeunload", this._saveStoredEditorState);
this.dispatcherRef = dis.register(this.onAction);
}
_setEditorRef = ref => {
@ -399,6 +400,7 @@ export default class EditMessageComposer extends React.Component {
if (this._shouldSaveStoredEditorState) {
this._saveStoredEditorState();
}
dis.unregister(this.dispatcherRef);
}
_createEditorModel() {
@ -443,6 +445,18 @@ export default class EditMessageComposer extends React.Component {
});
};
onAction = payload => {
if (payload.action === "edit_composer_insert" && this._editorRef) {
if (payload.userId) {
this._editorRef.insertMention(payload.userId);
} else if (payload.event) {
this._editorRef.insertQuotedMessage(payload.event);
} else if (payload.text) {
this._editorRef.insertPlaintext(payload.text);
}
}
};
render() {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
return (<div className={classNames("mx_EditMessageComposer", this.props.className)} onKeyDown={this._onKeyDown}>

View file

@ -46,6 +46,8 @@ import { EditorStateTransfer } from "../../../utils/EditorStateTransfer";
import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
import {StaticNotificationState} from "../../../stores/notifications/StaticNotificationState";
import NotificationBadge from "./NotificationBadge";
import {ComposerInsertPayload} from "../../../dispatcher/payloads/ComposerInsertPayload";
import { Action } from '../../../dispatcher/actions';
const eventTileTypes = {
[EventType.RoomMessage]: 'messages.MessageEvent',
@ -727,9 +729,9 @@ export default class EventTile extends React.Component<IProps, IState> {
onSenderProfileClick = event => {
const mxEvent = this.props.mxEvent;
dis.dispatch({
action: 'insert_mention',
user_id: mxEvent.getSender(),
dis.dispatch<ComposerInsertPayload>({
action: Action.ComposerInsert,
userId: mxEvent.getSender(),
});
};

View file

@ -41,6 +41,8 @@ import Tooltip, {Alignment} from "../elements/Tooltip";
import ResizeNotifier from "../../../utils/ResizeNotifier";
import { E2EStatus } from '../../../utils/ShieldUtils';
import SendMessageComposer from "./SendMessageComposer";
import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
import { Action } from "../../../dispatcher/actions";
interface IComposerAvatarProps {
me: object;
@ -316,10 +318,10 @@ export default class MessageComposer extends React.Component<IProps, IState> {
}
}
addEmoji(emoji) {
dis.dispatch({
action: "insert_emoji",
emoji,
addEmoji(emoji: string) {
dis.dispatch<ComposerInsertPayload>({
action: Action.ComposerInsert,
text: emoji,
});
}

View file

@ -30,7 +30,6 @@ import {
import { CommandPartCreator } from '../../../editor/parts';
import BasicMessageComposer from "./BasicMessageComposer";
import ReplyThread from "../elements/ReplyThread";
import {parseEvent} from '../../../editor/deserialize';
import { findEditableEvent } from '../../../utils/EventUtils';
import SendHistoryManager from "../../../SendHistoryManager";
import { CommandCategories, getCommand } from '../../../SlashCommands';
@ -486,62 +485,18 @@ export default class SendMessageComposer extends React.Component {
case Action.FocusComposer:
this._editorRef && this._editorRef.focus();
break;
case 'insert_mention':
this._insertMention(payload.user_id);
break;
case 'quote':
this._insertQuotedMessage(payload.event);
break;
case 'insert_emoji':
this._insertEmoji(payload.emoji);
case "send_composer_insert":
if (payload.userId) {
this._editorRef && this._editorRef.insertMention(payload.userId);
} else if (payload.event) {
this._editorRef && this._editorRef.insertQuotedMessage(payload.event);
} else if (payload.text) {
this._editorRef && this._editorRef.insertPlaintext(payload.text);
}
break;
}
};
_insertMention(userId) {
const {model} = this;
const {partCreator} = model;
const member = this.props.room.getMember(userId);
const displayName = member ?
member.rawDisplayName : userId;
const caret = this._editorRef.getCaret();
const position = model.positionForOffset(caret.offset, caret.atNodeEnd);
// Insert suffix only if the caret is at the start of the composer
const parts = partCreator.createMentionParts(caret.offset === 0, displayName, userId);
model.transform(() => {
const addedLen = model.insert(parts, position);
return model.positionForOffset(caret.offset + addedLen, true);
});
// refocus on composer, as we just clicked "Mention"
this._editorRef && this._editorRef.focus();
}
_insertQuotedMessage(event) {
const {model} = this;
const {partCreator} = model;
const quoteParts = parseEvent(event, partCreator, {isQuotedMessage: true});
// add two newlines
quoteParts.push(partCreator.newline());
quoteParts.push(partCreator.newline());
model.transform(() => {
const addedLen = model.insert(quoteParts, model.positionForOffset(0));
return model.positionForOffset(addedLen, true);
});
// refocus on composer, as we just clicked "Quote"
this._editorRef && this._editorRef.focus();
}
_insertEmoji = (emoji) => {
const {model} = this;
const {partCreator} = model;
const caret = this._editorRef.getCaret();
const position = model.positionForOffset(caret.offset, caret.atNodeEnd);
model.transform(() => {
const addedLen = model.insert([partCreator.plain(emoji)], position);
return model.positionForOffset(caret.offset + addedLen, true);
});
};
_onPaste = (event) => {
const {clipboardData} = event;
// Prioritize text on the clipboard over files as Office on macOS puts a bitmap

View file

@ -159,4 +159,9 @@ export enum Action {
* Fired when joining a room failed
*/
JoinRoomError = "join_room_error",
/**
* Inserts content into the active composer. Should be used with ComposerInsertPayload
*/
ComposerInsert = "composer_insert",
}

View file

@ -0,0 +1,42 @@
/*
Copyright 2021 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 { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { ActionPayload } from "../payloads";
import { Action } from "../actions";
interface IBaseComposerInsertPayload extends ActionPayload {
action: Action.ComposerInsert,
}
interface IComposerInsertMentionPayload extends IBaseComposerInsertPayload {
userId: string;
}
interface IComposerInsertQuotePayload extends IBaseComposerInsertPayload {
event: MatrixEvent;
}
interface IComposerInsertPlaintextPayload extends IBaseComposerInsertPayload {
text: string;
}
export type ComposerInsertPayload =
IComposerInsertMentionPayload |
IComposerInsertQuotePayload |
IComposerInsertPlaintextPayload;

View file

@ -390,7 +390,7 @@ export default class EditorModel {
return addLen;
}
positionForOffset(totalOffset: number, atPartEnd: boolean) {
positionForOffset(totalOffset: number, atPartEnd = false) {
let currentOffset = 0;
const index = this._parts.findIndex(part => {
const partLen = part.text.length;