Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Handle insertFromPaste properly for wider ranging paste support #10844

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions src/components/views/rooms/BasicMessageComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ limitations under the License.
*/

import classNames from "classnames";
import React, { createRef, ClipboardEvent } from "react";
import React, { createRef, ClipboardEvent, SyntheticEvent } 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";
Expand Down Expand Up @@ -108,7 +108,7 @@ interface IProps {
disabled?: boolean;

onChange?(selection: Caret, inputType?: string, diff?: IDiff): void;
onPaste?(event: ClipboardEvent<HTMLDivElement>, model: EditorModel): boolean;
onPaste?(event: Event | SyntheticEvent, data: DataTransfer, model: EditorModel): boolean;
}

interface IState {
Expand Down Expand Up @@ -355,18 +355,18 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
this.onCutCopy(event, "cut");
};

private onPaste = (event: ClipboardEvent<HTMLDivElement>): boolean | undefined => {
private onPasteHandler = (event: Event | SyntheticEvent, data: DataTransfer): boolean | undefined => {
event.preventDefault(); // we always handle the paste ourselves
if (!this.editorRef.current) return;
if (this.props.onPaste?.(event, this.props.model)) {
if (this.props.onPaste?.(event, data, this.props.model)) {
// to prevent double handling, allow props.onPaste to skip internal onPaste
return true;
}

const { model } = this.props;
const { partCreator } = model;
const plainText = event.clipboardData.getData("text/plain");
const partsText = event.clipboardData.getData("application/x-element-composer");
const plainText = data.getData("text/plain");
const partsText = data.getData("application/x-element-composer");

let parts: Part[];
if (partsText) {
Expand All @@ -387,6 +387,21 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
}
};

private onPaste = (event: ClipboardEvent<HTMLDivElement>): boolean | undefined => {
return this.onPasteHandler(event, event.clipboardData);
};

private onBeforeInput = (event: InputEvent): void => {
// ignore any input while doing IME compositions
if (this.isIMEComposing) {
return;
}

if (event.inputType === "insertFromPaste" && event.dataTransfer) {
this.onPasteHandler(event, event.dataTransfer);
}
};

private onInput = (event: Partial<InputEvent>): void => {
if (!this.editorRef.current) return;
// ignore any input while doing IME compositions
Expand Down Expand Up @@ -703,6 +718,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>

public componentWillUnmount(): void {
document.removeEventListener("selectionchange", this.onSelectionChange);
this.editorRef.current?.removeEventListener("beforeinput", this.onBeforeInput, true);
this.editorRef.current?.removeEventListener("input", this.onInput, true);
this.editorRef.current?.removeEventListener("compositionstart", this.onCompositionStart, true);
this.editorRef.current?.removeEventListener("compositionend", this.onCompositionEnd, true);
Expand All @@ -728,6 +744,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
this.updateEditorState(this.getInitialCaretPosition());
// attach input listener by hand so React doesn't proxy the events,
// as the proxied event doesn't support inputType, which we need.
this.editorRef.current?.addEventListener("beforeinput", this.onBeforeInput, true);
this.editorRef.current?.addEventListener("input", this.onInput, true);
this.editorRef.current?.addEventListener("compositionstart", this.onCompositionStart, true);
this.editorRef.current?.addEventListener("compositionend", this.onCompositionEnd, true);
Expand Down
9 changes: 4 additions & 5 deletions src/components/views/rooms/SendMessageComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import React, { ClipboardEvent, createRef, KeyboardEvent } from "react";
import React, { createRef, KeyboardEvent, SyntheticEvent } from "react";
import EMOJI_REGEX from "emojibase-regex";
import { IContent, MatrixEvent, IEventRelation, IMentions } from "matrix-js-sdk/src/models/event";
import { DebouncedFunc, throttle } from "lodash";
Expand Down Expand Up @@ -666,15 +666,14 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
}
};

private onPaste = (event: ClipboardEvent<HTMLDivElement>): boolean => {
const { clipboardData } = event;
private onPaste = (event: Event | SyntheticEvent, data: DataTransfer): boolean => {
// Prioritize text on the clipboard over files if RTF is present as Office on macOS puts a bitmap
// in the clipboard as well as the content being copied. Modern versions of Office seem to not do this anymore.
// We check text/rtf instead of text/plain as when copy+pasting a file from Finder or Gnome Image Viewer
// it puts the filename in as text/plain which we want to ignore.
if (clipboardData.files.length && !clipboardData.types.includes("text/rtf")) {
if (data.files.length && !data.types.includes("text/rtf")) {
ContentMessages.sharedInstance().sendContentListToRoom(
Array.from(clipboardData.files),
Array.from(data.files),
this.props.room.roomId,
this.props.relation,
this.props.mxClient,
Expand Down