Fix unfocused paste handling and focus return for file uploads (#7625)
This commit is contained in:
parent
e53427fce3
commit
88cd2f8af7
4 changed files with 39 additions and 16 deletions
|
@ -24,7 +24,7 @@ import encrypt from "browser-encrypt-attachment";
|
||||||
import extractPngChunks from "png-chunks-extract";
|
import extractPngChunks from "png-chunks-extract";
|
||||||
import { IAbortablePromise, IImageInfo } from "matrix-js-sdk/src/@types/partials";
|
import { IAbortablePromise, IImageInfo } from "matrix-js-sdk/src/@types/partials";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
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 { IEncryptedFile, IMediaEventInfo } from "./customisations/models/IMediaEventContent";
|
||||||
import dis from './dispatcher/dispatcher';
|
import dis from './dispatcher/dispatcher';
|
||||||
|
@ -46,6 +46,7 @@ import { IUpload } from "./models/IUpload";
|
||||||
import { BlurhashEncoder } from "./BlurhashEncoder";
|
import { BlurhashEncoder } from "./BlurhashEncoder";
|
||||||
import SettingsStore from "./settings/SettingsStore";
|
import SettingsStore from "./settings/SettingsStore";
|
||||||
import { decorateStartSendingTime, sendRoundTripMetric } from "./sendTimePerformanceMetrics";
|
import { decorateStartSendingTime, sendRoundTripMetric } from "./sendTimePerformanceMetrics";
|
||||||
|
import { TimelineRenderingType } from "./contexts/RoomContext";
|
||||||
|
|
||||||
const MAX_WIDTH = 800;
|
const MAX_WIDTH = 800;
|
||||||
const MAX_HEIGHT = 600;
|
const MAX_HEIGHT = 600;
|
||||||
|
@ -421,14 +422,14 @@ export default class ContentMessages {
|
||||||
private inprogress: IUpload[] = [];
|
private inprogress: IUpload[] = [];
|
||||||
private mediaConfig: IMediaConfig = null;
|
private mediaConfig: IMediaConfig = null;
|
||||||
|
|
||||||
sendStickerContentToRoom(
|
public sendStickerContentToRoom(
|
||||||
url: string,
|
url: string,
|
||||||
roomId: string,
|
roomId: string,
|
||||||
threadId: string | null,
|
threadId: string | null,
|
||||||
info: IImageInfo,
|
info: IImageInfo,
|
||||||
text: string,
|
text: string,
|
||||||
matrixClient: MatrixClient,
|
matrixClient: MatrixClient,
|
||||||
) {
|
): Promise<ISendEventResponse> {
|
||||||
const startTime = CountlyAnalytics.getTimestamp();
|
const startTime = CountlyAnalytics.getTimestamp();
|
||||||
const prom = matrixClient.sendStickerMessage(roomId, threadId, url, info, text).catch((e) => {
|
const prom = matrixClient.sendStickerMessage(roomId, threadId, url, info, text).catch((e) => {
|
||||||
logger.warn(`Failed to send content with URL ${url} to room ${roomId}`, e);
|
logger.warn(`Failed to send content with URL ${url} to room ${roomId}`, e);
|
||||||
|
@ -438,7 +439,7 @@ export default class ContentMessages {
|
||||||
return prom;
|
return prom;
|
||||||
}
|
}
|
||||||
|
|
||||||
getUploadLimit() {
|
public getUploadLimit(): number | null {
|
||||||
if (this.mediaConfig !== null && this.mediaConfig["m.upload.size"] !== undefined) {
|
if (this.mediaConfig !== null && this.mediaConfig["m.upload.size"] !== undefined) {
|
||||||
return this.mediaConfig["m.upload.size"];
|
return this.mediaConfig["m.upload.size"];
|
||||||
} else {
|
} else {
|
||||||
|
@ -446,12 +447,13 @@ export default class ContentMessages {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendContentListToRoom(
|
public async sendContentListToRoom(
|
||||||
files: File[],
|
files: File[],
|
||||||
roomId: string,
|
roomId: string,
|
||||||
relation: IEventRelation | null,
|
relation: IEventRelation | null,
|
||||||
matrixClient: MatrixClient,
|
matrixClient: MatrixClient,
|
||||||
) {
|
context = TimelineRenderingType.Room,
|
||||||
|
): Promise<void> {
|
||||||
if (matrixClient.isGuest()) {
|
if (matrixClient.isGuest()) {
|
||||||
dis.dispatch({ action: 'require_registration' });
|
dis.dispatch({ action: 'require_registration' });
|
||||||
return;
|
return;
|
||||||
|
@ -530,9 +532,15 @@ export default class ContentMessages {
|
||||||
|
|
||||||
promBefore = this.sendContentToRoom(file, roomId, relation, matrixClient, promBefore);
|
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 => {
|
return this.inprogress.filter(upload => {
|
||||||
const noRelation = !relation && !upload.relation;
|
const noRelation = !relation && !upload.relation;
|
||||||
const matchingRelation = relation && upload.relation
|
const matchingRelation = relation && upload.relation
|
||||||
|
@ -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;
|
let upload: IUpload;
|
||||||
for (let i = 0; i < this.inprogress.length; ++i) {
|
for (let i = 0; i < this.inprogress.length; ++i) {
|
||||||
if (this.inprogress[i].promise === promise) {
|
if (this.inprogress[i].promise === promise) {
|
||||||
|
@ -632,9 +640,6 @@ export default class ContentMessages {
|
||||||
this.inprogress.push(upload);
|
this.inprogress.push(upload);
|
||||||
dis.dispatch<UploadStartedPayload>({ action: Action.UploadStarted, upload });
|
dis.dispatch<UploadStartedPayload>({ action: Action.UploadStarted, upload });
|
||||||
|
|
||||||
// Focus the composer view
|
|
||||||
dis.fire(Action.FocusSendMessageComposer);
|
|
||||||
|
|
||||||
function onProgress(ev) {
|
function onProgress(ev) {
|
||||||
upload.total = ev.total;
|
upload.total = ev.total;
|
||||||
upload.loaded = ev.loaded;
|
upload.loaded = ev.loaded;
|
||||||
|
|
|
@ -69,6 +69,7 @@ import LegacyCommunityPreview from "./LegacyCommunityPreview";
|
||||||
import { UserTab } from "../views/dialogs/UserSettingsDialog";
|
import { UserTab } from "../views/dialogs/UserSettingsDialog";
|
||||||
import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
|
import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
|
||||||
import RightPanelStore from '../../stores/right-panel/RightPanelStore';
|
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)
|
// 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.
|
// so each pinned message may trigger a request. Limit the number per room for sanity.
|
||||||
|
@ -386,15 +387,20 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
private onPaste = (ev: ClipboardEvent) => {
|
private onPaste = (ev: ClipboardEvent) => {
|
||||||
const element = ev.target as HTMLElement;
|
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) {
|
if (inputableElement?.focus) {
|
||||||
inputableElement.focus();
|
inputableElement.focus();
|
||||||
} else {
|
} else {
|
||||||
|
const inThread = !!document.activeElement.closest(".mx_ThreadView");
|
||||||
// refocusing during a paste event will make the
|
// refocusing during a paste event will make the
|
||||||
// paste end up in the newly focused element,
|
// paste end up in the newly focused element,
|
||||||
// so dispatch synchronously before paste happens
|
// so dispatch synchronously before paste happens
|
||||||
dis.fire(Action.FocusSendMessageComposer, true);
|
dis.dispatch({
|
||||||
|
action: Action.FocusSendMessageComposer,
|
||||||
|
context: inThread ? TimelineRenderingType.Thread : TimelineRenderingType.Room,
|
||||||
|
}, true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -552,8 +558,12 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
// If the user is entering a printable character outside of an input field
|
// If the user is entering a printable character outside of an input field
|
||||||
// redirect it to the composer for them.
|
// redirect it to the composer for them.
|
||||||
if (!isClickShortcut && isPrintable && !getInputableElement(ev.target as HTMLElement)) {
|
if (!isClickShortcut && isPrintable && !getInputableElement(ev.target as HTMLElement)) {
|
||||||
|
const inThread = !!document.activeElement.closest(".mx_ThreadView");
|
||||||
// synchronous dispatch so we focus before key generates input
|
// 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();
|
ev.stopPropagation();
|
||||||
// we should *not* preventDefault() here as that would prevent typing in the now-focused composer
|
// we should *not* preventDefault() here as that would prevent typing in the now-focused composer
|
||||||
}
|
}
|
||||||
|
|
|
@ -160,7 +160,11 @@ class UploadButton extends React.Component<IUploadButtonProps> {
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentMessages.sharedInstance().sendContentListToRoom(
|
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
|
// This is the onChange handler for a file form control, but we're
|
||||||
|
|
|
@ -560,7 +560,11 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
|
||||||
// it puts the filename in as text/plain which we want to ignore.
|
// it puts the filename in as text/plain which we want to ignore.
|
||||||
if (clipboardData.files.length && !clipboardData.types.includes("text/rtf")) {
|
if (clipboardData.files.length && !clipboardData.types.includes("text/rtf")) {
|
||||||
ContentMessages.sharedInstance().sendContentListToRoom(
|
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
|
return true; // to skip internal onPaste handler
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue