diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e5cb60609..ca4a9caa2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -19,10 +19,11 @@ build-appimage: - sudo cmake --build build --target install - cd .. - git clone https://github.com/commonmark/cmark.git && cd cmark + - git checkout tags/0.29.0 - cmake . -Bbuild -LA -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=install -DCMARK_SHARED=ON -DCMARK_STATIC=ON -DCMARK_TESTS=OFF - cmake --build build --target install --parallel $(nproc) - cd .. - - cmake . -Bbuild -LA -DUSE_INTREE_LIBQMC=1 -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=install/usr -DOlm_DIR="olm/install/lib/cmake/Olm" -DQt5Keychain_DIR="qtkeychain/install/lib/x86_64-linux-gnu/cmake/Qt5Keychain" -DCMARK_LIBRARY="$PWD/cmark/install/lib/libcmark_static.a" -DCMARK_INCLUDE_DIR="$PWD/cmark/install/include" + - cmake . -Bbuild -LA -DUSE_INTREE_LIBQMC=1 -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=install/usr -DOlm_DIR="olm/install/lib/cmake/Olm" -DQt5Keychain_DIR="qtkeychain/install/lib/x86_64-linux-gnu/cmake/Qt5Keychain" -DCMARK_LIBRARY="$PWD/cmark/install/lib/libcmark.a" -DCMARK_INCLUDE_DIR="$PWD/cmark/install/include" - cmake --build build --target install --parallel $(nproc) - cp install/usr/share/icons/hicolor/256x256/apps/org.eu.encom.spectral.png install/org.eu.encom.spectral.png - linuxdeployqt install/usr/share/applications/org.eu.encom.spectral.desktop -appimage -qmldir=qml -qmldir=imports diff --git a/imports/Spectral/Dialog/RoomSettingsDialog.qml b/imports/Spectral/Dialog/RoomSettingsDialog.qml index 4919c751f..b9a216914 100644 --- a/imports/Spectral/Dialog/RoomSettingsDialog.qml +++ b/imports/Spectral/Dialog/RoomSettingsDialog.qml @@ -9,6 +9,11 @@ import Spectral.Setting 0.1 Dialog { property var room + readonly property bool canChangeAvatar: room.canSendState("m.room.avatar") + readonly property bool canChangeName: room.canSendState("m.room.name") + readonly property bool canChangeTopic: room.canSendState("m.room.topic") + readonly property bool canChangeCanonicalAlias: room.canSendState("m.room.canonical_alias") + anchors.centerIn: parent width: 480 @@ -36,6 +41,8 @@ Dialog { circular: true + enabled: canChangeAvatar + onClicked: { var fileDialog = openFileDialog.createObject(ApplicationWindow.overlay) @@ -61,6 +68,8 @@ Dialog { text: room.name placeholderText: "Room Name" + + enabled: canChangeName } AutoTextField { @@ -70,6 +79,8 @@ Dialog { text: room.topic placeholderText: "Room Topic" + + enabled: canChangeTopic } } } @@ -179,6 +190,8 @@ Dialog { Button { Layout.alignment: Qt.AlignRight + visible: canChangeName || canChangeTopic + text: "Save" highlighted: true @@ -216,6 +229,8 @@ Dialog { id: canonicalAliasComboBox + enabled: canChangeCanonicalAlias + model: room.remoteAliases currentIndex: room.remoteAliases.indexOf(room.canonicalAlias) @@ -275,6 +290,42 @@ Dialog { } } } + + RowLayout { + Layout.fillWidth: true + + Label { + Layout.preferredWidth: 100 + Layout.alignment: Qt.AlignTop + + wrapMode: Label.Wrap + text: "Remote Aliases" + color: MPalette.lighter + } + + ColumnLayout { + Layout.fillWidth: true + + spacing: 0 + + Repeater { + model: { + var localAliases = room.localAliases + var remoteAliases = room.remoteAliases + return remoteAliases.filter(n => !localAliases.includes(n)) + } + + delegate: Label { + width: parent.width + + text: modelData + + font.pixelSize: 12 + color: MPalette.lighter + } + } + } + } } } diff --git a/imports/Spectral/Panel/RoomDrawer.qml b/imports/Spectral/Panel/RoomDrawer.qml index 48561703e..fc7582336 100644 --- a/imports/Spectral/Panel/RoomDrawer.qml +++ b/imports/Spectral/Panel/RoomDrawer.qml @@ -235,6 +235,33 @@ Drawer { text: name color: MPalette.foreground + textFormat: Text.PlainText + elide: Text.ElideRight + wrapMode: Text.NoWrap + } + + Label { + visible: perm != UserType.Member + + text: { + if (perm == UserType.Owner) { + return "Owner" + } + if (perm == UserType.Admin) { + return "Admin" + } + if (perm == UserType.Moderator) { + return "Mod" + } + if (perm == UserType.Muted) { + return "Muted" + } + return "" + } + color: perm == UserType.Muted ? MPalette.lighter : MPalette.accent + font.pixelSize: 12 + textFormat: Text.PlainText + wrapMode: Text.NoWrap } } diff --git a/imports/Spectral/Panel/RoomPanel.qml b/imports/Spectral/Panel/RoomPanel.qml index c85ab5b07..377c6e5ab 100644 --- a/imports/Spectral/Panel/RoomPanel.qml +++ b/imports/Spectral/Panel/RoomPanel.qml @@ -226,6 +226,10 @@ Item { boundsBehavior: Flickable.DragOverBounds + onModelChanged: { + messageListView.positionViewAtBeginning() + } + model: SortFilterProxyModel { id: sortedMessageEventModel @@ -239,7 +243,6 @@ Item { onModelReset: { movingTimer.stop() - messageListView.positionViewAtBeginning() if (currentRoom) { movingTimer.restart() diff --git a/include/libQuotient b/include/libQuotient index 16d670095..e3a5b3a5e 160000 --- a/include/libQuotient +++ b/include/libQuotient @@ -1 +1 @@ -Subproject commit 16d6700950f5f0ebd71481efd5e1a24f04e3c651 +Subproject commit e3a5b3a5e5253fc5ce67574b01e8d25ec14e4d25 diff --git a/src/main.cpp b/src/main.cpp index babe5d8d0..c4a89ccc7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -53,6 +53,7 @@ int main(int argc, char* argv[]) { qmlRegisterUncreatableType("Spectral", 0, 1, "RoomMessageEvent", "ENUM"); qmlRegisterUncreatableType("Spectral", 0, 1, "RoomType", "ENUM"); + qmlRegisterUncreatableType("Spectral", 0, 1, "UserType", "ENUM"); qRegisterMetaType("User*"); qRegisterMetaType("const User*"); diff --git a/src/spectralroom.cpp b/src/spectralroom.cpp index ff2e3c6a4..2e9960cf8 100644 --- a/src/spectralroom.cpp +++ b/src/spectralroom.cpp @@ -21,6 +21,7 @@ #include "events/reactionevent.h" #include "events/roommessageevent.h" #include "events/typingevent.h" +#include "events/roompowerlevelsevent.h" #include "jobs/downloadfilejob.h" #include "user.h" #include "utils.h" @@ -489,20 +490,32 @@ void SpectralRoom::postPlainMessage(const QString& text, if (isReply) { const auto& replyEvt = **replyIt; - QJsonObject json{{"msgtype", msgTypeToString(type)}, - {"body", "> <" + replyEvt.senderId() + "> " + - eventToString(replyEvt) + "\n\n" + text}, - {"format", "org.matrix.custom.html"}, - {"m.relates_to", - QJsonObject{{"m.in_reply_to", - QJsonObject{{"event_id", replyEventId}}}}}, - {"formatted_body", - "
In reply to " + replyEvt.senderId() + - "
" + eventToString(replyEvt, Qt::RichText) + - "
" + text.toHtmlEscaped()}}; + // clang-format off + QJsonObject json{ + {"msgtype", msgTypeToString(type)}, + {"body", "> <" + replyEvt.senderId() + "> " + eventToString(replyEvt) + "\n\n" + text}, + {"format", "org.matrix.custom.html"}, + {"m.relates_to", + { + {"m.in_reply_to", + { + {"event_id", replyEventId} + } + } + } + }, + {"formatted_body", + "
In reply to " + replyEvt.senderId() + + "
" + eventToString(replyEvt, Qt::RichText) + + "
" + text + } + }; + // clang-format on + postJson("m.room.message", json); return; @@ -523,20 +536,32 @@ void SpectralRoom::postHtmlMessage(const QString& text, if (isReply) { const auto& replyEvt = **replyIt; - QJsonObject json{{"msgtype", msgTypeToString(type)}, - {"body", "> <" + replyEvt.senderId() + "> " + - eventToString(replyEvt) + "\n\n" + text}, - {"format", "org.matrix.custom.html"}, - {"m.relates_to", - QJsonObject{{"m.in_reply_to", - QJsonObject{{"event_id", replyEventId}}}}}, - {"formatted_body", - "
In reply to " + replyEvt.senderId() + - "
" + eventToString(replyEvt, Qt::RichText) + - "
" + html}}; + // clang-format off + QJsonObject json{ + {"msgtype", msgTypeToString(type)}, + {"body", "> <" + replyEvt.senderId() + "> " + eventToString(replyEvt) + "\n\n" + text}, + {"format", "org.matrix.custom.html"}, + {"m.relates_to", + { + {"m.in_reply_to", + { + {"event_id", replyEventId} + } + } + } + }, + {"formatted_body", + "
In reply to " + replyEvt.senderId() + + "
" + eventToString(replyEvt, Qt::RichText) + + "
" + html + } + }; + // clang-format on + postJson("m.room.message", json); return; @@ -590,3 +615,19 @@ bool SpectralRoom::containsUser(QString userID) const { return Room::memberJoinState(u) != JoinState::Leave; } + +bool SpectralRoom::canSendEvent(const QString& eventType) const { + auto plEvent = getCurrentState(); + auto pl = plEvent->powerLevelForEvent(eventType); + auto currentPl = plEvent->powerLevelForUser(localUser()->id()); + + return currentPl >= pl; +} + +bool SpectralRoom::canSendState(const QString& eventType) const { + auto plEvent = getCurrentState(); + auto pl = plEvent->powerLevelForState(eventType); + auto currentPl = plEvent->powerLevelForUser(localUser()->id()); + + return currentPl >= pl; +} diff --git a/src/spectralroom.h b/src/spectralroom.h index 8fd0bf8cb..308673502 100644 --- a/src/spectralroom.h +++ b/src/spectralroom.h @@ -75,6 +75,9 @@ class SpectralRoom : public Room { Q_INVOKABLE bool containsUser(QString userID) const; + Q_INVOKABLE bool canSendEvent(const QString& eventType) const; + Q_INVOKABLE bool canSendState(const QString& eventType) const; + private: QString m_cachedInput; QSet highlights; diff --git a/src/userlistmodel.cpp b/src/userlistmodel.cpp index 6ab23ce93..9a24f161a 100644 --- a/src/userlistmodel.cpp +++ b/src/userlistmodel.cpp @@ -4,6 +4,8 @@ #include #include +#include "events/roompowerlevelsevent.h" + #include #include #include @@ -63,7 +65,7 @@ QVariant UserListModel::data(const QModelIndex& index, int role) const { if (index.row() >= m_users.count()) { qDebug() << "UserListModel, something's wrong: index.row() >= m_users.count()"; - return QVariant(); + return {}; } auto user = m_users.at(index.row()); if (role == NameRole) { @@ -78,8 +80,46 @@ QVariant UserListModel::data(const QModelIndex& index, int role) const { if (role == ObjectRole) { return QVariant::fromValue(user); } + if (role == PermRole) { + auto pl = m_currentRoom->getCurrentState(); + auto userPl = pl->powerLevelForUser(user->id()); - return QVariant(); + if (userPl == pl->content().usersDefault) { // Shortcut + return UserType::Member; + } + + if (userPl < pl->powerLevelForState("m.room.message")) { + return UserType::Muted; + } + + auto userPls = pl->users(); + + int highestPl = pl->usersDefault(); + QHash::const_iterator i = userPls.constBegin(); + while (i != userPls.constEnd()) { + if (i.value() > highestPl) { + highestPl = i.value(); + } + + ++i; + } + + if (userPl == highestPl) { + return UserType::Owner; + } + + if (userPl >= pl->powerLevelForState("m.room.power_levels")) { + return UserType::Admin; + } + + if (userPl >= pl->ban() || userPl >= pl->kick() || userPl >= pl->redact()) { + return UserType::Moderator; + } + + return UserType::Member; + } + + return {}; } int UserListModel::rowCount(const QModelIndex& parent) const { @@ -138,6 +178,7 @@ QHash UserListModel::roleNames() const { roles[UserIDRole] = "userId"; roles[AvatarRole] = "avatar"; roles[ObjectRole] = "user"; + roles[PermRole] = "perm"; return roles; } diff --git a/src/userlistmodel.h b/src/userlistmodel.h index 0e83d37b1..c223aed91 100644 --- a/src/userlistmodel.h +++ b/src/userlistmodel.h @@ -12,6 +12,20 @@ class Room; class User; } // namespace Quotient +class UserType : public QObject { + Q_OBJECT + + public: + enum Types { + Owner = 1, + Admin, + Moderator, + Member, + Muted, + }; + Q_ENUMS(Types) +}; + class UserListModel : public QAbstractListModel { Q_OBJECT Q_PROPERTY( @@ -21,16 +35,15 @@ class UserListModel : public QAbstractListModel { NameRole = Qt::UserRole + 1, UserIDRole, AvatarRole, - ObjectRole + ObjectRole, + PermRole, }; - using User = Quotient::User; - UserListModel(QObject* parent = nullptr); Quotient::Room* room() const { return m_currentRoom; } void setRoom(Quotient::Room* room); - User* userAt(QModelIndex index) const; + Quotient::User* userAt(QModelIndex index) const; QVariant data(const QModelIndex& index, int role = NameRole) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; @@ -41,16 +54,16 @@ class UserListModel : public QAbstractListModel { void roomChanged(); private slots: - void userAdded(User* user); - void userRemoved(User* user); - void refresh(User* user, QVector roles = {}); - void avatarChanged(User* user, const Quotient::Room* context); + void userAdded(Quotient::User* user); + void userRemoved(Quotient::User* user); + void refresh(Quotient::User* user, QVector roles = {}); + void avatarChanged(Quotient::User* user, const Quotient::Room* context); private: Quotient::Room* m_currentRoom; - QList m_users; + QList m_users; - int findUserPos(User* user) const; + int findUserPos(Quotient::User* user) const; int findUserPos(const QString& username) const; };