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

Allow sending files as replies as per MSC3676 #8020

Merged
merged 5 commits into from
Mar 21, 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
44 changes: 21 additions & 23 deletions src/ContentMessages.tsx → src/ContentMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,21 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import React from "react";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { IUploadOpts } from "matrix-js-sdk/src/@types/requests";
import { MsgType } from "matrix-js-sdk/src/@types/event";
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, ISendEventResponse } from "matrix-js-sdk/src/matrix";
import { IEventRelation, ISendEventResponse, MatrixEvent } from "matrix-js-sdk/src/matrix";
import { THREAD_RELATION_TYPE } from "matrix-js-sdk/src/models/thread";

import { IEncryptedFile, IMediaEventInfo } from "./customisations/models/IMediaEventContent";
import dis from './dispatcher/dispatcher';
import * as sdk from './index';
import { _t } from './languageHandler';
import Modal from './Modal';
import RoomViewStore from './stores/RoomViewStore';
import Spinner from "./components/views/elements/Spinner";
import { Action } from "./dispatcher/actions";
import {
Expand All @@ -47,6 +45,8 @@ import { BlurhashEncoder } from "./BlurhashEncoder";
import SettingsStore from "./settings/SettingsStore";
import { decorateStartSendingTime, sendRoundTripMetric } from "./sendTimePerformanceMetrics";
import { TimelineRenderingType } from "./contexts/RoomContext";
import RoomViewStore from "./stores/RoomViewStore";
import { addReplyToMessageContent } from "./utils/Reply";

const MAX_WIDTH = 800;
const MAX_HEIGHT = 600;
Expand Down Expand Up @@ -457,25 +457,7 @@ export default class ContentMessages {
return;
}

const isQuoting = Boolean(RoomViewStore.getQuotingEvent());
if (isQuoting) {
// FIXME: Using an import will result in Element crashing
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
const { finished } = Modal.createTrackedDialog<[boolean]>('Upload Reply Warning', '', QuestionDialog, {
title: _t('Replying With Files'),
description: (
<div>{ _t(
'At this time it is not possible to reply with a file. ' +
'Would you like to upload this file without replying?',
) }</div>
),
hasCancelButton: true,
button: _t("Continue"),
});
const [shouldUpload] = await finished;
if (!shouldUpload) return;
}

const replyToEvent = RoomViewStore.getQuotingEvent();
if (!this.mediaConfig) { // hot-path optimization to not flash a spinner if we don't need to
const modal = Modal.createDialog(Spinner, null, 'mx_Dialog_spinner');
await this.ensureMediaConfigFetched(matrixClient);
Expand Down Expand Up @@ -528,7 +510,16 @@ export default class ContentMessages {
}
}

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

if (replyToEvent) {
// Clear event being replied to
dis.dispatch({
action: "reply_to_event",
event: null,
context,
});
}

// Focus the correct composer
Expand Down Expand Up @@ -569,6 +560,7 @@ export default class ContentMessages {
roomId: string,
relation: IEventRelation | undefined,
matrixClient: MatrixClient,
replyToEvent: MatrixEvent | undefined,
promBefore: Promise<any>,
) {
const content: IContent = {
Expand All @@ -583,6 +575,12 @@ export default class ContentMessages {
content["m.relates_to"] = relation;
}

if (replyToEvent) {
addReplyToMessageContent(content, replyToEvent, {
includeLegacyFallback: false,
});
}

if (SettingsStore.getValue("Performance.addSendMessageTimingMetadata")) {
decorateStartSendingTime(content);
}
Expand Down
30 changes: 1 addition & 29 deletions src/components/views/rooms/SendMessageComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,35 +58,7 @@ import { ComposerType } from "../../../dispatcher/payloads/ComposerInsertPayload
import { getSlashCommand, isSlashCommand, runSlashCommand, shouldSendAnyway } from "../../../editor/commands";
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
import { PosthogAnalytics } from "../../../PosthogAnalytics";
import { getNestedReplyText, makeReplyMixIn } from '../../../utils/Reply';

interface IAddReplyOpts {
permalinkCreator?: RoomPermalinkCreator;
includeLegacyFallback?: boolean;
}

function addReplyToMessageContent(
content: IContent,
replyToEvent: MatrixEvent,
opts: IAddReplyOpts = {
includeLegacyFallback: true,
},
): void {
const replyContent = makeReplyMixIn(replyToEvent);
Object.assign(content, replyContent);

if (opts.includeLegacyFallback) {
// Part of Replies fallback support - prepend the text we're sending
// with the text we're replying to
const nestedReply = getNestedReplyText(replyToEvent, opts.permalinkCreator);
if (nestedReply) {
if (content.formatted_body) {
content.formatted_body = nestedReply.html + content.formatted_body;
}
content.body = nestedReply.body + content.body;
}
}
}
import { addReplyToMessageContent } from '../../../utils/Reply';

export function attachRelation(
content: IContent,
Expand Down
4 changes: 1 addition & 3 deletions src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,6 @@
"You do not have permission to start a conference call in this room": "You do not have permission to start a conference call in this room",
"End conference": "End conference",
"This will end the conference for everyone. Continue?": "This will end the conference for everyone. Continue?",
"Replying With Files": "Replying With Files",
"At this time it is not possible to reply with a file. Would you like to upload this file without replying?": "At this time it is not possible to reply with a file. Would you like to upload this file without replying?",
"Continue": "Continue",
"The file '%(fileName)s' failed to upload.": "The file '%(fileName)s' failed to upload.",
"The file '%(fileName)s' exceeds this homeserver's size limit for uploads": "The file '%(fileName)s' exceeds this homeserver's size limit for uploads",
"Upload Failed": "Upload Failed",
Expand Down Expand Up @@ -455,6 +452,7 @@
"Invites user with given id to current room": "Invites user with given id to current room",
"Use an identity server": "Use an identity server",
"Use an identity server to invite by email. Click continue to use the default identity server (%(defaultIdentityServerName)s) or manage in Settings.": "Use an identity server to invite by email. Click continue to use the default identity server (%(defaultIdentityServerName)s) or manage in Settings.",
"Continue": "Continue",
"Use an identity server to invite by email. Manage in Settings.": "Use an identity server to invite by email. Manage in Settings.",
"Joins room with given address": "Joins room with given address",
"Leave room": "Leave room",
Expand Down
28 changes: 28 additions & 0 deletions src/utils/Reply.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,3 +184,31 @@ export function shouldDisplayReply(event: MatrixEvent): boolean {

return !!inReplyTo.event_id;
}

interface IAddReplyOpts {
permalinkCreator?: RoomPermalinkCreator;
includeLegacyFallback?: boolean;
}

export function addReplyToMessageContent(
content: IContent,
replyToEvent: MatrixEvent,
opts: IAddReplyOpts = {
includeLegacyFallback: true,
},
): void {
const replyContent = makeReplyMixIn(replyToEvent);
Object.assign(content, replyContent);

if (opts.includeLegacyFallback) {
// Part of Replies fallback support - prepend the text we're sending
// with the text we're replying to
const nestedReply = getNestedReplyText(replyToEvent, opts.permalinkCreator);
if (nestedReply) {
if (content.formatted_body) {
content.formatted_body = nestedReply.html + content.formatted_body;
}
content.body = nestedReply.body + content.body;
}
}
}