mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-22 19:08:58 +03:00
Merge pull request #552 from Jedi18/forward_message_feature
Forward Message
This commit is contained in:
commit
1e3f0f3b26
12 changed files with 343 additions and 37 deletions
117
resources/qml/ForwardCompleter.qml
Normal file
117
resources/qml/ForwardCompleter.qml
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import "./delegates/"
|
||||||
|
import QtQuick 2.9
|
||||||
|
import QtQuick.Controls 2.3
|
||||||
|
import im.nheko 1.0
|
||||||
|
|
||||||
|
Popup {
|
||||||
|
id: forwardMessagePopup
|
||||||
|
|
||||||
|
property var mid
|
||||||
|
|
||||||
|
function setMessageEventId(mid_in) {
|
||||||
|
mid = mid_in;
|
||||||
|
}
|
||||||
|
|
||||||
|
x: Math.round(parent.width / 2 - width / 2)
|
||||||
|
y: Math.round(parent.height / 2 - height / 2)
|
||||||
|
modal: true
|
||||||
|
palette: colors
|
||||||
|
parent: Overlay.overlay
|
||||||
|
width: implicitWidth >= (timelineRoot.width * 0.8) ? implicitWidth : (timelineRoot.width * 0.8)
|
||||||
|
height: implicitHeight + completerPopup.height + padding * 2
|
||||||
|
leftPadding: 10
|
||||||
|
rightPadding: 10
|
||||||
|
background: Rectangle {
|
||||||
|
color: colors.window
|
||||||
|
}
|
||||||
|
|
||||||
|
onOpened: {
|
||||||
|
completerPopup.open();
|
||||||
|
roomTextInput.forceActiveFocus();
|
||||||
|
}
|
||||||
|
onClosed: {
|
||||||
|
completerPopup.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: forwardColumn
|
||||||
|
|
||||||
|
spacing: 5
|
||||||
|
|
||||||
|
Label {
|
||||||
|
id: titleLabel
|
||||||
|
|
||||||
|
text: qsTr("Forward Message")
|
||||||
|
font.bold: true
|
||||||
|
bottomPadding: 10
|
||||||
|
color: colors.text
|
||||||
|
}
|
||||||
|
|
||||||
|
Reply {
|
||||||
|
id: replyPreview
|
||||||
|
|
||||||
|
modelData: TimelineManager.timeline ? TimelineManager.timeline.getDump(mid, "") : {
|
||||||
|
}
|
||||||
|
userColor: TimelineManager.userColor(modelData.userId, colors.window)
|
||||||
|
}
|
||||||
|
|
||||||
|
MatrixTextField {
|
||||||
|
id: roomTextInput
|
||||||
|
|
||||||
|
width: forwardMessagePopup.width - forwardMessagePopup.leftPadding * 2
|
||||||
|
color: colors.text
|
||||||
|
onTextEdited: {
|
||||||
|
completerPopup.completer.searchString = text;
|
||||||
|
}
|
||||||
|
Keys.onPressed: {
|
||||||
|
if (event.key == Qt.Key_Up && completerPopup.opened) {
|
||||||
|
event.accepted = true;
|
||||||
|
completerPopup.up();
|
||||||
|
} else if (event.key == Qt.Key_Down && completerPopup.opened) {
|
||||||
|
event.accepted = true;
|
||||||
|
completerPopup.down();
|
||||||
|
} else if (event.matches(StandardKey.InsertParagraphSeparator)) {
|
||||||
|
completerPopup.finishCompletion();
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Completer {
|
||||||
|
id: completerPopup
|
||||||
|
|
||||||
|
y: titleLabel.height + replyPreview.height + roomTextInput.height + roomTextInput.bottomPadding + forwardColumn.spacing * 3
|
||||||
|
width: forwardMessagePopup.width - forwardMessagePopup.leftPadding * 2
|
||||||
|
completerName: "room"
|
||||||
|
fullWidth: true
|
||||||
|
centerRowContent: false
|
||||||
|
avatarHeight: 24
|
||||||
|
avatarWidth: 24
|
||||||
|
bottomToTop: false
|
||||||
|
closePolicy: Popup.NoAutoClose
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
onCompletionSelected: {
|
||||||
|
TimelineManager.timeline.forwardMessage(messageContextMenu.eventId, id);
|
||||||
|
forwardMessagePopup.close();
|
||||||
|
}
|
||||||
|
onCountChanged: {
|
||||||
|
if (completerPopup.count > 0 && (completerPopup.currentIndex < 0 || completerPopup.currentIndex >= completerPopup.count))
|
||||||
|
completerPopup.currentIndex = 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
target: completerPopup
|
||||||
|
}
|
||||||
|
|
||||||
|
Overlay.modal: Rectangle {
|
||||||
|
color: Qt.rgba(colors.window.r, colors.window.g, colors.window.b, 0.7)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -71,6 +71,14 @@ Page {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: forwardCompleterComponent
|
||||||
|
|
||||||
|
ForwardCompleter {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
Shortcut {
|
Shortcut {
|
||||||
sequence: "Ctrl+K"
|
sequence: "Ctrl+K"
|
||||||
onActivated: {
|
onActivated: {
|
||||||
|
@ -125,6 +133,16 @@ Page {
|
||||||
onTriggered: TimelineManager.timeline.readReceiptsAction(messageContextMenu.eventId)
|
onTriggered: TimelineManager.timeline.readReceiptsAction(messageContextMenu.eventId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Platform.MenuItem {
|
||||||
|
visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker || messageContextMenu.eventType == MtxEvent.TextMessage || messageContextMenu.eventType == MtxEvent.LocationMessage || messageContextMenu.eventType == MtxEvent.EmoteMessage || messageContextMenu.eventType == MtxEvent.NoticeMessage
|
||||||
|
text: qsTr("Forward")
|
||||||
|
onTriggered: {
|
||||||
|
var forwardMess = forwardCompleterComponent.createObject(timelineRoot);
|
||||||
|
forwardMess.setMessageEventId(messageContextMenu.eventId);
|
||||||
|
forwardMess.open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Platform.MenuItem {
|
Platform.MenuItem {
|
||||||
text: qsTr("Mark as read")
|
text: qsTr("Mark as read")
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,6 +142,7 @@
|
||||||
<file>qml/TimelineRow.qml</file>
|
<file>qml/TimelineRow.qml</file>
|
||||||
<file>qml/TopBar.qml</file>
|
<file>qml/TopBar.qml</file>
|
||||||
<file>qml/QuickSwitcher.qml</file>
|
<file>qml/QuickSwitcher.qml</file>
|
||||||
|
<file>qml/ForwardCompleter.qml</file>
|
||||||
<file>qml/TypingIndicator.qml</file>
|
<file>qml/TypingIndicator.qml</file>
|
||||||
<file>qml/RoomSettings.qml</file>
|
<file>qml/RoomSettings.qml</file>
|
||||||
<file>qml/emoji/EmojiButton.qml</file>
|
<file>qml/emoji/EmojiButton.qml</file>
|
||||||
|
|
|
@ -11,32 +11,8 @@
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
struct nonesuch
|
|
||||||
{
|
|
||||||
~nonesuch() = delete;
|
|
||||||
nonesuch(nonesuch const &) = delete;
|
|
||||||
void operator=(nonesuch const &) = delete;
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace detail {
|
|
||||||
template<class Default, class AlwaysVoid, template<class...> class Op, class... Args>
|
|
||||||
struct detector
|
|
||||||
{
|
|
||||||
using value_t = std::false_type;
|
|
||||||
using type = Default;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<class Default, template<class...> class Op, class... Args>
|
|
||||||
struct detector<Default, std::void_t<Op<Args...>>, Op, Args...>
|
|
||||||
{
|
|
||||||
using value_t = std::true_type;
|
|
||||||
using type = Op<Args...>;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace detail
|
|
||||||
|
|
||||||
template<template<class...> class Op, class... Args>
|
template<template<class...> class Op, class... Args>
|
||||||
using is_detected = typename detail::detector<nonesuch, void, Op, Args...>::value_t;
|
using is_detected = typename nheko::detail::detector<nheko::nonesuch, void, Op, Args...>::value_t;
|
||||||
|
|
||||||
struct IsStateEvent
|
struct IsStateEvent
|
||||||
{
|
{
|
||||||
|
|
|
@ -11,6 +11,32 @@
|
||||||
|
|
||||||
#include <mtx/events/collections.hpp>
|
#include <mtx/events/collections.hpp>
|
||||||
|
|
||||||
|
namespace nheko {
|
||||||
|
struct nonesuch
|
||||||
|
{
|
||||||
|
~nonesuch() = delete;
|
||||||
|
nonesuch(nonesuch const &) = delete;
|
||||||
|
void operator=(nonesuch const &) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
template<class Default, class AlwaysVoid, template<class...> class Op, class... Args>
|
||||||
|
struct detector
|
||||||
|
{
|
||||||
|
using value_t = std::false_type;
|
||||||
|
using type = Default;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class Default, template<class...> class Op, class... Args>
|
||||||
|
struct detector<Default, std::void_t<Op<Args...>>, Op, Args...>
|
||||||
|
{
|
||||||
|
using value_t = std::true_type;
|
||||||
|
using type = Op<Args...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
}
|
||||||
|
|
||||||
namespace mtx::accessors {
|
namespace mtx::accessors {
|
||||||
std::string
|
std::string
|
||||||
event_id(const mtx::events::collections::TimelineEvents &event);
|
event_id(const mtx::events::collections::TimelineEvents &event);
|
||||||
|
|
|
@ -52,6 +52,28 @@ createDescriptionInfo(const Event &event, const QString &localUser, const QStrin
|
||||||
ts};
|
ts};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string
|
||||||
|
utils::stripReplyFromBody(const std::string &bodyi)
|
||||||
|
{
|
||||||
|
QString body = QString::fromStdString(bodyi);
|
||||||
|
QRegularExpression plainQuote("^>.*?$\n?", QRegularExpression::MultilineOption);
|
||||||
|
while (body.startsWith(">"))
|
||||||
|
body.remove(plainQuote);
|
||||||
|
if (body.startsWith("\n"))
|
||||||
|
body.remove(0, 1);
|
||||||
|
return body.toStdString();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string
|
||||||
|
utils::stripReplyFromFormattedBody(const std::string &formatted_bodyi)
|
||||||
|
{
|
||||||
|
QString formatted_body = QString::fromStdString(formatted_bodyi);
|
||||||
|
formatted_body.remove(QRegularExpression("<mx-reply>.*</mx-reply>",
|
||||||
|
QRegularExpression::DotMatchesEverythingOption));
|
||||||
|
formatted_body.replace("@room", "@\u2060aroom");
|
||||||
|
return formatted_body.toStdString();
|
||||||
|
}
|
||||||
|
|
||||||
RelatedInfo
|
RelatedInfo
|
||||||
utils::stripReplyFallbacks(const TimelineEvent &event, std::string id, QString room_id_)
|
utils::stripReplyFallbacks(const TimelineEvent &event, std::string id, QString room_id_)
|
||||||
{
|
{
|
||||||
|
@ -63,19 +85,15 @@ utils::stripReplyFallbacks(const TimelineEvent &event, std::string id, QString r
|
||||||
// get body, strip reply fallback, then transform the event to text, if it is a media event
|
// get body, strip reply fallback, then transform the event to text, if it is a media event
|
||||||
// etc
|
// etc
|
||||||
related.quoted_body = QString::fromStdString(mtx::accessors::body(event));
|
related.quoted_body = QString::fromStdString(mtx::accessors::body(event));
|
||||||
QRegularExpression plainQuote("^>.*?$\n?", QRegularExpression::MultilineOption);
|
related.quoted_body =
|
||||||
while (related.quoted_body.startsWith(">"))
|
QString::fromStdString(stripReplyFromBody(related.quoted_body.toStdString()));
|
||||||
related.quoted_body.remove(plainQuote);
|
|
||||||
if (related.quoted_body.startsWith("\n"))
|
|
||||||
related.quoted_body.remove(0, 1);
|
|
||||||
related.quoted_body = utils::getQuoteBody(related);
|
related.quoted_body = utils::getQuoteBody(related);
|
||||||
related.quoted_body.replace("@room", QString::fromUtf8("@\u2060room"));
|
related.quoted_body.replace("@room", QString::fromUtf8("@\u2060room"));
|
||||||
|
|
||||||
// get quoted body and strip reply fallback
|
// get quoted body and strip reply fallback
|
||||||
related.quoted_formatted_body = mtx::accessors::formattedBodyWithFallback(event);
|
related.quoted_formatted_body = mtx::accessors::formattedBodyWithFallback(event);
|
||||||
related.quoted_formatted_body.remove(QRegularExpression(
|
related.quoted_formatted_body = QString::fromStdString(
|
||||||
"<mx-reply>.*</mx-reply>", QRegularExpression::DotMatchesEverythingOption));
|
stripReplyFromFormattedBody(related.quoted_formatted_body.toStdString()));
|
||||||
related.quoted_formatted_body.replace("@room", "@\u2060aroom");
|
|
||||||
related.room = room_id_;
|
related.room = room_id_;
|
||||||
|
|
||||||
return related;
|
return related;
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
|
#include <QRegularExpression>
|
||||||
#include <mtx/events/collections.hpp>
|
#include <mtx/events/collections.hpp>
|
||||||
#include <mtx/events/common.hpp>
|
#include <mtx/events/common.hpp>
|
||||||
|
|
||||||
|
@ -40,6 +41,14 @@ namespace utils {
|
||||||
|
|
||||||
using TimelineEvent = mtx::events::collections::TimelineEvents;
|
using TimelineEvent = mtx::events::collections::TimelineEvents;
|
||||||
|
|
||||||
|
//! Helper function to remove reply fallback from body
|
||||||
|
std::string
|
||||||
|
stripReplyFromBody(const std::string &body);
|
||||||
|
|
||||||
|
//! Helper function to remove reply fallback from formatted body
|
||||||
|
std::string
|
||||||
|
stripReplyFromFormattedBody(const std::string &formatted_body);
|
||||||
|
|
||||||
RelatedInfo
|
RelatedInfo
|
||||||
stripReplyFallbacks(const TimelineEvent &event, std::string id, QString room_id_);
|
stripReplyFallbacks(const TimelineEvent &event, std::string id, QString room_id_);
|
||||||
|
|
||||||
|
|
|
@ -826,6 +826,16 @@ TimelineModel::viewRawMessage(QString id) const
|
||||||
Q_UNUSED(dialog);
|
Q_UNUSED(dialog);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TimelineModel::forwardMessage(QString eventId, QString roomId)
|
||||||
|
{
|
||||||
|
auto e = events.get(eventId.toStdString(), "");
|
||||||
|
if (!e)
|
||||||
|
return;
|
||||||
|
|
||||||
|
emit forwardToRoom(e, roomId);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
TimelineModel::viewDecryptedRawMessage(QString id) const
|
TimelineModel::viewDecryptedRawMessage(QString id) const
|
||||||
{
|
{
|
||||||
|
|
|
@ -219,6 +219,7 @@ public:
|
||||||
Q_INVOKABLE QString formatPowerLevelEvent(QString id);
|
Q_INVOKABLE QString formatPowerLevelEvent(QString id);
|
||||||
|
|
||||||
Q_INVOKABLE void viewRawMessage(QString id) const;
|
Q_INVOKABLE void viewRawMessage(QString id) const;
|
||||||
|
Q_INVOKABLE void forwardMessage(QString eventId, QString roomId);
|
||||||
Q_INVOKABLE void viewDecryptedRawMessage(QString id) const;
|
Q_INVOKABLE void viewDecryptedRawMessage(QString id) const;
|
||||||
Q_INVOKABLE void openUserProfile(QString userid, bool global = false);
|
Q_INVOKABLE void openUserProfile(QString userid, bool global = false);
|
||||||
Q_INVOKABLE void openRoomSettings();
|
Q_INVOKABLE void openRoomSettings();
|
||||||
|
@ -322,6 +323,7 @@ signals:
|
||||||
void roomNameChanged();
|
void roomNameChanged();
|
||||||
void roomTopicChanged();
|
void roomTopicChanged();
|
||||||
void roomAvatarUrlChanged();
|
void roomAvatarUrlChanged();
|
||||||
|
void forwardToRoom(mtx::events::collections::TimelineEvents *e, QString roomId);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
template<typename T>
|
template<typename T>
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
#include "CompletionProxyModel.h"
|
#include "CompletionProxyModel.h"
|
||||||
#include "DelegateChooser.h"
|
#include "DelegateChooser.h"
|
||||||
#include "DeviceVerificationFlow.h"
|
#include "DeviceVerificationFlow.h"
|
||||||
|
#include "EventAccessors.h"
|
||||||
#include "Logging.h"
|
#include "Logging.h"
|
||||||
#include "MainWindow.h"
|
#include "MainWindow.h"
|
||||||
#include "MatrixClient.h"
|
#include "MatrixClient.h"
|
||||||
|
@ -31,13 +32,59 @@
|
||||||
#include "ui/NhekoCursorShape.h"
|
#include "ui/NhekoCursorShape.h"
|
||||||
#include "ui/NhekoDropArea.h"
|
#include "ui/NhekoDropArea.h"
|
||||||
|
|
||||||
#include <iostream> //only for debugging
|
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(mtx::events::collections::TimelineEvents)
|
Q_DECLARE_METATYPE(mtx::events::collections::TimelineEvents)
|
||||||
Q_DECLARE_METATYPE(std::vector<DeviceInfo>)
|
Q_DECLARE_METATYPE(std::vector<DeviceInfo>)
|
||||||
|
|
||||||
namespace msgs = mtx::events::msg;
|
namespace msgs = mtx::events::msg;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
template<template<class...> class Op, class... Args>
|
||||||
|
using is_detected = typename nheko::detail::detector<nheko::nonesuch, void, Op, Args...>::value_t;
|
||||||
|
|
||||||
|
template<class Content>
|
||||||
|
using file_t = decltype(Content::file);
|
||||||
|
|
||||||
|
template<class Content>
|
||||||
|
using url_t = decltype(Content::url);
|
||||||
|
|
||||||
|
template<class Content>
|
||||||
|
using body_t = decltype(Content::body);
|
||||||
|
|
||||||
|
template<class Content>
|
||||||
|
using formatted_body_t = decltype(Content::formatted_body);
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
static constexpr bool
|
||||||
|
messageWithFileAndUrl(const mtx::events::Event<T> &)
|
||||||
|
{
|
||||||
|
return is_detected<file_t, T>::value && is_detected<url_t, T>::value;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
static constexpr void
|
||||||
|
removeReplyFallback(mtx::events::Event<T> &e)
|
||||||
|
{
|
||||||
|
if constexpr (is_detected<body_t, T>::value) {
|
||||||
|
if constexpr (std::is_same_v<std::optional<std::string>,
|
||||||
|
std::remove_cv_t<decltype(e.content.body)>>) {
|
||||||
|
if (e.content.body) {
|
||||||
|
e.content.body = utils::stripReplyFromBody(e.content.body);
|
||||||
|
}
|
||||||
|
} else if constexpr (std::is_same_v<std::string,
|
||||||
|
std::remove_cv_t<decltype(e.content.body)>>) {
|
||||||
|
e.content.body = utils::stripReplyFromBody(e.content.body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if constexpr (is_detected<formatted_body_t, T>::value) {
|
||||||
|
if (e.content.format == "org.matrix.custom.html") {
|
||||||
|
e.content.formatted_body =
|
||||||
|
utils::stripReplyFromFormattedBody(e.content.formatted_body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
TimelineViewManager::updateEncryptedDescriptions()
|
TimelineViewManager::updateEncryptedDescriptions()
|
||||||
{
|
{
|
||||||
|
@ -329,6 +376,10 @@ TimelineViewManager::addRoom(const QString &room_id)
|
||||||
&TimelineModel::newEncryptedImage,
|
&TimelineModel::newEncryptedImage,
|
||||||
imgProvider,
|
imgProvider,
|
||||||
&MxcImageProvider::addEncryptionInfo);
|
&MxcImageProvider::addEncryptionInfo);
|
||||||
|
connect(newRoom.data(),
|
||||||
|
&TimelineModel::forwardToRoom,
|
||||||
|
this,
|
||||||
|
&TimelineViewManager::forwardMessageToRoom);
|
||||||
models.insert(room_id, std::move(newRoom));
|
models.insert(room_id, std::move(newRoom));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -616,3 +667,80 @@ TimelineViewManager::focusTimeline()
|
||||||
{
|
{
|
||||||
getWidget()->setFocus();
|
getWidget()->setFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TimelineViewManager::forwardMessageToRoom(mtx::events::collections::TimelineEvents *e,
|
||||||
|
QString roomId)
|
||||||
|
{
|
||||||
|
auto room = models.find(roomId);
|
||||||
|
auto content = mtx::accessors::url(*e);
|
||||||
|
std::optional<mtx::crypto::EncryptedFile> encryptionInfo = mtx::accessors::file(*e);
|
||||||
|
|
||||||
|
if (encryptionInfo) {
|
||||||
|
http::client()->download(
|
||||||
|
content,
|
||||||
|
[this, roomId, e, encryptionInfo](const std::string &res,
|
||||||
|
const std::string &content_type,
|
||||||
|
const std::string &originalFilename,
|
||||||
|
mtx::http::RequestErr err) {
|
||||||
|
if (err)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto data = mtx::crypto::to_string(
|
||||||
|
mtx::crypto::decrypt_file(res, encryptionInfo.value()));
|
||||||
|
|
||||||
|
http::client()->upload(
|
||||||
|
data,
|
||||||
|
content_type,
|
||||||
|
originalFilename,
|
||||||
|
[this, roomId, e](const mtx::responses::ContentURI &res,
|
||||||
|
mtx::http::RequestErr err) mutable {
|
||||||
|
if (err) {
|
||||||
|
nhlog::net()->warn("failed to upload media: {} {} ({})",
|
||||||
|
err->matrix_error.error,
|
||||||
|
to_string(err->matrix_error.errcode),
|
||||||
|
static_cast<int>(err->status_code));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::visit(
|
||||||
|
[this, roomId, url = res.content_uri](auto ev) {
|
||||||
|
if constexpr (mtx::events::message_content_to_type<
|
||||||
|
decltype(ev.content)> ==
|
||||||
|
mtx::events::EventType::RoomMessage) {
|
||||||
|
if constexpr (messageWithFileAndUrl(ev)) {
|
||||||
|
ev.content.relations.relations
|
||||||
|
.clear();
|
||||||
|
ev.content.file.reset();
|
||||||
|
ev.content.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto room = models.find(roomId);
|
||||||
|
removeReplyFallback(ev);
|
||||||
|
ev.content.relations.relations.clear();
|
||||||
|
room.value()->sendMessageEvent(
|
||||||
|
ev.content,
|
||||||
|
mtx::events::EventType::RoomMessage);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
*e);
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::visit(
|
||||||
|
[room](auto e) {
|
||||||
|
if constexpr (mtx::events::message_content_to_type<decltype(e.content)> ==
|
||||||
|
mtx::events::EventType::RoomMessage) {
|
||||||
|
e.content.relations.relations.clear();
|
||||||
|
removeReplyFallback(e);
|
||||||
|
room.value()->sendMessageEvent(e.content,
|
||||||
|
mtx::events::EventType::RoomMessage);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
*e);
|
||||||
|
}
|
||||||
|
|
|
@ -146,6 +146,7 @@ public slots:
|
||||||
|
|
||||||
void backToRooms() { emit showRoomList(); }
|
void backToRooms() { emit showRoomList(); }
|
||||||
QObject *completerFor(QString completerName, QString roomId = "");
|
QObject *completerFor(QString completerName, QString roomId = "");
|
||||||
|
void forwardMessageToRoom(mtx::events::collections::TimelineEvents *e, QString roomId);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void openImageOverlayInternal(QString eventId, QImage img);
|
void openImageOverlayInternal(QString eventId, QImage img);
|
||||||
|
|
Loading…
Reference in a new issue