Merge branch 'powerlevel' into 'master'

Powerlevel support

See merge request spectral-im/spectral!69
This commit is contained in:
Black Hat
2020-01-21 19:27:43 +00:00
10 changed files with 224 additions and 43 deletions

View File

@@ -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

View File

@@ -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
}
}
}
}
}
}

View File

@@ -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
}
}

View File

@@ -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()

View File

@@ -53,6 +53,7 @@ int main(int argc, char* argv[]) {
qmlRegisterUncreatableType<RoomMessageEvent>("Spectral", 0, 1,
"RoomMessageEvent", "ENUM");
qmlRegisterUncreatableType<RoomType>("Spectral", 0, 1, "RoomType", "ENUM");
qmlRegisterUncreatableType<UserType>("Spectral", 0, 1, "UserType", "ENUM");
qRegisterMetaType<User*>("User*");
qRegisterMetaType<User*>("const User*");

View File

@@ -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",
"<mx-reply><blockquote><a href=\"https://matrix.to/#/" +
id() + "/" + replyEventId +
"\">In reply to</a> <a href=\"https://matrix.to/#/" +
replyEvt.senderId() + "\">" + replyEvt.senderId() +
"</a><br>" + eventToString(replyEvt, Qt::RichText) +
"</blockquote></mx-reply>" + 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",
"<mx-reply><blockquote><a href=\"https://matrix.to/#/" +
id() + "/" +
replyEventId +
"\">In reply to</a> <a href=\"https://matrix.to/#/" +
replyEvt.senderId() + "\">" + replyEvt.senderId() +
"</a><br>" + eventToString(replyEvt, Qt::RichText) +
"</blockquote></mx-reply>" + 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",
"<mx-reply><blockquote><a href=\"https://matrix.to/#/" +
id() + "/" + replyEventId +
"\">In reply to</a> <a href=\"https://matrix.to/#/" +
replyEvt.senderId() + "\">" + replyEvt.senderId() +
"</a><br>" + eventToString(replyEvt, Qt::RichText) +
"</blockquote></mx-reply>" + 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",
"<mx-reply><blockquote><a href=\"https://matrix.to/#/" +
id() + "/" +
replyEventId +
"\">In reply to</a> <a href=\"https://matrix.to/#/" +
replyEvt.senderId() + "\">" + replyEvt.senderId() +
"</a><br>" + eventToString(replyEvt, Qt::RichText) +
"</blockquote></mx-reply>" + 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<RoomPowerLevelsEvent>();
auto pl = plEvent->powerLevelForEvent(eventType);
auto currentPl = plEvent->powerLevelForUser(localUser()->id());
return currentPl >= pl;
}
bool SpectralRoom::canSendState(const QString& eventType) const {
auto plEvent = getCurrentState<RoomPowerLevelsEvent>();
auto pl = plEvent->powerLevelForState(eventType);
auto currentPl = plEvent->powerLevelForUser(localUser()->id());
return currentPl >= pl;
}

View File

@@ -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<const Quotient::RoomEvent*> highlights;

View File

@@ -4,6 +4,8 @@
#include <room.h>
#include <user.h>
#include "events/roompowerlevelsevent.h"
#include <QElapsedTimer>
#include <QtCore/QDebug>
#include <QtGui/QPixmap>
@@ -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<RoomPowerLevelsEvent>();
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<QString, int>::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<int, QByteArray> UserListModel::roleNames() const {
roles[UserIDRole] = "userId";
roles[AvatarRole] = "avatar";
roles[ObjectRole] = "user";
roles[PermRole] = "perm";
return roles;
}

View File

@@ -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<int> 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<int> roles = {});
void avatarChanged(Quotient::User* user, const Quotient::Room* context);
private:
Quotient::Room* m_currentRoom;
QList<User*> m_users;
QList<Quotient::User*> m_users;
int findUserPos(User* user) const;
int findUserPos(Quotient::User* user) const;
int findUserPos(const QString& username) const;
};