Merge pull request #5858 from matrix-org/t3chguy/fix/12740
This commit is contained in:
commit
d9caa3533b
12 changed files with 243 additions and 151 deletions
|
@ -18,14 +18,14 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import SettingsStore from "../../settings/SettingsStore";
|
import SettingsStore from "../../settings/SettingsStore";
|
||||||
import {LayoutPropType} from "../../settings/Layout";
|
import { LayoutPropType } from "../../settings/Layout";
|
||||||
import React, {createRef} from 'react';
|
import React, { createRef } from 'react';
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {EventTimeline} from "matrix-js-sdk/src/models/event-timeline";
|
import { EventTimeline } from "matrix-js-sdk/src/models/event-timeline";
|
||||||
import {TimelineWindow} from "matrix-js-sdk/src/timeline-window";
|
import { TimelineWindow } from "matrix-js-sdk/src/timeline-window";
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
import {MatrixClientPeg} from "../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||||
import RoomContext from "../../contexts/RoomContext";
|
import RoomContext from "../../contexts/RoomContext";
|
||||||
import UserActivity from "../../UserActivity";
|
import UserActivity from "../../UserActivity";
|
||||||
import Modal from "../../Modal";
|
import Modal from "../../Modal";
|
||||||
|
@ -35,10 +35,11 @@ import { Key } from '../../Keyboard';
|
||||||
import Timer from '../../utils/Timer';
|
import Timer from '../../utils/Timer';
|
||||||
import shouldHideEvent from '../../shouldHideEvent';
|
import shouldHideEvent from '../../shouldHideEvent';
|
||||||
import EditorStateTransfer from '../../utils/EditorStateTransfer';
|
import EditorStateTransfer from '../../utils/EditorStateTransfer';
|
||||||
import {haveTileForEvent} from "../views/rooms/EventTile";
|
import { haveTileForEvent } from "../views/rooms/EventTile";
|
||||||
import {UIFeature} from "../../settings/UIFeature";
|
import { UIFeature } from "../../settings/UIFeature";
|
||||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||||
import { arrayFastClone } from "../../utils/arrays";
|
import { arrayFastClone } from "../../utils/arrays";
|
||||||
|
import { Action } from "../../dispatcher/actions";
|
||||||
|
|
||||||
const PAGINATE_SIZE = 20;
|
const PAGINATE_SIZE = 20;
|
||||||
const INITIAL_SIZE = 20;
|
const INITIAL_SIZE = 20;
|
||||||
|
@ -439,21 +440,42 @@ class TimelinePanel extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
onAction = payload => {
|
onAction = payload => {
|
||||||
if (payload.action === 'ignore_state_changed') {
|
switch (payload.action) {
|
||||||
this.forceUpdate();
|
case "ignore_state_changed":
|
||||||
}
|
this.forceUpdate();
|
||||||
if (payload.action === "edit_event") {
|
break;
|
||||||
const editState = payload.event ? new EditorStateTransfer(payload.event) : null;
|
|
||||||
this.setState({editState}, () => {
|
case "edit_event": {
|
||||||
if (payload.event && this._messagePanel.current) {
|
const editState = payload.event ? new EditorStateTransfer(payload.event) : null;
|
||||||
this._messagePanel.current.scrollToEventIfNeeded(
|
this.setState({editState}, () => {
|
||||||
payload.event.getId(),
|
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",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
break;
|
||||||
}
|
}
|
||||||
if (payload.action === "scroll_to_bottom") {
|
|
||||||
this.jumpToLiveTimeline();
|
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 { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import { ReadPinsEventId } from "../right_panel/PinnedMessagesCard";
|
import { ReadPinsEventId } from "../right_panel/PinnedMessagesCard";
|
||||||
import ForwardDialog from "../dialogs/ForwardDialog";
|
import ForwardDialog from "../dialogs/ForwardDialog";
|
||||||
|
import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
|
||||||
|
import { Action } from "../../../dispatcher/actions";
|
||||||
|
|
||||||
export function canCancel(eventStatus) {
|
export function canCancel(eventStatus) {
|
||||||
return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT;
|
return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT;
|
||||||
|
@ -199,8 +201,8 @@ export default class MessageContextMenu extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
onQuoteClick = () => {
|
onQuoteClick = () => {
|
||||||
dis.dispatch({
|
dis.dispatch<ComposerInsertPayload>({
|
||||||
action: 'quote',
|
action: Action.ComposerInsert,
|
||||||
event: this.props.mxEvent,
|
event: this.props.mxEvent,
|
||||||
});
|
});
|
||||||
this.closeMenu();
|
this.closeMenu();
|
||||||
|
|
|
@ -16,12 +16,12 @@ 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, { createRef } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import highlight from 'highlight.js';
|
import highlight from 'highlight.js';
|
||||||
import * as HtmlUtils from '../../../HtmlUtils';
|
import * as HtmlUtils from '../../../HtmlUtils';
|
||||||
import {formatDate} from '../../../DateUtils';
|
import { formatDate } from '../../../DateUtils';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import dis from '../../../dispatcher/dispatcher';
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
|
@ -29,14 +29,16 @@ import { _t } from '../../../languageHandler';
|
||||||
import * as ContextMenu from '../../structures/ContextMenu';
|
import * as ContextMenu from '../../structures/ContextMenu';
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import ReplyThread from "../elements/ReplyThread";
|
import ReplyThread from "../elements/ReplyThread";
|
||||||
import {pillifyLinks, unmountPills} from '../../../utils/pillify';
|
import { pillifyLinks, unmountPills } from '../../../utils/pillify';
|
||||||
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
|
import { IntegrationManagers } from "../../../integrations/IntegrationManagers";
|
||||||
import {isPermalinkHost} from "../../../utils/permalinks/Permalinks";
|
import { isPermalinkHost } from "../../../utils/permalinks/Permalinks";
|
||||||
import {toRightOf} from "../../structures/ContextMenu";
|
import { toRightOf } from "../../structures/ContextMenu";
|
||||||
import {copyPlaintext} from "../../../utils/strings";
|
import { copyPlaintext } from "../../../utils/strings";
|
||||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import UIStore from "../../../stores/UIStore";
|
import UIStore from "../../../stores/UIStore";
|
||||||
|
import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
|
||||||
|
import { Action } from "../../../dispatcher/actions";
|
||||||
|
|
||||||
@replaceableComponent("views.messages.TextualBody")
|
@replaceableComponent("views.messages.TextualBody")
|
||||||
export default class TextualBody extends React.Component {
|
export default class TextualBody extends React.Component {
|
||||||
|
@ -390,9 +392,9 @@ export default class TextualBody extends React.Component {
|
||||||
|
|
||||||
onEmoteSenderClick = event => {
|
onEmoteSenderClick = event => {
|
||||||
const mxEvent = this.props.mxEvent;
|
const mxEvent = this.props.mxEvent;
|
||||||
dis.dispatch({
|
dis.dispatch<ComposerInsertPayload>({
|
||||||
action: 'insert_mention',
|
action: Action.ComposerInsert,
|
||||||
user_id: mxEvent.getSender(),
|
userId: mxEvent.getSender(),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import RoomViewStore from "../../../stores/RoomViewStore";
|
import RoomViewStore from "../../../stores/RoomViewStore";
|
||||||
import MultiInviter from "../../../utils/MultiInviter";
|
import MultiInviter from "../../../utils/MultiInviter";
|
||||||
import GroupStore from "../../../stores/GroupStore";
|
import GroupStore from "../../../stores/GroupStore";
|
||||||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||||
import E2EIcon from "../rooms/E2EIcon";
|
import E2EIcon from "../rooms/E2EIcon";
|
||||||
import { useEventEmitter } from "../../../hooks/useEventEmitter";
|
import { useEventEmitter } from "../../../hooks/useEventEmitter";
|
||||||
import { textualPowerLevel } from '../../../Roles';
|
import { textualPowerLevel } from '../../../Roles';
|
||||||
|
@ -68,6 +68,7 @@ import RoomAvatar from "../avatars/RoomAvatar";
|
||||||
import RoomName from "../elements/RoomName";
|
import RoomName from "../elements/RoomName";
|
||||||
import { mediaFromMxc } from "../../../customisations/Media";
|
import { mediaFromMxc } from "../../../customisations/Media";
|
||||||
import UIStore from "../../../stores/UIStore";
|
import UIStore from "../../../stores/UIStore";
|
||||||
|
import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
|
||||||
|
|
||||||
export interface IDevice {
|
export interface IDevice {
|
||||||
deviceId: string;
|
deviceId: string;
|
||||||
|
@ -368,9 +369,9 @@ const UserOptionsSection: React.FC<{
|
||||||
};
|
};
|
||||||
|
|
||||||
const onInsertPillButton = function() {
|
const onInsertPillButton = function() {
|
||||||
dis.dispatch({
|
dis.dispatch<ComposerInsertPayload>({
|
||||||
action: 'insert_mention',
|
action: Action.ComposerInsert,
|
||||||
user_id: member.userId,
|
userId: member.userId,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -16,38 +16,39 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React, {createRef, ClipboardEvent} from 'react';
|
import React, { createRef, ClipboardEvent } from 'react';
|
||||||
import {Room} from 'matrix-js-sdk/src/models/room';
|
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 EMOTICON_REGEX from 'emojibase-regex/emoticon';
|
||||||
|
|
||||||
import EditorModel from '../../../editor/model';
|
import EditorModel from '../../../editor/model';
|
||||||
import HistoryManager from '../../../editor/history';
|
import HistoryManager from '../../../editor/history';
|
||||||
import {Caret, setSelection} from '../../../editor/caret';
|
import { Caret, setSelection } from '../../../editor/caret';
|
||||||
import {
|
import {
|
||||||
formatRangeAsQuote,
|
formatRangeAsQuote,
|
||||||
formatRangeAsCode,
|
formatRangeAsCode,
|
||||||
toggleInlineFormat,
|
toggleInlineFormat,
|
||||||
replaceRangeAndMoveCaret,
|
replaceRangeAndMoveCaret,
|
||||||
} from '../../../editor/operations';
|
} from '../../../editor/operations';
|
||||||
import {getCaretOffsetAndText, getRangeForSelection} from '../../../editor/dom';
|
import { getCaretOffsetAndText, getRangeForSelection } from '../../../editor/dom';
|
||||||
import Autocomplete, {generateCompletionDomId} from '../rooms/Autocomplete';
|
import Autocomplete, { generateCompletionDomId } from '../rooms/Autocomplete';
|
||||||
import {getAutoCompleteCreator} from '../../../editor/parts';
|
import { getAutoCompleteCreator } from '../../../editor/parts';
|
||||||
import {parsePlainTextMessage} from '../../../editor/deserialize';
|
import { parseEvent, parsePlainTextMessage } from '../../../editor/deserialize';
|
||||||
import {renderModel} from '../../../editor/render';
|
import { renderModel } from '../../../editor/render';
|
||||||
import TypingStore from "../../../stores/TypingStore";
|
import TypingStore from "../../../stores/TypingStore";
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import {Key} from "../../../Keyboard";
|
import { Key } from "../../../Keyboard";
|
||||||
import {EMOTICON_TO_EMOJI} from "../../../emoji";
|
import { EMOTICON_TO_EMOJI } from "../../../emoji";
|
||||||
import {CommandCategories, CommandMap, parseCommandString} from "../../../SlashCommands";
|
import { CommandCategories, CommandMap, parseCommandString } from "../../../SlashCommands";
|
||||||
import Range from "../../../editor/range";
|
import Range from "../../../editor/range";
|
||||||
import MessageComposerFormatBar from "./MessageComposerFormatBar";
|
import MessageComposerFormatBar from "./MessageComposerFormatBar";
|
||||||
import DocumentOffset from "../../../editor/offset";
|
import DocumentOffset from "../../../editor/offset";
|
||||||
import {IDiff} from "../../../editor/diff";
|
import { IDiff } from "../../../editor/diff";
|
||||||
import AutocompleteWrapperModel from "../../../editor/autocomplete";
|
import AutocompleteWrapperModel from "../../../editor/autocomplete";
|
||||||
import DocumentPosition from "../../../editor/position";
|
import DocumentPosition from "../../../editor/position";
|
||||||
import {ICompletion} from "../../../autocomplete/Autocompleter";
|
import { ICompletion } from "../../../autocomplete/Autocompleter";
|
||||||
import { AutocompleteAction, getKeyBindingsManager, MessageComposerAction } from '../../../KeyBindingsManager';
|
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
|
// matches emoticons which follow the start of a line or whitespace
|
||||||
const REGEX_EMOTICON_WHITESPACE = new RegExp('(?:^|\\s)(' + EMOTICON_REGEX.source + ')\\s$');
|
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() {
|
focus() {
|
||||||
this.editorRef.current.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 React from 'react';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import {_t, _td} from '../../../languageHandler';
|
import { _t, _td } from '../../../languageHandler';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import dis from '../../../dispatcher/dispatcher';
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
import EditorModel from '../../../editor/model';
|
import EditorModel from '../../../editor/model';
|
||||||
import {getCaretOffsetAndText} from '../../../editor/dom';
|
import { getCaretOffsetAndText } from '../../../editor/dom';
|
||||||
import {htmlSerializeIfNeeded, textSerialize, containsEmote, stripEmoteCommand} from '../../../editor/serialize';
|
import { htmlSerializeIfNeeded, textSerialize, containsEmote, stripEmoteCommand } from '../../../editor/serialize';
|
||||||
import {findEditableEvent} from '../../../utils/EventUtils';
|
import { findEditableEvent } from '../../../utils/EventUtils';
|
||||||
import {parseEvent} from '../../../editor/deserialize';
|
import { parseEvent } from '../../../editor/deserialize';
|
||||||
import {CommandPartCreator} from '../../../editor/parts';
|
import { CommandPartCreator } from '../../../editor/parts';
|
||||||
import EditorStateTransfer from '../../../utils/EditorStateTransfer';
|
import EditorStateTransfer from '../../../utils/EditorStateTransfer';
|
||||||
import classNames from 'classnames';
|
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 BasicMessageComposer from "./BasicMessageComposer";
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import {CommandCategories, getCommand} from '../../../SlashCommands';
|
import { CommandCategories, getCommand } from '../../../SlashCommands';
|
||||||
import {Action} from "../../../dispatcher/actions";
|
import { Action } from "../../../dispatcher/actions";
|
||||||
import CountlyAnalytics from "../../../CountlyAnalytics";
|
import CountlyAnalytics from "../../../CountlyAnalytics";
|
||||||
import {getKeyBindingsManager, MessageComposerAction} from '../../../KeyBindingsManager';
|
import { getKeyBindingsManager, MessageComposerAction } from '../../../KeyBindingsManager';
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import SendHistoryManager from '../../../SendHistoryManager';
|
import SendHistoryManager from '../../../SendHistoryManager';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
|
|
||||||
|
@ -124,6 +124,7 @@ export default class EditMessageComposer extends React.Component {
|
||||||
};
|
};
|
||||||
this._createEditorModel();
|
this._createEditorModel();
|
||||||
window.addEventListener("beforeunload", this._saveStoredEditorState);
|
window.addEventListener("beforeunload", this._saveStoredEditorState);
|
||||||
|
this.dispatcherRef = dis.register(this.onAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
_setEditorRef = ref => {
|
_setEditorRef = ref => {
|
||||||
|
@ -399,6 +400,7 @@ export default class EditMessageComposer extends React.Component {
|
||||||
if (this._shouldSaveStoredEditorState) {
|
if (this._shouldSaveStoredEditorState) {
|
||||||
this._saveStoredEditorState();
|
this._saveStoredEditorState();
|
||||||
}
|
}
|
||||||
|
dis.unregister(this.dispatcherRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
_createEditorModel() {
|
_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() {
|
render() {
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||||
return (<div className={classNames("mx_EditMessageComposer", this.props.className)} onKeyDown={this._onKeyDown}>
|
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 { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
|
||||||
import {StaticNotificationState} from "../../../stores/notifications/StaticNotificationState";
|
import {StaticNotificationState} from "../../../stores/notifications/StaticNotificationState";
|
||||||
import NotificationBadge from "./NotificationBadge";
|
import NotificationBadge from "./NotificationBadge";
|
||||||
|
import {ComposerInsertPayload} from "../../../dispatcher/payloads/ComposerInsertPayload";
|
||||||
|
import { Action } from '../../../dispatcher/actions';
|
||||||
|
|
||||||
const eventTileTypes = {
|
const eventTileTypes = {
|
||||||
[EventType.RoomMessage]: 'messages.MessageEvent',
|
[EventType.RoomMessage]: 'messages.MessageEvent',
|
||||||
|
@ -727,9 +729,9 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
onSenderProfileClick = event => {
|
onSenderProfileClick = event => {
|
||||||
const mxEvent = this.props.mxEvent;
|
const mxEvent = this.props.mxEvent;
|
||||||
dis.dispatch({
|
dis.dispatch<ComposerInsertPayload>({
|
||||||
action: 'insert_mention',
|
action: Action.ComposerInsert,
|
||||||
user_id: mxEvent.getSender(),
|
userId: mxEvent.getSender(),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -16,11 +16,11 @@ limitations under the License.
|
||||||
import React 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';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import {MatrixEvent} from "matrix-js-sdk/src/models/event";
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
import {Room} from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import {RoomMember} from "matrix-js-sdk/src/models/room-member";
|
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||||
import dis from '../../../dispatcher/dispatcher';
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
import { ActionPayload } from "../../../dispatcher/payloads";
|
import { ActionPayload } from "../../../dispatcher/payloads";
|
||||||
import Stickerpicker from './Stickerpicker';
|
import Stickerpicker from './Stickerpicker';
|
||||||
|
@ -28,19 +28,21 @@ 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 {aboveLeftOf, ContextMenu, ContextMenuTooltipButton, useContextMenu} from "../../structures/ContextMenu";
|
import { aboveLeftOf, ContextMenu, ContextMenuTooltipButton, useContextMenu } 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";
|
||||||
import {UPDATE_EVENT} from "../../../stores/AsyncStore";
|
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import VoiceRecordComposerTile from "./VoiceRecordComposerTile";
|
import VoiceRecordComposerTile from "./VoiceRecordComposerTile";
|
||||||
import {VoiceRecordingStore} from "../../../stores/VoiceRecordingStore";
|
import { VoiceRecordingStore } from "../../../stores/VoiceRecordingStore";
|
||||||
import {RecordingState} from "../../../voice/VoiceRecording";
|
import { RecordingState } from "../../../voice/VoiceRecording";
|
||||||
import Tooltip, {Alignment} from "../elements/Tooltip";
|
import Tooltip, { Alignment } from "../elements/Tooltip";
|
||||||
import ResizeNotifier from "../../../utils/ResizeNotifier";
|
import ResizeNotifier from "../../../utils/ResizeNotifier";
|
||||||
import { E2EStatus } from '../../../utils/ShieldUtils';
|
import { E2EStatus } from '../../../utils/ShieldUtils';
|
||||||
import SendMessageComposer from "./SendMessageComposer";
|
import SendMessageComposer from "./SendMessageComposer";
|
||||||
|
import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
|
||||||
|
import { Action } from "../../../dispatcher/actions";
|
||||||
|
|
||||||
interface IComposerAvatarProps {
|
interface IComposerAvatarProps {
|
||||||
me: object;
|
me: object;
|
||||||
|
@ -316,10 +318,10 @@ export default class MessageComposer extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addEmoji(emoji) {
|
addEmoji(emoji: string) {
|
||||||
dis.dispatch({
|
dis.dispatch<ComposerInsertPayload>({
|
||||||
action: "insert_emoji",
|
action: Action.ComposerInsert,
|
||||||
emoji,
|
text: emoji,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,27 +27,26 @@ import {
|
||||||
startsWith,
|
startsWith,
|
||||||
stripPrefix,
|
stripPrefix,
|
||||||
} from '../../../editor/serialize';
|
} from '../../../editor/serialize';
|
||||||
import {CommandPartCreator} from '../../../editor/parts';
|
import { CommandPartCreator } from '../../../editor/parts';
|
||||||
import BasicMessageComposer from "./BasicMessageComposer";
|
import BasicMessageComposer from "./BasicMessageComposer";
|
||||||
import ReplyThread from "../elements/ReplyThread";
|
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 SendHistoryManager from "../../../SendHistoryManager";
|
||||||
import {CommandCategories, getCommand} from '../../../SlashCommands';
|
import { CommandCategories, getCommand } from '../../../SlashCommands';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import {_t, _td} from '../../../languageHandler';
|
import { _t, _td } from '../../../languageHandler';
|
||||||
import ContentMessages from '../../../ContentMessages';
|
import ContentMessages from '../../../ContentMessages';
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import RateLimitedFunc from '../../../ratelimitedfunc';
|
import RateLimitedFunc from '../../../ratelimitedfunc';
|
||||||
import {Action} from "../../../dispatcher/actions";
|
import { Action } from "../../../dispatcher/actions";
|
||||||
import {containsEmoji} from "../../../effects/utils";
|
import { containsEmoji } from "../../../effects/utils";
|
||||||
import {CHAT_EFFECTS} from '../../../effects';
|
import { CHAT_EFFECTS } from '../../../effects';
|
||||||
import CountlyAnalytics from "../../../CountlyAnalytics";
|
import CountlyAnalytics from "../../../CountlyAnalytics";
|
||||||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||||
import EMOJI_REGEX from 'emojibase-regex';
|
import EMOJI_REGEX from 'emojibase-regex';
|
||||||
import {getKeyBindingsManager, MessageComposerAction} from '../../../KeyBindingsManager';
|
import { getKeyBindingsManager, MessageComposerAction } from '../../../KeyBindingsManager';
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import SettingsStore from '../../../settings/SettingsStore';
|
import SettingsStore from '../../../settings/SettingsStore';
|
||||||
|
|
||||||
function addReplyToMessageContent(content, repliedToEvent, permalinkCreator) {
|
function addReplyToMessageContent(content, repliedToEvent, permalinkCreator) {
|
||||||
|
@ -486,62 +485,18 @@ export default class SendMessageComposer extends React.Component {
|
||||||
case Action.FocusComposer:
|
case Action.FocusComposer:
|
||||||
this._editorRef && this._editorRef.focus();
|
this._editorRef && this._editorRef.focus();
|
||||||
break;
|
break;
|
||||||
case 'insert_mention':
|
case "send_composer_insert":
|
||||||
this._insertMention(payload.user_id);
|
if (payload.userId) {
|
||||||
break;
|
this._editorRef && this._editorRef.insertMention(payload.userId);
|
||||||
case 'quote':
|
} else if (payload.event) {
|
||||||
this._insertQuotedMessage(payload.event);
|
this._editorRef && this._editorRef.insertQuotedMessage(payload.event);
|
||||||
break;
|
} else if (payload.text) {
|
||||||
case 'insert_emoji':
|
this._editorRef && this._editorRef.insertPlaintext(payload.text);
|
||||||
this._insertEmoji(payload.emoji);
|
}
|
||||||
break;
|
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) => {
|
_onPaste = (event) => {
|
||||||
const {clipboardData} = event;
|
const {clipboardData} = event;
|
||||||
// Prioritize text on the clipboard over files as Office on macOS puts a bitmap
|
// 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
|
* Fired when joining a room failed
|
||||||
*/
|
*/
|
||||||
JoinRoomError = "join_room_error",
|
JoinRoomError = "join_room_error",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts content into the active composer. Should be used with ComposerInsertPayload
|
||||||
|
*/
|
||||||
|
ComposerInsert = "composer_insert",
|
||||||
}
|
}
|
||||||
|
|
42
src/dispatcher/payloads/ComposerInsertPayload.ts
Normal file
42
src/dispatcher/payloads/ComposerInsertPayload.ts
Normal 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;
|
||||||
|
|
|
@ -15,13 +15,13 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {diffAtCaret, diffDeletion, IDiff} from "./diff";
|
import { diffAtCaret, diffDeletion, IDiff } from "./diff";
|
||||||
import DocumentPosition, {IPosition} from "./position";
|
import DocumentPosition, { IPosition } from "./position";
|
||||||
import Range from "./range";
|
import Range from "./range";
|
||||||
import {SerializedPart, Part, PartCreator} from "./parts";
|
import { SerializedPart, Part, PartCreator } from "./parts";
|
||||||
import AutocompleteWrapperModel, {ICallback} from "./autocomplete";
|
import AutocompleteWrapperModel, { ICallback } from "./autocomplete";
|
||||||
import DocumentOffset from "./offset";
|
import DocumentOffset from "./offset";
|
||||||
import {Caret} from "./caret";
|
import { Caret } from "./caret";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @callback ModelCallback
|
* @callback ModelCallback
|
||||||
|
@ -390,7 +390,7 @@ export default class EditorModel {
|
||||||
return addLen;
|
return addLen;
|
||||||
}
|
}
|
||||||
|
|
||||||
positionForOffset(totalOffset: number, atPartEnd: boolean) {
|
positionForOffset(totalOffset: number, atPartEnd = false) {
|
||||||
let currentOffset = 0;
|
let currentOffset = 0;
|
||||||
const index = this._parts.findIndex(part => {
|
const index = this._parts.findIndex(part => {
|
||||||
const partLen = part.text.length;
|
const partLen = part.text.length;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue