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

Fix unfocused paste handling and focus return for file uploads #7625

Merged
merged 1 commit into from
Jan 26, 2022
Merged
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
27 changes: 16 additions & 11 deletions src/ContentMessages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import encrypt from "browser-encrypt-attachment";
import extractPngChunks from "png-chunks-extract";
import { IAbortablePromise, IImageInfo } from "matrix-js-sdk/src/@types/partials";
import { logger } from "matrix-js-sdk/src/logger";
import { IEventRelation } from "matrix-js-sdk/src";
import { IEventRelation, ISendEventResponse } from "matrix-js-sdk/src";

import { IEncryptedFile, IMediaEventInfo } from "./customisations/models/IMediaEventContent";
import dis from './dispatcher/dispatcher';
Expand All @@ -46,6 +46,7 @@ import { IUpload } from "./models/IUpload";
import { BlurhashEncoder } from "./BlurhashEncoder";
import SettingsStore from "./settings/SettingsStore";
import { decorateStartSendingTime, sendRoundTripMetric } from "./sendTimePerformanceMetrics";
import { TimelineRenderingType } from "./contexts/RoomContext";

const MAX_WIDTH = 800;
const MAX_HEIGHT = 600;
Expand Down Expand Up @@ -421,14 +422,14 @@ export default class ContentMessages {
private inprogress: IUpload[] = [];
private mediaConfig: IMediaConfig = null;

sendStickerContentToRoom(
public sendStickerContentToRoom(
url: string,
roomId: string,
threadId: string | null,
info: IImageInfo,
text: string,
matrixClient: MatrixClient,
) {
): Promise<ISendEventResponse> {
const startTime = CountlyAnalytics.getTimestamp();
const prom = matrixClient.sendStickerMessage(roomId, threadId, url, info, text).catch((e) => {
logger.warn(`Failed to send content with URL ${url} to room ${roomId}`, e);
Expand All @@ -438,20 +439,21 @@ export default class ContentMessages {
return prom;
}

getUploadLimit() {
public getUploadLimit(): number | null {
if (this.mediaConfig !== null && this.mediaConfig["m.upload.size"] !== undefined) {
return this.mediaConfig["m.upload.size"];
} else {
return null;
}
}

async sendContentListToRoom(
public async sendContentListToRoom(
files: File[],
roomId: string,
relation: IEventRelation | null,
matrixClient: MatrixClient,
) {
context = TimelineRenderingType.Room,
): Promise<void> {
if (matrixClient.isGuest()) {
dis.dispatch({ action: 'require_registration' });
return;
Expand Down Expand Up @@ -530,9 +532,15 @@ export default class ContentMessages {

promBefore = this.sendContentToRoom(file, roomId, relation, matrixClient, promBefore);
}

// Focus the correct composer
dis.dispatch({
action: Action.FocusSendMessageComposer,
context,
});
}

getCurrentUploads(relation?: IEventRelation) {
public getCurrentUploads(relation?: IEventRelation): IUpload[] {
return this.inprogress.filter(upload => {
const noRelation = !relation && !upload.relation;
const matchingRelation = relation && upload.relation
Expand All @@ -543,7 +551,7 @@ export default class ContentMessages {
});
}

cancelUpload(promise: Promise<any>, matrixClient: MatrixClient) {
public cancelUpload(promise: Promise<any>, matrixClient: MatrixClient): void {
let upload: IUpload;
for (let i = 0; i < this.inprogress.length; ++i) {
if (this.inprogress[i].promise === promise) {
Expand Down Expand Up @@ -632,9 +640,6 @@ export default class ContentMessages {
this.inprogress.push(upload);
dis.dispatch<UploadStartedPayload>({ action: Action.UploadStarted, upload });

// Focus the composer view
dis.fire(Action.FocusSendMessageComposer);

function onProgress(ev) {
upload.total = ev.total;
upload.loaded = ev.loaded;
Expand Down
16 changes: 13 additions & 3 deletions src/components/structures/LoggedInView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import LegacyCommunityPreview from "./LegacyCommunityPreview";
import { UserTab } from "../views/dialogs/UserSettingsDialog";
import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
import RightPanelStore from '../../stores/right-panel/RightPanelStore';
import { TimelineRenderingType } from "../../contexts/RoomContext";

// We need to fetch each pinned message individually (if we don't already have it)
// so each pinned message may trigger a request. Limit the number per room for sanity.
Expand Down Expand Up @@ -386,15 +387,20 @@ class LoggedInView extends React.Component<IProps, IState> {

private onPaste = (ev: ClipboardEvent) => {
const element = ev.target as HTMLElement;
const inputableElement = getInputableElement(element) || document.activeElement as HTMLElement;
const inputableElement = getInputableElement(element);
if (inputableElement === document.activeElement) return; // nothing to do

if (inputableElement?.focus) {
inputableElement.focus();
} else {
const inThread = !!document.activeElement.closest(".mx_ThreadView");
// refocusing during a paste event will make the
// paste end up in the newly focused element,
// so dispatch synchronously before paste happens
dis.fire(Action.FocusSendMessageComposer, true);
dis.dispatch({
action: Action.FocusSendMessageComposer,
context: inThread ? TimelineRenderingType.Thread : TimelineRenderingType.Room,
}, true);
}
};

Expand Down Expand Up @@ -552,8 +558,12 @@ class LoggedInView extends React.Component<IProps, IState> {
// If the user is entering a printable character outside of an input field
// redirect it to the composer for them.
if (!isClickShortcut && isPrintable && !getInputableElement(ev.target as HTMLElement)) {
const inThread = !!document.activeElement.closest(".mx_ThreadView");
// synchronous dispatch so we focus before key generates input
dis.fire(Action.FocusSendMessageComposer, true);
dis.dispatch({
action: Action.FocusSendMessageComposer,
context: inThread ? TimelineRenderingType.Thread : TimelineRenderingType.Room,
}, true);
ev.stopPropagation();
// we should *not* preventDefault() here as that would prevent typing in the now-focused composer
}
Expand Down
6 changes: 5 additions & 1 deletion src/components/views/rooms/MessageComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,11 @@ class UploadButton extends React.Component<IUploadButtonProps> {
}

ContentMessages.sharedInstance().sendContentListToRoom(
tfiles, this.props.roomId, this.props.relation, MatrixClientPeg.get(),
tfiles,
this.props.roomId,
this.props.relation,
MatrixClientPeg.get(),
this.context.timelineRenderingType,
);

// This is the onChange handler for a file form control, but we're
Expand Down
6 changes: 5 additions & 1 deletion src/components/views/rooms/SendMessageComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -560,7 +560,11 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
// it puts the filename in as text/plain which we want to ignore.
if (clipboardData.files.length && !clipboardData.types.includes("text/rtf")) {
ContentMessages.sharedInstance().sendContentListToRoom(
Array.from(clipboardData.files), this.props.room.roomId, this.props.relation, this.props.mxClient,
Array.from(clipboardData.files),
this.props.room.roomId,
this.props.relation,
this.props.mxClient,
this.context.timelineRenderingType,
);
return true; // to skip internal onPaste handler
}
Expand Down