diff --git a/src/AliasEditModel.cpp b/src/AliasEditModel.cpp
new file mode 100644
index 00000000..1ca7f5e6
--- /dev/null
+++ b/src/AliasEditModel.cpp
@@ -0,0 +1,336 @@
+// SPDX-FileCopyrightText: 2022 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "AliasEditModel.h"
+
+#include <QSharedPointer>
+
+#include <set>
+
+#include <mtx/responses/common.hpp>
+
+#include "Cache.h"
+#include "Cache_p.h"
+#include "ChatPage.h"
+#include "Logging.h"
+#include "MatrixClient.h"
+#include "timeline/Permissions.h"
+#include "timeline/TimelineModel.h"
+
+AliasEditingModel::AliasEditingModel(const std::string &rid, QObject *parent)
+ : QAbstractListModel(parent)
+ , room_id(rid)
+ , aliasEvent(cache::client()
+ ->getStateEvent<mtx::events::state::CanonicalAlias>(room_id)
+ .value_or(mtx::events::StateEvent<mtx::events::state::CanonicalAlias>{})
+ .content)
+ , canSendStateEvent(
+ Permissions(QString::fromStdString(rid)).canChange(qml_mtx_events::CanonicalAlias))
+{
+ std::set<std::string> seen_aliases;
+
+ if (!aliasEvent.alias.empty()) {
+ aliases.push_back(Entry{aliasEvent.alias, true, true, false});
+ seen_aliases.insert(aliasEvent.alias);
+ }
+
+ for (const auto &alias : aliasEvent.alt_aliases) {
+ if (!seen_aliases.count(alias)) {
+ aliases.push_back(Entry{aliasEvent.alias, false, true, false});
+ seen_aliases.insert(aliasEvent.alias);
+ }
+ }
+
+ for (const auto &alias : aliases) {
+ fetchAliasesStatus(alias.alias);
+ }
+ fetchPublishedAliases();
+}
+
+void
+AliasEditingModel::fetchPublishedAliases()
+{
+ auto job = QSharedPointer<FetchPublishedAliasesJob>::create();
+ connect(job.data(),
+ &FetchPublishedAliasesJob::advertizedAliasesFetched,
+ this,
+ &AliasEditingModel::updatePublishedAliases);
+ http::client()->list_room_aliases(
+ room_id, [job](const mtx::responses::Aliases &aliasesFetched, mtx::http::RequestErr) {
+ emit job->advertizedAliasesFetched(std::move(aliasesFetched.aliases));
+ });
+}
+
+void
+AliasEditingModel::fetchAliasesStatus(const std::string &alias)
+{
+ auto job = QSharedPointer<FetchPublishedAliasesJob>::create();
+ connect(
+ job.data(), &FetchPublishedAliasesJob::aliasFetched, this, &AliasEditingModel::updateAlias);
+ http::client()->resolve_room_alias(
+ alias, [job, alias](const mtx::responses::RoomId &roomIdFetched, mtx::http::RequestErr e) {
+ if (!e)
+ emit job->aliasFetched(alias, std::move(roomIdFetched.room_id));
+ });
+}
+
+QHash<int, QByteArray>
+AliasEditingModel::roleNames() const
+{
+ return {
+ {Name, "name"},
+ {IsPublished, "isPublished"},
+ {IsCanonical, "isCanonical"},
+ {IsAdvertized, "isAdvertized"},
+ };
+}
+
+QVariant
+AliasEditingModel::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid() || index.row() >= aliases.size())
+ return {};
+
+ const auto &entry = aliases.at(index.row());
+
+ switch (role) {
+ case Name:
+ return QString::fromStdString(entry.alias);
+ case IsPublished:
+ return entry.published;
+ case IsCanonical:
+ return entry.canonical;
+ case IsAdvertized:
+ return entry.advertized;
+ }
+
+ return {};
+}
+
+bool
+AliasEditingModel::deleteAlias(int row)
+{
+ if (row < 0 || row >= aliases.size() || aliases.at(row).alias.empty())
+ return false;
+
+ auto alias = aliases.at(row);
+
+ beginRemoveRows(QModelIndex(), row, row);
+ aliases.remove(row);
+ endRemoveRows();
+
+ if (alias.published)
+ http::client()->delete_room_alias(alias.alias, [alias](mtx::http::RequestErr e) {
+ if (e) {
+ nhlog::net()->error("Failed to delete {}: {}", alias.alias, *e);
+ ChatPage::instance()->showNotification(
+ tr("Failed to unpublish alias %1: %2")
+ .arg(QString::fromStdString(alias.alias),
+ QString::fromStdString(e->matrix_error.error)));
+ }
+ });
+
+ if (aliasEvent.alias == alias.alias)
+ aliasEvent.alias.clear();
+
+ for (size_t i = 0; i < aliasEvent.alt_aliases.size(); i++) {
+ if (aliasEvent.alt_aliases[i] == alias.alias) {
+ aliasEvent.alt_aliases.erase(aliasEvent.alt_aliases.begin() + i);
+ break;
+ }
+ }
+
+ return true;
+}
+
+void
+AliasEditingModel::addAlias(QString newAlias)
+{
+ const auto aliasStr = newAlias.toStdString();
+ for (const auto &e : aliases) {
+ if (e.alias == aliasStr) {
+ return;
+ }
+ }
+
+ beginInsertRows(QModelIndex(), aliases.length(), aliases.length());
+ if (aliasEvent.alias.empty())
+ aliasEvent.alias = aliasStr;
+ else
+ aliasEvent.alt_aliases.push_back(aliasStr);
+ aliases.push_back(
+ Entry{aliasStr, aliasEvent.alias.empty() && canSendStateEvent, canSendStateEvent, false});
+ endInsertRows();
+
+ auto job = QSharedPointer<FetchPublishedAliasesJob>::create();
+ connect(
+ job.data(), &FetchPublishedAliasesJob::aliasFetched, this, &AliasEditingModel::updateAlias);
+ auto room = room_id;
+ http::client()->add_room_alias(
+ aliasStr, room_id, [job, aliasStr, room](mtx::http::RequestErr e) {
+ if (e) {
+ nhlog::net()->error("Failed to publish {}: {}", aliasStr, *e);
+ ChatPage::instance()->showNotification(
+ tr("Failed to unpublish alias %1: %2")
+ .arg(QString::fromStdString(aliasStr),
+ QString::fromStdString(e->matrix_error.error)));
+ emit job->aliasFetched(aliasStr, "");
+ } else {
+ emit job->aliasFetched(aliasStr, room);
+ }
+ });
+}
+
+void
+AliasEditingModel::makeCanonical(int row)
+{
+ if (!canSendStateEvent || row < 0 || row >= aliases.size() || aliases.at(row).alias.empty())
+ return;
+
+ auto moveAlias = aliases.at(row).alias;
+
+ if (!aliasEvent.alias.empty()) {
+ for (qsizetype i = 0; i < aliases.size(); i++) {
+ if (moveAlias == aliases[i].alias) {
+ if (aliases[i].canonical) {
+ aliases[i].canonical = false;
+ aliasEvent.alt_aliases.push_back(aliasEvent.alias);
+ emit dataChanged(index(i), index(i), {IsCanonical});
+ }
+ break;
+ }
+ }
+ }
+
+ aliasEvent.alias = moveAlias;
+ for (auto i = aliasEvent.alt_aliases.begin(); i != aliasEvent.alt_aliases.end(); ++i) {
+ if (*i == moveAlias) {
+ aliasEvent.alt_aliases.erase(i);
+ break;
+ }
+ }
+ aliases[row].canonical = true;
+ aliases[row].advertized = true;
+ emit dataChanged(index(row), index(row), {IsCanonical, IsAdvertized});
+}
+
+void
+AliasEditingModel::togglePublish(int row)
+{
+ if (row < 0 || row >= aliases.size() || aliases.at(row).alias.empty())
+ return;
+ auto aliasStr = aliases[row].alias;
+
+ auto job = QSharedPointer<FetchPublishedAliasesJob>::create();
+ connect(
+ job.data(), &FetchPublishedAliasesJob::aliasFetched, this, &AliasEditingModel::updateAlias);
+ auto room = room_id;
+ if (!aliases[row].published)
+ http::client()->add_room_alias(
+ aliasStr, room_id, [job, aliasStr, room](mtx::http::RequestErr e) {
+ if (e) {
+ nhlog::net()->error("Failed to publish {}: {}", aliasStr, *e);
+ ChatPage::instance()->showNotification(
+ tr("Failed to unpublish alias %1: %2")
+ .arg(QString::fromStdString(aliasStr),
+ QString::fromStdString(e->matrix_error.error)));
+ emit job->aliasFetched(aliasStr, "");
+ } else {
+ emit job->aliasFetched(aliasStr, room);
+ }
+ });
+ else
+ http::client()->delete_room_alias(aliasStr, [job, aliasStr, room](mtx::http::RequestErr e) {
+ if (e) {
+ nhlog::net()->error("Failed to unpublish {}: {}", aliasStr, *e);
+ ChatPage::instance()->showNotification(
+ tr("Failed to unpublish alias %1: %2")
+ .arg(QString::fromStdString(aliasStr),
+ QString::fromStdString(e->matrix_error.error)));
+ emit job->aliasFetched(aliasStr, room);
+ } else {
+ emit job->aliasFetched(aliasStr, "");
+ }
+ });
+}
+
+void
+AliasEditingModel::toggleAdvertize(int row)
+{
+ if (!canSendStateEvent || row < 0 || row >= aliases.size() || aliases.at(row).alias.empty())
+ return;
+
+ auto &moveAlias = aliases[row];
+ if (aliasEvent.alias == moveAlias.alias) {
+ moveAlias.canonical = false;
+ moveAlias.advertized = false;
+ aliasEvent.alias.clear();
+ emit dataChanged(index(row), index(row), {IsAdvertized, IsCanonical});
+ } else if (moveAlias.advertized) {
+ for (auto i = aliasEvent.alt_aliases.begin(); i != aliasEvent.alt_aliases.end(); ++i) {
+ if (*i == moveAlias.alias) {
+ aliasEvent.alt_aliases.erase(i);
+ moveAlias.advertized = false;
+ emit dataChanged(index(row), index(row), {IsAdvertized});
+ break;
+ }
+ }
+ } else {
+ aliasEvent.alt_aliases.push_back(moveAlias.alias);
+ moveAlias.advertized = true;
+ emit dataChanged(index(row), index(row), {IsAdvertized});
+ }
+}
+
+void
+AliasEditingModel::updateAlias(std::string alias, std::string target)
+{
+ for (qsizetype i = 0; i < aliases.size(); i++) {
+ auto &e = aliases[i];
+ if (e.alias == alias) {
+ e.published = (target == room_id);
+ emit dataChanged(index(i), index(i), {IsPublished});
+ }
+ }
+}
+
+void
+AliasEditingModel::updatePublishedAliases(std::vector<std::string> advAliases)
+{
+ for (const auto &advAlias : advAliases) {
+ bool found = false;
+ for (qsizetype i = 0; i < aliases.size(); i++) {
+ auto &alias = aliases[i];
+ if (alias.alias == advAlias) {
+ alias.published = true;
+ emit dataChanged(index(i), index(i), {IsPublished});
+ found = true;
+ break;
+ }
+
+ if (!found) {
+ beginInsertRows(QModelIndex(), aliases.size(), aliases.size());
+ aliases.push_back(Entry{advAlias, false, false, true});
+ endInsertRows();
+ }
+ }
+ }
+}
+
+void
+AliasEditingModel::commit()
+{
+ if (!canSendStateEvent)
+ return;
+
+ http::client()->send_state_event(
+ room_id, aliasEvent, [](const mtx::responses::EventId &, mtx::http::RequestErr e) {
+ if (e) {
+ nhlog::net()->error("Failed to send Alias event: {}", *e);
+ ChatPage::instance()->showNotification(
+ tr("Failed to update aliases: %1")
+ .arg(QString::fromStdString(e->matrix_error.error)));
+ }
+ });
+}
|