mirror of https://github.com/vector-im/riot-web
Merge pull request #5858 from matrix-org/t3chguy/fix/12740
commit
d9caa3533b
src
components
structures
views
context_menus
messages
right_panel
dispatcher
editor
|
@ -18,14 +18,14 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
import {LayoutPropType} from "../../settings/Layout";
|
||||
import React, {createRef} from 'react';
|
||||
import { LayoutPropType } from "../../settings/Layout";
|
||||
import React, { createRef } from 'react';
|
||||
import ReactDOM from "react-dom";
|
||||
import PropTypes from 'prop-types';
|
||||
import {EventTimeline} from "matrix-js-sdk/src/models/event-timeline";
|
||||
import {TimelineWindow} from "matrix-js-sdk/src/timeline-window";
|
||||
import { EventTimeline } from "matrix-js-sdk/src/models/event-timeline";
|
||||
import { TimelineWindow } from "matrix-js-sdk/src/timeline-window";
|
||||
import { _t } from '../../languageHandler';
|
||||
import {MatrixClientPeg} from "../../MatrixClientPeg";
|
||||
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||
import RoomContext from "../../contexts/RoomContext";
|
||||
import UserActivity from "../../UserActivity";
|
||||
import Modal from "../../Modal";
|
||||
|
@ -35,10 +35,11 @@ import { Key } from '../../Keyboard';
|
|||
import Timer from '../../utils/Timer';
|
||||
import shouldHideEvent from '../../shouldHideEvent';
|
||||
import EditorStateTransfer from '../../utils/EditorStateTransfer';
|
||||
import {haveTileForEvent} from "../views/rooms/EventTile";
|
||||
import {UIFeature} from "../../settings/UIFeature";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
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,21 +440,42 @@ class TimelinePanel extends React.Component {
|
|||
};
|
||||
|
||||
onAction = payload => {
|
||||
if (payload.action === 'ignore_state_changed') {
|
||||
this.forceUpdate();
|
||||
}
|
||||
if (payload.action === "edit_event") {
|
||||
const editState = payload.event ? new EditorStateTransfer(payload.event) : null;
|
||||
this.setState({editState}, () => {
|
||||
if (payload.event && this._messagePanel.current) {
|
||||
this._messagePanel.current.scrollToEventIfNeeded(
|
||||
payload.event.getId(),
|
||||
);
|
||||
switch (payload.action) {
|
||||
case "ignore_state_changed":
|
||||
this.forceUpdate();
|
||||
break;
|
||||
|
||||
case "edit_event": {
|
||||
const editState = payload.event ? new EditorStateTransfer(payload.event) : null;
|
||||
this.setState({editState}, () => {
|
||||
if (payload.event && this._messagePanel.current) {
|
||||
this._messagePanel.current.scrollToEventIfNeeded(
|
||||
payload.event.getId(),
|
||||
);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
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",
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
if (payload.action === "scroll_to_bottom") {
|
||||
this.jumpToLiveTimeline();
|
||||
break;
|
||||
}
|
||||
|
||||
case "scroll_to_bottom":
|
||||
this.jumpToLiveTimeline();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -16,12 +16,12 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, {createRef} from 'react';
|
||||
import React, { createRef } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import highlight from 'highlight.js';
|
||||
import * as HtmlUtils from '../../../HtmlUtils';
|
||||
import {formatDate} from '../../../DateUtils';
|
||||
import { formatDate } from '../../../DateUtils';
|
||||
import * as sdk from '../../../index';
|
||||
import Modal from '../../../Modal';
|
||||
import dis from '../../../dispatcher/dispatcher';
|
||||
|
@ -29,14 +29,16 @@ import { _t } from '../../../languageHandler';
|
|||
import * as ContextMenu from '../../structures/ContextMenu';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import ReplyThread from "../elements/ReplyThread";
|
||||
import {pillifyLinks, unmountPills} from '../../../utils/pillify';
|
||||
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
|
||||
import {isPermalinkHost} from "../../../utils/permalinks/Permalinks";
|
||||
import {toRightOf} from "../../structures/ContextMenu";
|
||||
import {copyPlaintext} from "../../../utils/strings";
|
||||
import { pillifyLinks, unmountPills } from '../../../utils/pillify';
|
||||
import { IntegrationManagers } from "../../../integrations/IntegrationManagers";
|
||||
import { isPermalinkHost } from "../../../utils/permalinks/Permalinks";
|
||||
import { toRightOf } from "../../structures/ContextMenu";
|
||||
import { copyPlaintext } from "../../../utils/strings";
|
||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
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(),
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ import SettingsStore from "../../../settings/SettingsStore";
|
|||
import RoomViewStore from "../../../stores/RoomViewStore";
|
||||
import MultiInviter from "../../../utils/MultiInviter";
|
||||
import GroupStore from "../../../stores/GroupStore";
|
||||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import E2EIcon from "../rooms/E2EIcon";
|
||||
import { useEventEmitter } from "../../../hooks/useEventEmitter";
|
||||
import { textualPowerLevel } from '../../../Roles';
|
||||
|
@ -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,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -16,38 +16,39 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import classNames from 'classnames';
|
||||
import React, {createRef, ClipboardEvent} from 'react';
|
||||
import {Room} from 'matrix-js-sdk/src/models/room';
|
||||
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';
|
||||
import HistoryManager from '../../../editor/history';
|
||||
import {Caret, setSelection} from '../../../editor/caret';
|
||||
import { Caret, setSelection } from '../../../editor/caret';
|
||||
import {
|
||||
formatRangeAsQuote,
|
||||
formatRangeAsCode,
|
||||
toggleInlineFormat,
|
||||
replaceRangeAndMoveCaret,
|
||||
} from '../../../editor/operations';
|
||||
import {getCaretOffsetAndText, getRangeForSelection} from '../../../editor/dom';
|
||||
import Autocomplete, {generateCompletionDomId} from '../rooms/Autocomplete';
|
||||
import {getAutoCompleteCreator} from '../../../editor/parts';
|
||||
import {parsePlainTextMessage} from '../../../editor/deserialize';
|
||||
import {renderModel} from '../../../editor/render';
|
||||
import { getCaretOffsetAndText, getRangeForSelection } from '../../../editor/dom';
|
||||
import Autocomplete, { generateCompletionDomId } from '../rooms/Autocomplete';
|
||||
import { getAutoCompleteCreator } from '../../../editor/parts';
|
||||
import { parseEvent, parsePlainTextMessage } from '../../../editor/deserialize';
|
||||
import { renderModel } from '../../../editor/render';
|
||||
import TypingStore from "../../../stores/TypingStore";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import {Key} from "../../../Keyboard";
|
||||
import {EMOTICON_TO_EMOJI} from "../../../emoji";
|
||||
import {CommandCategories, CommandMap, parseCommandString} from "../../../SlashCommands";
|
||||
import { Key } from "../../../Keyboard";
|
||||
import { EMOTICON_TO_EMOJI } from "../../../emoji";
|
||||
import { CommandCategories, CommandMap, parseCommandString } from "../../../SlashCommands";
|
||||
import Range from "../../../editor/range";
|
||||
import MessageComposerFormatBar from "./MessageComposerFormatBar";
|
||||
import DocumentOffset from "../../../editor/offset";
|
||||
import {IDiff} from "../../../editor/diff";
|
||||
import { IDiff } from "../../../editor/diff";
|
||||
import AutocompleteWrapperModel from "../../../editor/autocomplete";
|
||||
import DocumentPosition from "../../../editor/position";
|
||||
import {ICompletion} from "../../../autocomplete/Autocompleter";
|
||||
import { ICompletion } from "../../../autocomplete/Autocompleter";
|
||||
import { AutocompleteAction, getKeyBindingsManager, MessageComposerAction } from '../../../KeyBindingsManager';
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
// matches emoticons which follow the start of a line or whitespace
|
||||
const REGEX_EMOTICON_WHITESPACE = new RegExp('(?:^|\\s)(' + EMOTICON_REGEX.source + ')\\s$');
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,25 +16,25 @@ limitations under the License.
|
|||
*/
|
||||
import React from 'react';
|
||||
import * as sdk from '../../../index';
|
||||
import {_t, _td} from '../../../languageHandler';
|
||||
import { _t, _td } from '../../../languageHandler';
|
||||
import PropTypes from 'prop-types';
|
||||
import dis from '../../../dispatcher/dispatcher';
|
||||
import EditorModel from '../../../editor/model';
|
||||
import {getCaretOffsetAndText} from '../../../editor/dom';
|
||||
import {htmlSerializeIfNeeded, textSerialize, containsEmote, stripEmoteCommand} from '../../../editor/serialize';
|
||||
import {findEditableEvent} from '../../../utils/EventUtils';
|
||||
import {parseEvent} from '../../../editor/deserialize';
|
||||
import {CommandPartCreator} from '../../../editor/parts';
|
||||
import { getCaretOffsetAndText } from '../../../editor/dom';
|
||||
import { htmlSerializeIfNeeded, textSerialize, containsEmote, stripEmoteCommand } from '../../../editor/serialize';
|
||||
import { findEditableEvent } from '../../../utils/EventUtils';
|
||||
import { parseEvent } from '../../../editor/deserialize';
|
||||
import { CommandPartCreator } from '../../../editor/parts';
|
||||
import EditorStateTransfer from '../../../utils/EditorStateTransfer';
|
||||
import classNames from 'classnames';
|
||||
import {EventStatus} from 'matrix-js-sdk/src/models/event';
|
||||
import { EventStatus } from 'matrix-js-sdk/src/models/event';
|
||||
import BasicMessageComposer from "./BasicMessageComposer";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import {CommandCategories, getCommand} from '../../../SlashCommands';
|
||||
import {Action} from "../../../dispatcher/actions";
|
||||
import { CommandCategories, getCommand } from '../../../SlashCommands';
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import CountlyAnalytics from "../../../CountlyAnalytics";
|
||||
import {getKeyBindingsManager, MessageComposerAction} from '../../../KeyBindingsManager';
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
import { getKeyBindingsManager, MessageComposerAction } from '../../../KeyBindingsManager';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import SendHistoryManager from '../../../SendHistoryManager';
|
||||
import Modal from '../../../Modal';
|
||||
|
||||
|
@ -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}>
|
||||
|
|
|
@ -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(),
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -16,11 +16,11 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import * as sdk from '../../../index';
|
||||
import {MatrixEvent} from "matrix-js-sdk/src/models/event";
|
||||
import {Room} from "matrix-js-sdk/src/models/room";
|
||||
import {RoomMember} from "matrix-js-sdk/src/models/room-member";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||
import dis from '../../../dispatcher/dispatcher';
|
||||
import { ActionPayload } from "../../../dispatcher/payloads";
|
||||
import Stickerpicker from './Stickerpicker';
|
||||
|
@ -28,19 +28,21 @@ import { makeRoomPermalink, RoomPermalinkCreator } from '../../../utils/permalin
|
|||
import ContentMessages from '../../../ContentMessages';
|
||||
import E2EIcon from './E2EIcon';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import {aboveLeftOf, ContextMenu, ContextMenuTooltipButton, useContextMenu} from "../../structures/ContextMenu";
|
||||
import { aboveLeftOf, ContextMenu, ContextMenuTooltipButton, useContextMenu } from "../../structures/ContextMenu";
|
||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||
import ReplyPreview from "./ReplyPreview";
|
||||
import {UIFeature} from "../../../settings/UIFeature";
|
||||
import {UPDATE_EVENT} from "../../../stores/AsyncStore";
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
import { UIFeature } from "../../../settings/UIFeature";
|
||||
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import VoiceRecordComposerTile from "./VoiceRecordComposerTile";
|
||||
import {VoiceRecordingStore} from "../../../stores/VoiceRecordingStore";
|
||||
import {RecordingState} from "../../../voice/VoiceRecording";
|
||||
import Tooltip, {Alignment} from "../elements/Tooltip";
|
||||
import { VoiceRecordingStore } from "../../../stores/VoiceRecordingStore";
|
||||
import { RecordingState } from "../../../voice/VoiceRecording";
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -27,27 +27,26 @@ import {
|
|||
startsWith,
|
||||
stripPrefix,
|
||||
} from '../../../editor/serialize';
|
||||
import {CommandPartCreator} from '../../../editor/parts';
|
||||
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 { findEditableEvent } from '../../../utils/EventUtils';
|
||||
import SendHistoryManager from "../../../SendHistoryManager";
|
||||
import {CommandCategories, getCommand} from '../../../SlashCommands';
|
||||
import { CommandCategories, getCommand } from '../../../SlashCommands';
|
||||
import * as sdk from '../../../index';
|
||||
import Modal from '../../../Modal';
|
||||
import {_t, _td} from '../../../languageHandler';
|
||||
import { _t, _td } from '../../../languageHandler';
|
||||
import ContentMessages from '../../../ContentMessages';
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import RateLimitedFunc from '../../../ratelimitedfunc';
|
||||
import {Action} from "../../../dispatcher/actions";
|
||||
import {containsEmoji} from "../../../effects/utils";
|
||||
import {CHAT_EFFECTS} from '../../../effects';
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import { containsEmoji } from "../../../effects/utils";
|
||||
import { CHAT_EFFECTS } from '../../../effects';
|
||||
import CountlyAnalytics from "../../../CountlyAnalytics";
|
||||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import EMOJI_REGEX from 'emojibase-regex';
|
||||
import {getKeyBindingsManager, MessageComposerAction} from '../../../KeyBindingsManager';
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
import { getKeyBindingsManager, MessageComposerAction } from '../../../KeyBindingsManager';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import SettingsStore from '../../../settings/SettingsStore';
|
||||
|
||||
function addReplyToMessageContent(content, repliedToEvent, permalinkCreator) {
|
||||
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
@ -15,13 +15,13 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {diffAtCaret, diffDeletion, IDiff} from "./diff";
|
||||
import DocumentPosition, {IPosition} from "./position";
|
||||
import { diffAtCaret, diffDeletion, IDiff } from "./diff";
|
||||
import DocumentPosition, { IPosition } from "./position";
|
||||
import Range from "./range";
|
||||
import {SerializedPart, Part, PartCreator} from "./parts";
|
||||
import AutocompleteWrapperModel, {ICallback} from "./autocomplete";
|
||||
import { SerializedPart, Part, PartCreator } from "./parts";
|
||||
import AutocompleteWrapperModel, { ICallback } from "./autocomplete";
|
||||
import DocumentOffset from "./offset";
|
||||
import {Caret} from "./caret";
|
||||
import { Caret } from "./caret";
|
||||
|
||||
/**
|
||||
* @callback ModelCallback
|
||||
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue