summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorKonstantinos Sideris <sideris.konstantin@gmail.com>2018-03-17 21:23:46 +0200
committerKonstantinos Sideris <sideris.konstantin@gmail.com>2018-03-17 21:23:46 +0200
commita0ae6cf5d583bb650b7164dbe602b7552a201814 (patch)
treec831eba0640000f1fd767122d35932d82a1f3987 /src
parentUpdate issue template (diff)
downloadnheko-a0ae6cf5d583bb650b7164dbe602b7552a201814.tar.xz
Add ability to redact messages
Diffstat (limited to 'src')
-rw-r--r--src/ChatPage.cc3
-rw-r--r--src/MatrixClient.cc44
-rw-r--r--src/timeline/TimelineItem.cc101
-rw-r--r--src/timeline/TimelineView.cc45
-rw-r--r--src/timeline/TimelineViewManager.cc10
5 files changed, 166 insertions, 37 deletions
diff --git a/src/ChatPage.cc b/src/ChatPage.cc

index fee4f982..158427fd 100644 --- a/src/ChatPage.cc +++ b/src/ChatPage.cc
@@ -342,6 +342,9 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, emit showNotification(QString("Room %1 created").arg(room_id)); }); connect(client_.data(), &MatrixClient::leftRoom, this, &ChatPage::removeRoom); + connect(client_.data(), &MatrixClient::redactionFailed, this, [this](const QString &error) { + emit showNotification(QString("Message redaction failed: %1").arg(error)); + }); showContentTimer_ = new QTimer(this); showContentTimer_->setSingleShot(true); diff --git a/src/MatrixClient.cc b/src/MatrixClient.cc
index 4a4fc67c..5f9e1b86 100644 --- a/src/MatrixClient.cc +++ b/src/MatrixClient.cc
@@ -1281,3 +1281,47 @@ MatrixClient::getUploadReply(QNetworkReply *reply) return object; } + +void +MatrixClient::redactEvent(const QString &room_id, const QString &event_id) +{ + QUrlQuery query; + query.addQueryItem("access_token", token_); + + QUrl endpoint(server_); + endpoint.setPath(clientApiUrl_ + QString("/rooms/%1/redact/%2/%3") + .arg(room_id) + .arg(event_id) + .arg(incrementTransactionId())); + endpoint.setQuery(query); + + QNetworkRequest request(QString(endpoint.toEncoded())); + request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); + + // TODO: no reason specified + QJsonObject body{}; + auto reply = put(request, QJsonDocument(body).toJson(QJsonDocument::Compact)); + + connect(reply, &QNetworkReply::finished, this, [reply, this, room_id, event_id]() { + reply->deleteLater(); + + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + auto data = reply->readAll(); + + if (status == 0 || status >= 400) { + try { + mtx::errors::Error res = nlohmann::json::parse(data); + emit redactionFailed(QString::fromStdString(res.error)); + return; + } catch (const std::exception &) { + } + } + + try { + mtx::responses::EventId res = nlohmann::json::parse(data); + emit redactionCompleted(room_id, event_id); + } catch (const std::exception &e) { + emit redactionFailed(QString::fromStdString(e.what())); + } + }); +} diff --git a/src/timeline/TimelineItem.cc b/src/timeline/TimelineItem.cc
index 2f04a1bd..371ced10 100644 --- a/src/timeline/TimelineItem.cc +++ b/src/timeline/TimelineItem.cc
@@ -40,29 +40,39 @@ TimelineItem::init() body_ = nullptr; font_.setPixelSize(conf::fontSize); + usernameFont_ = font_; + usernameFont_.setWeight(60); QFontMetrics fm(font_); contextMenu_ = new QMenu(this); showReadReceipts_ = new QAction("Read receipts", this); markAsRead_ = new QAction("Mark as read", this); + redactMsg_ = new QAction("Redact message", this); contextMenu_->addAction(showReadReceipts_); contextMenu_->addAction(markAsRead_); + contextMenu_->addAction(redactMsg_); connect(showReadReceipts_, &QAction::triggered, this, [this]() { if (!event_id_.isEmpty()) ChatPage::instance()->showReadReceipts(event_id_); }); + connect(redactMsg_, &QAction::triggered, this, [this]() { + if (!event_id_.isEmpty()) + ChatPage::instance()->redactEvent(room_id_, event_id_); + }); + connect(markAsRead_, &QAction::triggered, this, [this]() { sendReadReceipt(); }); topLayout_ = new QHBoxLayout(this); mainLayout_ = new QVBoxLayout; messageLayout_ = new QHBoxLayout; + messageLayout_->setContentsMargins(0, 0, 20, 4); + messageLayout_->setSpacing(20); topLayout_->setContentsMargins(conf::timeline::msgMargin, conf::timeline::msgMargin, 0, 0); topLayout_->setSpacing(0); - topLayout_->addLayout(mainLayout_, 1); mainLayout_->setContentsMargins(conf::timeline::headerLeftMargin, 0, 0, 0); @@ -73,7 +83,7 @@ TimelineItem::init() // Setting fixed width for checkmark because systems may have a differing width for a // space and the Unicode checkmark. - checkmark_ = new QLabel(" ", this); + checkmark_ = new QLabel(this); checkmark_->setFont(checkmarkFont); checkmark_->setFixedWidth(QFontMetrics{checkmarkFont}.width(CHECKMARK)); } @@ -106,9 +116,6 @@ TimelineItem::TimelineItem(mtx::events::MessageType ty, body.replace("\n", "<br/>"); generateTimestamp(timestamp); - messageLayout_->setContentsMargins(0, 0, 20, 4); - messageLayout_->setSpacing(20); - if (withSender) { generateBody(displayName, body); setupAvatarLayout(displayName); @@ -240,9 +247,6 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Notice body.replace("\n", "<br/>"); body = "<i>" + body + "</i>"; - messageLayout_->setContentsMargins(0, 0, 20, 4); - messageLayout_->setSpacing(20); - if (with_sender) { auto displayName = TimelineViewManager::displayName(sender); @@ -289,9 +293,6 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Emote> emoteMsg.replace(conf::strings::url_regex, conf::strings::url_html); emoteMsg.replace("\n", "<br/>"); - messageLayout_->setContentsMargins(0, 0, 20, 4); - messageLayout_->setSpacing(20); - if (with_sender) { generateBody(displayName, emoteMsg); setupAvatarLayout(displayName); @@ -341,9 +342,6 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Text> body.replace(conf::strings::url_regex, conf::strings::url_html); body.replace("\n", "<br/>"); - messageLayout_->setContentsMargins(0, 0, 20, 4); - messageLayout_->setSpacing(20); - if (with_sender) { generateBody(displayName, body); setupAvatarLayout(displayName); @@ -400,25 +398,13 @@ TimelineItem::generateBody(const QString &userid, const QString &body) sender = userid.split(":")[0].split("@")[1]; } - QFont usernameFont = font_; - usernameFont.setWeight(60); - - QFontMetrics fm(usernameFont); + QFontMetrics fm(usernameFont_); userName_ = new QLabel(this); - userName_->setFont(usernameFont); + userName_->setFont(usernameFont_); userName_->setText(fm.elidedText(sender, Qt::ElideRight, 500)); - if (body.isEmpty()) - return; - - body_ = new QLabel(this); - body_->setFont(font_); - body_->setWordWrap(true); - body_->setText(QString("<span>%1</span>").arg(replaceEmoji(body))); - body_->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextBrowserInteraction); - body_->setOpenExternalLinks(true); - body_->setMargin(0); + generateBody(body); } void @@ -474,12 +460,8 @@ TimelineItem::setupAvatarLayout(const QString &userName) if (userName[0] == '@' && userName.size() > 1) userAvatar_->setLetter(QChar(userName[1]).toUpper()); - sideLayout_ = new QVBoxLayout; - sideLayout_->setMargin(0); - sideLayout_->setSpacing(0); - sideLayout_->addWidget(userAvatar_); - sideLayout_->addStretch(1); - topLayout_->insertLayout(0, sideLayout_); + topLayout_->insertWidget(0, userAvatar_); + topLayout_->setAlignment(userAvatar_, Qt::AlignTop); headerLayout_ = new QVBoxLayout; headerLayout_->setMargin(0); @@ -492,8 +474,8 @@ TimelineItem::setupAvatarLayout(const QString &userName) void TimelineItem::setupSimpleLayout() { - topLayout_->setContentsMargins(conf::timeline::avatarSize + conf::timeline::msgMargin + 1, - conf::timeline::msgMargin / 3, + topLayout_->setContentsMargins(conf::timeline::msgMargin + conf::timeline::avatarSize + 2, + conf::timeline::msgMargin, 0, 0); } @@ -533,3 +515,48 @@ TimelineItem::addSaveImageAction(ImageItem *image) connect(saveImage, &QAction::triggered, image, &ImageItem::saveAs); } } + +void +TimelineItem::addAvatar() +{ + if (userAvatar_) + return; + + // TODO: should be replaced with the proper event struct. + auto userid = descriptionMsg_.userid; + auto displayName = TimelineViewManager::displayName(userid); + + QFontMetrics fm(usernameFont_); + userName_ = new QLabel(this); + userName_->setFont(usernameFont_); + userName_->setText(fm.elidedText(displayName, Qt::ElideRight, 500)); + + QWidget *widget = nullptr; + + // Extract the widget before we delete its layout. + if (widgetLayout_) + widget = widgetLayout_->itemAt(0)->widget(); + + // Remove all items from the layout. + QLayoutItem *item; + while ((item = messageLayout_->takeAt(0)) != 0) + delete item; + + setupAvatarLayout(displayName); + + // Restore widget's layout. + if (widget) { + widgetLayout_ = new QHBoxLayout(); + widgetLayout_->setContentsMargins(0, 5, 0, 0); + widgetLayout_->addWidget(widget); + widgetLayout_->addStretch(1); + + headerLayout_->addLayout(widgetLayout_); + } + + messageLayout_->addLayout(headerLayout_, 1); + messageLayout_->addWidget(checkmark_); + messageLayout_->addWidget(timestamp_); + + AvatarProvider::resolve(userid, [this](const QImage &img) { setUserAvatar(img); }); +} diff --git a/src/timeline/TimelineView.cc b/src/timeline/TimelineView.cc
index 7e281e03..ded5ad2c 100644 --- a/src/timeline/TimelineView.cc +++ b/src/timeline/TimelineView.cc
@@ -491,6 +491,7 @@ TimelineView::updatePendingMessage(int txn_id, QString event_id) if (msg.widget) { msg.widget->setEventId(event_id); msg.widget->markReceived(); + eventIds_[event_id] = msg.widget; } pending_sent_msgs_.append(msg); @@ -591,6 +592,9 @@ TimelineView::isPendingMessage(const QString &txnid, void TimelineView::removePendingMessage(const QString &txnid) { + if (txnid.isEmpty()) + return; + for (auto it = pending_sent_msgs_.begin(); it != pending_sent_msgs_.end(); ++it) { if (QString::number(it->txn_id) == txnid) { int index = std::distance(pending_sent_msgs_.begin(), it); @@ -739,3 +743,44 @@ TimelineView::toggleScrollDownButton() scrollDownBtn_->hide(); } } + +void +TimelineView::removeEvent(const QString &event_id) +{ + if (!eventIds_.contains(event_id)) { + qWarning() << "unknown event_id couldn't be removed:" << event_id; + return; + } + + auto removedItem = eventIds_[event_id]; + + // Find the next and the previous widgets in the timeline + auto prevItem = qobject_cast<TimelineItem *>(relativeWidget(removedItem, -1)); + auto nextItem = qobject_cast<TimelineItem *>(relativeWidget(removedItem, 1)); + + // If it's a TimelineItem add an avatar. + if (prevItem) + prevItem->addAvatar(); + + if (nextItem) + nextItem->addAvatar(); + + // Finally remove the event. + removedItem->deleteLater(); + eventIds_.remove(event_id); +} + +QWidget * +TimelineView::relativeWidget(TimelineItem *item, int dt) const +{ + int pos = scroll_layout_->indexOf(item); + + if (pos == -1) + return nullptr; + + pos = pos + dt; + + bool isOutOfBounds = (pos <= 0 || pos > scroll_layout_->count() - 1); + + return isOutOfBounds ? nullptr : scroll_layout_->itemAt(pos)->widget(); +} diff --git a/src/timeline/TimelineViewManager.cc b/src/timeline/TimelineViewManager.cc
index ccbf509b..55f25dfc 100644 --- a/src/timeline/TimelineViewManager.cc +++ b/src/timeline/TimelineViewManager.cc
@@ -44,6 +44,16 @@ TimelineViewManager::TimelineViewManager(QSharedPointer<MatrixClient> client, QW &MatrixClient::messageSendFailed, this, &TimelineViewManager::messageSendFailed); + + connect(client_.data(), + &MatrixClient::redactionCompleted, + this, + [this](const QString &room_id, const QString &event_id) { + auto view = views_[room_id]; + + if (view) + view->removeEvent(event_id); + }); } void