Apply the same to quoting & inserting of emoji then consolidate

This commit is contained in:
Michael Telatynski 2021-04-13 15:09:37 +01:00
parent ace3a62bac
commit 5f59e39958
12 changed files with 105 additions and 50 deletions

View file

@ -467,16 +467,17 @@ class TimelinePanel extends React.Component {
break;
}
case "insert_mention": {
case "composer_insert": {
// re-dispatch to the correct composer
if (this.state.editState) {
dis.dispatch({
...payload,
action: "insert_mention_edit_composer",
action: "edit_composer_insert",
});
} else {
dis.dispatch({
...payload,
action: "insert_mention_send_composer",
action: "send_composer_insert",
});
}
break;

View file

@ -233,7 +233,7 @@ export default class MessageContextMenu extends React.Component {
onQuoteClick = () => {
dis.dispatch({
action: 'quote',
action: "composer_insert",
event: this.props.mxEvent,
});
this.closeMenu();

View file

@ -390,8 +390,8 @@ export default class TextualBody extends React.Component {
onEmoteSenderClick = event => {
const mxEvent = this.props.mxEvent;
dis.dispatch({
action: 'insert_mention',
user_id: mxEvent.getSender(),
action: "composer_insert",
userId: mxEvent.getSender(),
});
};

View file

@ -360,8 +360,8 @@ const UserOptionsSection: React.FC<{
const onInsertPillButton = function() {
dis.dispatch({
action: 'insert_mention',
user_id: member.userId,
action: "composer_insert",
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";
@ -732,4 +733,30 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
// 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

@ -281,8 +281,14 @@ export default class EditMessageComposer extends React.Component {
};
onAction = payload => {
if (payload.action === "insert_mention_edit_composer" && this._editorRef) {
this._editorRef.insertMention(payload.user_id);
if (payload.action === "edit_composer_insert" && this._editorRef) {
if (payload.user_id) {
this._editorRef.insertMention(payload.userId);
} else if (payload.event) {
this._editorRef.insertQuotedMessage(payload.event);
} else if (payload.text) {
this._editorRef.insertPlaintext(payload.text);
}
}
};

View file

@ -644,8 +644,8 @@ export default class EventTile extends React.Component {
onSenderProfileClick = event => {
const mxEvent = this.props.mxEvent;
dis.dispatch({
action: 'insert_mention',
user_id: mxEvent.getSender(),
action: "composer_insert",
userId: mxEvent.getSender(),
});
};

View file

@ -312,8 +312,8 @@ export default class MessageComposer extends React.Component {
addEmoji(emoji) {
dis.dispatch({
action: "insert_emoji",
emoji,
action: "composer_insert",
text: emoji,
});
}

View file

@ -482,44 +482,18 @@ export default class SendMessageComposer extends React.Component {
case Action.FocusComposer:
this._editorRef && this._editorRef.focus();
break;
case 'insert_mention_send_composer':
this._editorRef && this._editorRef.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.emoji);
}
break;
}
};
_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

@ -138,4 +138,9 @@ export enum Action {
* Fired when an upload is cancelled by the user. Should be used with UploadCanceledPayload.
*/
UploadCanceled = "upload_canceled",
/**
* 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;