diff --git a/src/domain/session/room/timeline/tiles/BaseTextTile.js b/src/domain/session/room/timeline/tiles/BaseTextTile.js index 7c0aa7b0..042747aa 100644 --- a/src/domain/session/room/timeline/tiles/BaseTextTile.js +++ b/src/domain/session/room/timeline/tiles/BaseTextTile.js @@ -55,25 +55,45 @@ export class BaseTextTile extends BaseMessageTile { this._messageBody = this._parseBody(body, format); this._format = format; } - // console.log("messageBody", this._messageBody); return this._messageBody; } get replyPreviewBody() { + if (!this._entry.contextEventId) { + return null; + } const entry = this._entry.contextEntry; - if (!entry) { - return {}; + const error = this._generateError(entry); + if (error?.name === "ContextEntryNotFound") { + return { error }; } const format = entry.content.format === "org.matrix.custom.html" ? BodyFormat.Html : BodyFormat.Plain; const body = entry.content["formatted_body"] ?? entry.content["body"]; return { - body: this._parseBody(body, format), - sender: entry.displayName, + body: body && this._parseBody(body, format), + senderName: entry.displayName, avatar: { avatarColorNumber: getIdentifierColorNumber(entry.sender), avatarLetter: avatarInitials(entry.displayName), avatarUrl: (size) => getAvatarHttpUrl(entry.avatarUrl, size, this.platform, this._mediaRepository) - } + }, + error }; } + + _generateError(entry) { + const createError = (name) => { const e = new Error(); e.name = name; return e;} + if (!entry) { + return createError("ContextEntryNotFound"); + } + else if (entry.decryptionError) { + return entry.decryptionError; + } + else if (entry.isRedacted) { + return createError("MessageRedacted"); + } + else if (!(entry.content["formatted_body"] || entry.content["body"])) { + return createError("MissingBody"); + } + } } diff --git a/src/platform/web/ui/session/room/timeline/ReplyPreviewView.js b/src/platform/web/ui/session/room/timeline/ReplyPreviewView.js new file mode 100644 index 00000000..dbb07579 --- /dev/null +++ b/src/platform/web/ui/session/room/timeline/ReplyPreviewView.js @@ -0,0 +1,61 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import {renderStaticAvatar} from "../../../avatar"; +import {tag} from "../../../general/html"; +import {TemplateView} from "../../../general/TemplateView"; +import {renderPart} from "./TextMessageView.js"; + +export class ReplyPreviewView extends TemplateView { + render(t, vm) { + const replyContainer = vm.error? this._renderError(vm) : this._renderReplyPreview(vm); + return replyContainer; + } + + _renderError({ error, avatar, senderName }) { + const errorMessage = this._getErrorMessage(error); + const reply = avatar && senderName? this._renderReplyHeader(avatar, senderName) : tag.blockquote(); + reply.append(tag.span({ className: "statusMessage" }, errorMessage), tag.br()); + return reply; + } + + _getErrorMessage(error) { + switch (error.name) { + case "ContextEntryNotFound": + case "MissingBody": + return "This message could not be fetched."; + case "MessageRedacted": + return "This message has been deleted."; + default: + return error.message; + } + } + + _renderReplyPreview({ body, avatar, senderName }) { + const reply = this._renderReplyHeader(avatar, senderName); + for (const part of body.parts) { + reply.appendChild(renderPart(part)); + } + return reply; + } + + _renderReplyHeader(avatar, displayName) { + return tag.blockquote([ + tag.a({ className: "link", href: "#" }, "In reply to"), + tag.a({ className: "pill", href: "#" }, [renderStaticAvatar(avatar, 12), displayName]), tag.br() + ]); + } +} diff --git a/src/platform/web/ui/session/room/timeline/TextMessageView.js b/src/platform/web/ui/session/room/timeline/TextMessageView.js index a70aaba2..b0733482 100644 --- a/src/platform/web/ui/session/room/timeline/TextMessageView.js +++ b/src/platform/web/ui/session/room/timeline/TextMessageView.js @@ -14,9 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { renderStaticAvatar } from "../../../avatar"; import {tag, text} from "../../../general/html"; import {BaseMessageView} from "./BaseMessageView.js"; +import {ReplyPreviewView} from "./ReplyPreviewView.js"; export class TextMessageView extends BaseMessageView { renderMessageBody(t, vm) { @@ -25,10 +25,11 @@ export class TextMessageView extends BaseMessageView { className: { "Timeline_messageBody": true, statusMessage: vm => vm.shape === "message-status", - }, - }); + } + }, t.mapView(vm => vm.replyPreviewBody, reply => reply ? new ReplyPreviewView(reply): null)); + t.mapSideEffect(vm => vm.body, body => { - while (container.lastChild) { + while (container.lastChild && container.lastChild.tagName !== "BLOCKQUOTE") { container.removeChild(container.lastChild); } for (const part of body.parts) { @@ -36,20 +37,10 @@ export class TextMessageView extends BaseMessageView { } container.appendChild(time); }); - t.mapSideEffect(vm => vm.replyPreviewBody, ({ body, sender, avatar }) => { - if (!body) { - return; - } - const replyContainer = tag.blockquote([ - tag.a({ className: "link", href: "#" }, "In reply to"), - tag.a({ className: "pill", href: "#" }, [renderStaticAvatar(avatar, 12), sender]), tag.br()]); - for (const part of body.parts) { - replyContainer.appendChild(renderPart(part)); - } - container.insertBefore(replyContainer, container.firstChild); - }); + return container; } + } function renderList(listBlock) { @@ -116,7 +107,7 @@ const formatFunction = { newline: () => tag.br() }; -function renderPart(part) { +export function renderPart(part) { const f = formatFunction[part.type]; if (!f) { return text(`[unknown part type ${part.type}]`);