Wire up drag-drop file uploads for the thread view (#7860)

This commit is contained in:
Michael Telatynski 2022-02-22 11:14:56 +00:00 committed by GitHub
parent 42e9ea4540
commit 8fccef86d8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 616 additions and 482 deletions

View file

@ -17,11 +17,8 @@ limitations under the License.
import React from 'react';
import classNames from 'classnames';
import { IEventRelation, MatrixEvent } from 'matrix-js-sdk/src/models/event';
import escapeHtml from "escape-html";
import sanitizeHtml from "sanitize-html";
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
import { Room } from 'matrix-js-sdk/src/models/room';
import { RelationType } from 'matrix-js-sdk/src/@types/event';
import { Relations } from 'matrix-js-sdk/src/models/relations';
import { _t } from '../../../languageHandler';
@ -32,12 +29,12 @@ import { Layout } from "../../../settings/enums/Layout";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { getUserNameColorClass } from "../../../utils/FormattingUtils";
import { Action } from "../../../dispatcher/actions";
import { PERMITTED_URL_SCHEMES } from "../../../HtmlUtils";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import Spinner from './Spinner';
import ReplyTile from "../rooms/ReplyTile";
import Pill from './Pill';
import { ButtonEvent } from './AccessibleButton';
import { getParentEventId } from '../../../utils/Reply';
/**
* This number is based on the previous behavior - if we have message of height
@ -97,174 +94,6 @@ export default class ReplyChain extends React.Component<IProps, IState> {
this.room = this.context.getRoom(this.props.parentEv.getRoomId());
}
public static getParentEventId(ev: MatrixEvent): string | undefined {
if (!ev || ev.isRedacted()) return;
if (ev.replyEventId) {
return ev.replyEventId;
}
}
// Part of Replies fallback support
public static stripPlainReply(body: string): string {
// Removes lines beginning with `> ` until you reach one that doesn't.
const lines = body.split('\n');
while (lines.length && lines[0].startsWith('> ')) lines.shift();
// Reply fallback has a blank line after it, so remove it to prevent leading newline
if (lines[0] === '') lines.shift();
return lines.join('\n');
}
// Part of Replies fallback support
public static stripHTMLReply(html: string): string {
// Sanitize the original HTML for inclusion in <mx-reply>. We allow
// any HTML, since the original sender could use special tags that we
// don't recognize, but want to pass along to any recipients who do
// recognize them -- recipients should be sanitizing before displaying
// anyways. However, we sanitize to 1) remove any mx-reply, so that we
// don't generate a nested mx-reply, and 2) make sure that the HTML is
// properly formatted (e.g. tags are closed where necessary)
return sanitizeHtml(
html,
{
allowedTags: false, // false means allow everything
allowedAttributes: false,
// we somehow can't allow all schemes, so we allow all that we
// know of and mxc (for img tags)
allowedSchemes: [...PERMITTED_URL_SCHEMES, 'mxc'],
exclusiveFilter: (frame) => frame.tag === "mx-reply",
},
);
}
// Part of Replies fallback support
public static getNestedReplyText(
ev: MatrixEvent,
permalinkCreator: RoomPermalinkCreator,
): { body: string, html: string } | null {
if (!ev) return null;
let { body, formatted_body: html } = ev.getContent();
if (this.getParentEventId(ev)) {
if (body) body = this.stripPlainReply(body);
}
if (!body) body = ""; // Always ensure we have a body, for reasons.
if (html) {
// sanitize the HTML before we put it in an <mx-reply>
html = this.stripHTMLReply(html);
} else {
// Escape the body to use as HTML below.
// We also run a nl2br over the result to fix the fallback representation. We do this
// after converting the text to safe HTML to avoid user-provided BR's from being converted.
html = escapeHtml(body).replace(/\n/g, '<br/>');
}
// dev note: do not rely on `body` being safe for HTML usage below.
const evLink = permalinkCreator.forEvent(ev.getId());
const userLink = makeUserPermalink(ev.getSender());
const mxid = ev.getSender();
// This fallback contains text that is explicitly EN.
switch (ev.getContent().msgtype) {
case 'm.text':
case 'm.notice': {
html = `<mx-reply><blockquote><a href="${evLink}">In reply to</a> <a href="${userLink}">${mxid}</a>`
+ `<br>${html}</blockquote></mx-reply>`;
const lines = body.trim().split('\n');
if (lines.length > 0) {
lines[0] = `<${mxid}> ${lines[0]}`;
body = lines.map((line) => `> ${line}`).join('\n') + '\n\n';
}
break;
}
case 'm.image':
html = `<mx-reply><blockquote><a href="${evLink}">In reply to</a> <a href="${userLink}">${mxid}</a>`
+ `<br>sent an image.</blockquote></mx-reply>`;
body = `> <${mxid}> sent an image.\n\n`;
break;
case 'm.video':
html = `<mx-reply><blockquote><a href="${evLink}">In reply to</a> <a href="${userLink}">${mxid}</a>`
+ `<br>sent a video.</blockquote></mx-reply>`;
body = `> <${mxid}> sent a video.\n\n`;
break;
case 'm.audio':
html = `<mx-reply><blockquote><a href="${evLink}">In reply to</a> <a href="${userLink}">${mxid}</a>`
+ `<br>sent an audio file.</blockquote></mx-reply>`;
body = `> <${mxid}> sent an audio file.\n\n`;
break;
case 'm.file':
html = `<mx-reply><blockquote><a href="${evLink}">In reply to</a> <a href="${userLink}">${mxid}</a>`
+ `<br>sent a file.</blockquote></mx-reply>`;
body = `> <${mxid}> sent a file.\n\n`;
break;
case 'm.emote': {
html = `<mx-reply><blockquote><a href="${evLink}">In reply to</a> * `
+ `<a href="${userLink}">${mxid}</a><br>${html}</blockquote></mx-reply>`;
const lines = body.trim().split('\n');
if (lines.length > 0) {
lines[0] = `* <${mxid}> ${lines[0]}`;
body = lines.map((line) => `> ${line}`).join('\n') + '\n\n';
}
break;
}
default:
return null;
}
return { body, html };
}
public static makeReplyMixIn(ev: MatrixEvent, renderIn?: string[]) {
if (!ev) return {};
const mixin: any = {
'm.relates_to': {
'm.in_reply_to': {
'event_id': ev.getId(),
},
},
};
if (renderIn) {
mixin['m.relates_to']['m.in_reply_to']['m.render_in'] = renderIn;
}
/**
* If the event replied is part of a thread
* Add the `m.thread` relation so that clients
* that know how to handle that relation will
* be able to render them more accurately
*/
if (ev.isThreadRelation) {
mixin['m.relates_to'] = {
...mixin['m.relates_to'],
rel_type: RelationType.Thread,
event_id: ev.threadRootId,
};
}
return mixin;
}
public static shouldDisplayReply(event: MatrixEvent, renderTarget?: string): boolean {
const parentExist = Boolean(ReplyChain.getParentEventId(event));
const relations = event.getRelation();
const renderIn = relations?.["m.in_reply_to"]?.["m.render_in"] ?? [];
const shouldRenderInTarget = !renderTarget || (renderIn.includes(renderTarget));
return parentExist && shouldRenderInTarget;
}
public static getRenderInMixin(relation?: IEventRelation): string[] | undefined {
if (relation?.rel_type === RelationType.Thread) {
return [RelationType.Thread];
}
}
componentDidMount() {
this.initialize();
this.trySetExpandableQuotes();
@ -296,7 +125,7 @@ export default class ReplyChain extends React.Component<IProps, IState> {
private async initialize(): Promise<void> {
const { parentEv } = this.props;
// at time of making this component we checked that props.parentEv has a parentEventId
const ev = await this.getEvent(ReplyChain.getParentEventId(parentEv));
const ev = await this.getEvent(getParentEventId(parentEv));
if (this.unmounted) return;
@ -314,7 +143,7 @@ export default class ReplyChain extends React.Component<IProps, IState> {
private async getNextEvent(ev: MatrixEvent): Promise<MatrixEvent> {
try {
const inReplyToEventId = ReplyChain.getParentEventId(ev);
const inReplyToEventId = getParentEventId(ev);
return await this.getEvent(inReplyToEventId);
} catch (e) {
return null;
@ -399,7 +228,7 @@ export default class ReplyChain extends React.Component<IProps, IState> {
}
</blockquote>;
} else if (this.props.forExport) {
const eventId = ReplyChain.getParentEventId(this.props.parentEv);
const eventId = getParentEventId(this.props.parentEv);
header = <p className="mx_ReplyChain_Export">
{ _t("In reply to <a>this message</a>",
{},