mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-21 18:50:47 +03:00
Support editing space children
This commit is contained in:
parent
68c02da6c8
commit
376612e4eb
9 changed files with 362 additions and 45 deletions
|
@ -26,10 +26,10 @@ set(CMAKE_AUTOMOC ON)
|
|||
option(HUNTER_ENABLED "Enable Hunter package manager" OFF)
|
||||
include("cmake/HunterGate.cmake")
|
||||
HunterGate(
|
||||
URL "https://github.com/cpp-pm/hunter/archive/v0.23.305.tar.gz"
|
||||
SHA1 "fc8d7a6dac2fa23681847b3872d88d3839b657b0"
|
||||
LOCAL
|
||||
)
|
||||
URL "https://github.com/cpp-pm/hunter/archive/v0.24.3.tar.gz"
|
||||
SHA1 "10738b59e539818a01090e64c2d09896247530c7"
|
||||
LOCAL
|
||||
)
|
||||
|
||||
macro(hunter_add_package_safe)
|
||||
set(pkg_temp_backup_libdir "$ENV{PKG_CONFIG_LIBDIR}")
|
||||
|
@ -583,7 +583,7 @@ if(USE_BUNDLED_MTXCLIENT)
|
|||
FetchContent_Declare(
|
||||
MatrixClient
|
||||
GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git
|
||||
GIT_TAG b706492de042455630063c847574bbc5ed5d4641
|
||||
GIT_TAG eb747bb7723c11e38ed21543d05c42cc883c9d06
|
||||
)
|
||||
set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "")
|
||||
set(BUILD_LIB_TESTS OFF CACHE INTERNAL "")
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
|
||||
hunter_config(
|
||||
spdlog
|
||||
VERSION 1.8.0-p1
|
||||
)
|
||||
|
||||
hunter_config(
|
||||
lmdb
|
||||
VERSION 0.9.21-p2
|
||||
|
|
|
@ -203,7 +203,7 @@ modules:
|
|||
buildsystem: cmake-ninja
|
||||
name: mtxclient
|
||||
sources:
|
||||
- commit: b706492de042455630063c847574bbc5ed5d4641
|
||||
- commit: eb747bb7723c11e38ed21543d05c42cc883c9d06
|
||||
#tag: v0.8.0
|
||||
type: git
|
||||
url: https://github.com/Nheko-Reborn/mtxclient.git
|
||||
|
|
|
@ -110,6 +110,17 @@ Page {
|
|||
|
||||
}
|
||||
|
||||
|
||||
Component {
|
||||
id: nestedSpaceMenuLevel
|
||||
|
||||
SpaceMenuLevel {
|
||||
roomid: roomContextMenu.roomid
|
||||
childMenu: rootSpaceMenu.childMenu
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Platform.Menu {
|
||||
id: roomContextMenu
|
||||
|
||||
|
@ -154,42 +165,51 @@ Page {
|
|||
onTriggered: Rooms.copyLink(roomContextMenu.roomid)
|
||||
}
|
||||
|
||||
Platform.MenuSeparator {
|
||||
text: qsTr("Tag room as:")
|
||||
}
|
||||
Platform.Menu {
|
||||
id: tagsMenu
|
||||
title: qsTr("Tag room as:")
|
||||
|
||||
Instantiator {
|
||||
model: Communities.tagsWithDefault
|
||||
onObjectAdded: roomContextMenu.insertItem(index + 4, object)
|
||||
onObjectRemoved: roomContextMenu.removeItem(object)
|
||||
Instantiator {
|
||||
model: Communities.tagsWithDefault
|
||||
onObjectAdded: tagsMenu.insertItem(index, object)
|
||||
onObjectRemoved: tagsMenu.removeItem(object)
|
||||
|
||||
delegate: Platform.MenuItem {
|
||||
property string t: modelData
|
||||
delegate: Platform.MenuItem {
|
||||
property string t: modelData
|
||||
|
||||
text: {
|
||||
switch (t) {
|
||||
case "m.favourite":
|
||||
return qsTr("Favourite");
|
||||
case "m.lowpriority":
|
||||
return qsTr("Low priority");
|
||||
case "m.server_notice":
|
||||
return qsTr("Server notice");
|
||||
default:
|
||||
return t.substring(2);
|
||||
text: {
|
||||
switch (t) {
|
||||
case "m.favourite":
|
||||
return qsTr("Favourite");
|
||||
case "m.lowpriority":
|
||||
return qsTr("Low priority");
|
||||
case "m.server_notice":
|
||||
return qsTr("Server notice");
|
||||
default:
|
||||
return t.substring(2);
|
||||
}
|
||||
}
|
||||
checkable: true
|
||||
checked: roomContextMenu.tags !== undefined && roomContextMenu.tags.includes(t)
|
||||
onTriggered: Rooms.toggleTag(roomContextMenu.roomid, t, checked)
|
||||
}
|
||||
checkable: true
|
||||
checked: roomContextMenu.tags !== undefined && roomContextMenu.tags.includes(t)
|
||||
onTriggered: Rooms.toggleTag(roomContextMenu.roomid, t, checked)
|
||||
|
||||
}
|
||||
|
||||
Platform.MenuItem {
|
||||
text: qsTr("Create new tag...")
|
||||
onTriggered: newTag.show()
|
||||
}
|
||||
}
|
||||
|
||||
Platform.MenuItem {
|
||||
text: qsTr("Create new tag...")
|
||||
onTriggered: newTag.show()
|
||||
}
|
||||
SpaceMenuLevel {
|
||||
id: rootSpaceMenu
|
||||
|
||||
roomid: roomContextMenu.roomid
|
||||
position: -1
|
||||
title: qsTr("Add or remove from space")
|
||||
childMenu: nestedSpaceMenuLevel
|
||||
}
|
||||
}
|
||||
|
||||
delegate: ItemDelegate {
|
||||
|
|
84
resources/qml/components/SpaceMenuLevel.qml
Normal file
84
resources/qml/components/SpaceMenuLevel.qml
Normal file
|
@ -0,0 +1,84 @@
|
|||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import Qt.labs.platform 1.1 as Platform
|
||||
import im.nheko 1.0
|
||||
|
||||
Platform.Menu {
|
||||
id: spacesMenu
|
||||
|
||||
property string roomid
|
||||
property Component childMenu
|
||||
|
||||
property int position: modelData == undefined ? -2 : modelData.treeIndex
|
||||
title: modelData != undefined ? modelData.name : qsTr("Add or remove from space")
|
||||
property bool loadChildren: false
|
||||
|
||||
onAboutToShow: loadChildren = true
|
||||
//onAboutToHide: loadChildren = false
|
||||
|
||||
Platform.MenuItemGroup {
|
||||
id: modificationGroup
|
||||
visible: position != -1
|
||||
}
|
||||
|
||||
Platform.MenuItem {
|
||||
text: qsTr("Official community for this room")
|
||||
group: modificationGroup
|
||||
checkable: true
|
||||
checked: spacesMenu.position >= 0 && (modelData.childValid && modelData.parentValid && modelData.canonical)
|
||||
enabled: spacesMenu.position >= 0 && (modelData.canEditChild && modelData.canEditParent)
|
||||
onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, true, true, true)
|
||||
}
|
||||
Platform.MenuItem {
|
||||
text: qsTr("Affiliated community for this room")
|
||||
group: modificationGroup
|
||||
checkable: true
|
||||
checked: spacesMenu.position >= 0 && (modelData.childValid && modelData.parentValid && !modelData.canonical)
|
||||
enabled: spacesMenu.position >= 0 && (modelData.canEditChild && modelData.canEditParent)
|
||||
onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, true, true, false)
|
||||
}
|
||||
Platform.MenuItem {
|
||||
text: qsTr("Listed only for community members")
|
||||
group: modificationGroup
|
||||
checkable: true
|
||||
checked: spacesMenu.position >= 0 && (modelData.childValid && !modelData.parentValid)
|
||||
enabled: spacesMenu.position >= 0 && ((modelData.canEditChild || modelData.childValid) && (!modelData.parentValid || modelData.canEditParent))
|
||||
onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, false, true, false)
|
||||
}
|
||||
Platform.MenuItem {
|
||||
text: qsTr("Listed only for room members")
|
||||
group: modificationGroup
|
||||
checkable: true
|
||||
checked: spacesMenu.position >= 0 && (!modelData.childValid && modelData.parentValid)
|
||||
enabled: spacesMenu.position >= 0 && ((modelData.canEditChild) && (modelData.parentValid || modelData.canEditParent))
|
||||
onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, true, false, false)
|
||||
}
|
||||
Platform.MenuItem {
|
||||
text: qsTr("Not related")
|
||||
group: modificationGroup
|
||||
checkable: true
|
||||
checked: spacesMenu.position >= 0 && (!modelData.childValid && !modelData.parentValid)
|
||||
enabled: spacesMenu.position >= 0 && ((modelData.canEditChild || !modelData.childValid) && (!modelData.parentValid || modelData.canEditParent))
|
||||
onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, false, false, false)
|
||||
}
|
||||
|
||||
Platform.MenuSeparator {
|
||||
text: qsTr("Subcommunities")
|
||||
group: modificationGroup
|
||||
visible: modificationGroup.visible && inst.model != undefined
|
||||
}
|
||||
|
||||
Instantiator {
|
||||
id: inst
|
||||
model: spacesMenu.loadChildren ? Communities.spaceChildrenListFromIndex(spacesMenu.roomid, spacesMenu.position) : undefined
|
||||
onObjectAdded: (idx, o) => {
|
||||
spacesMenu.insertMenu(idx + (spacesMenu.position != -1 ? 6 : 0), o)
|
||||
}
|
||||
//onObjectRemoved: spacesMenu.removeMenu(object)
|
||||
|
||||
delegate: childMenu
|
||||
}
|
||||
}
|
|
@ -120,16 +120,14 @@
|
|||
<file>qml/SelfVerificationCheck.qml</file>
|
||||
<file>qml/TypingIndicator.qml</file>
|
||||
<file>qml/NotificationWarning.qml</file>
|
||||
<file>qml/pages/UserSettingsPage.qml</file>
|
||||
<file>qml/pages/WelcomePage.qml</file>
|
||||
<file>qml/pages/LoginPage.qml</file>
|
||||
<file>qml/pages/RegisterPage.qml</file>
|
||||
<file>qml/components/AdaptiveLayout.qml</file>
|
||||
<file>qml/components/AdaptiveLayoutElement.qml</file>
|
||||
<file>qml/components/AvatarListTile.qml</file>
|
||||
<file>qml/components/FlatButton.qml</file>
|
||||
<file>qml/components/MainWindowDialog.qml</file>
|
||||
<file>qml/components/NotificationBubble.qml</file>
|
||||
<file>qml/components/ReorderableListview.qml</file>
|
||||
<file>qml/components/SpaceMenuLevel.qml</file>
|
||||
<file>qml/components/TextButton.qml</file>
|
||||
<file>qml/delegates/Encrypted.qml</file>
|
||||
<file>qml/delegates/FileMessage.qml</file>
|
||||
|
@ -172,10 +170,14 @@
|
|||
<file>qml/dialogs/UserProfile.qml</file>
|
||||
<file>qml/emoji/EmojiPicker.qml</file>
|
||||
<file>qml/emoji/StickerPicker.qml</file>
|
||||
<file>qml/pages/LoginPage.qml</file>
|
||||
<file>qml/pages/RegisterPage.qml</file>
|
||||
<file>qml/pages/UserSettingsPage.qml</file>
|
||||
<file>qml/pages/WelcomePage.qml</file>
|
||||
<file>qml/ui/NhekoSlider.qml</file>
|
||||
<file>qml/ui/Ripple.qml</file>
|
||||
<file>qml/ui/Spinner.qml</file>
|
||||
<file>qml/ui/Snackbar.qml</file>
|
||||
<file>qml/ui/Spinner.qml</file>
|
||||
<file>qml/ui/animations/BlinkAnimation.qml</file>
|
||||
<file>qml/ui/media/MediaControls.qml</file>
|
||||
<file>qml/voip/ActiveCallBar.qml</file>
|
||||
|
@ -186,7 +188,6 @@
|
|||
<file>qml/voip/PlaceCall.qml</file>
|
||||
<file>qml/voip/ScreenShare.qml</file>
|
||||
<file>qml/voip/VideoCall.qml</file>
|
||||
<file>qml/components/NotificationBubble.qml</file>
|
||||
</qresource>
|
||||
<qresource prefix="/media">
|
||||
<file>media/ring.ogg</file>
|
||||
|
|
|
@ -5,20 +5,29 @@
|
|||
|
||||
#include "CommunitiesModel.h"
|
||||
|
||||
#include <mtx/responses/common.hpp>
|
||||
#include <set>
|
||||
|
||||
#include "Cache.h"
|
||||
#include "Cache_p.h"
|
||||
#include "ChatPage.h"
|
||||
#include "Logging.h"
|
||||
#include "MatrixClient.h"
|
||||
#include "Permissions.h"
|
||||
#include "UserSettingsPage.h"
|
||||
#include "Utils.h"
|
||||
#include "timeline/TimelineModel.h"
|
||||
|
||||
Q_DECLARE_METATYPE(SpaceItem)
|
||||
|
||||
CommunitiesModel::CommunitiesModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
, hiddenTagIds_{UserSettings::instance()->hiddenTags()}
|
||||
, mutedTagIds_{UserSettings::instance()->mutedTags()}
|
||||
{}
|
||||
{
|
||||
static auto ignore = qRegisterMetaType<SpaceItem>();
|
||||
(void)ignore;
|
||||
}
|
||||
|
||||
QHash<int, QByteArray>
|
||||
CommunitiesModel::roleNames() const
|
||||
|
@ -723,3 +732,156 @@ FilteredCommunitiesModel::filterAcceptsRow(int sourceRow, const QModelIndex &) c
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
QVariantList
|
||||
CommunitiesModel::spaceChildrenListFromIndex(QString room, int idx) const
|
||||
{
|
||||
if (idx < -1)
|
||||
return {};
|
||||
|
||||
auto room_ = room.toStdString();
|
||||
|
||||
int begin = idx + 1;
|
||||
int end = idx >= 0 ? this->spaceOrder_.lastChild(idx) + 1 : this->spaceOrder_.size();
|
||||
QVariantList ret;
|
||||
|
||||
bool canSendParent = Permissions(room).canChange(qml_mtx_events::SpaceParent);
|
||||
|
||||
for (int i = begin; i < end; i++) {
|
||||
const auto &e = spaceOrder_.tree[i];
|
||||
if (e.depth == spaceOrder_.tree[begin].depth && spaces_.count(e.id)) {
|
||||
bool canSendChild = Permissions(e.id).canChange(qml_mtx_events::SpaceChild);
|
||||
auto spaceId = e.id.toStdString();
|
||||
auto child =
|
||||
cache::client()->getStateEvent<mtx::events::state::space::Child>(spaceId, room_);
|
||||
auto parent =
|
||||
cache::client()->getStateEvent<mtx::events::state::space::Parent>(room_, spaceId);
|
||||
|
||||
bool childValid =
|
||||
child && !child->content.via.value_or(std::vector<std::string>{}).empty();
|
||||
bool parentValid =
|
||||
parent && !parent->content.via.value_or(std::vector<std::string>{}).empty();
|
||||
bool canonical = parent && parent->content.canonical;
|
||||
|
||||
if (e.id == room) {
|
||||
canonical = parentValid = childValid = canSendChild = canSendParent = false;
|
||||
}
|
||||
|
||||
ret.push_back(
|
||||
QVariant::fromValue(SpaceItem(e.id,
|
||||
QString::fromStdString(spaces_.at(e.id).name),
|
||||
i,
|
||||
childValid,
|
||||
parentValid,
|
||||
canonical,
|
||||
canSendChild,
|
||||
canSendParent)));
|
||||
}
|
||||
}
|
||||
|
||||
nhlog::ui()->critical("Returning {} spaces", ret.size());
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
CommunitiesModel::updateSpaceStatus(QString space,
|
||||
QString room,
|
||||
bool setParent,
|
||||
bool setChild,
|
||||
bool canonical) const
|
||||
{
|
||||
nhlog::ui()->critical("Setting space {} children {}: {} {} {}",
|
||||
space.toStdString(),
|
||||
room.toStdString(),
|
||||
setParent,
|
||||
setChild,
|
||||
canonical);
|
||||
auto child =
|
||||
cache::client()
|
||||
->getStateEvent<mtx::events::state::space::Child>(space.toStdString(), room.toStdString())
|
||||
.value_or(mtx::events::StateEvent<mtx::events::state::space::Child>{})
|
||||
.content;
|
||||
auto parent =
|
||||
cache::client()
|
||||
->getStateEvent<mtx::events::state::space::Parent>(room.toStdString(), space.toStdString())
|
||||
.value_or(mtx::events::StateEvent<mtx::events::state::space::Parent>{})
|
||||
.content;
|
||||
|
||||
if (setChild) {
|
||||
if (!child.via || child.via->empty()) {
|
||||
child.via = utils::roomVias(room.toStdString());
|
||||
child.suggested = true;
|
||||
|
||||
http::client()->send_state_event(
|
||||
space.toStdString(),
|
||||
room.toStdString(),
|
||||
child,
|
||||
[space, room](mtx::responses::EventId, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
ChatPage::instance()->showNotification(
|
||||
tr("Failed to update space child: %1")
|
||||
.arg(QString::fromStdString(err->matrix_error.error)));
|
||||
nhlog::net()->error("Failed to update child {} of {}: {}",
|
||||
room.toStdString(),
|
||||
space.toStdString());
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (child.via && !child.via->empty()) {
|
||||
http::client()->send_state_event(
|
||||
space.toStdString(),
|
||||
room.toStdString(),
|
||||
mtx::events::state::space::Child{},
|
||||
[space, room](mtx::responses::EventId, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
ChatPage::instance()->showNotification(
|
||||
tr("Failed to delete space child: %1")
|
||||
.arg(QString::fromStdString(err->matrix_error.error)));
|
||||
nhlog::net()->error("Failed to delete child {} of {}: {}",
|
||||
room.toStdString(),
|
||||
space.toStdString());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (setParent) {
|
||||
if (!parent.via || parent.via->empty() || canonical != parent.canonical) {
|
||||
parent.via = utils::roomVias(room.toStdString());
|
||||
parent.canonical = canonical;
|
||||
|
||||
http::client()->send_state_event(
|
||||
room.toStdString(),
|
||||
space.toStdString(),
|
||||
parent,
|
||||
[space, room](mtx::responses::EventId, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
ChatPage::instance()->showNotification(
|
||||
tr("Failed to update space parent: %1")
|
||||
.arg(QString::fromStdString(err->matrix_error.error)));
|
||||
nhlog::net()->error("Failed to update parent {} of {}: {}",
|
||||
space.toStdString(),
|
||||
room.toStdString());
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (parent.via && !parent.via->empty()) {
|
||||
http::client()->send_state_event(
|
||||
room.toStdString(),
|
||||
space.toStdString(),
|
||||
mtx::events::state::space::Parent{},
|
||||
[space, room](mtx::responses::EventId, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
ChatPage::instance()->showNotification(
|
||||
tr("Failed to delete space parent: %1")
|
||||
.arg(QString::fromStdString(err->matrix_error.error)));
|
||||
nhlog::net()->error("Failed to delete parent {} of {}: {}",
|
||||
space.toStdString(),
|
||||
room.toStdString());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,47 @@ public:
|
|||
bool filterAcceptsRow(int sourceRow, const QModelIndex &) const override;
|
||||
};
|
||||
|
||||
class SpaceItem
|
||||
{
|
||||
Q_GADGET
|
||||
|
||||
Q_PROPERTY(QString roomid MEMBER roomid CONSTANT)
|
||||
Q_PROPERTY(QString name MEMBER name CONSTANT)
|
||||
Q_PROPERTY(int treeIndex MEMBER treeIndex CONSTANT)
|
||||
|
||||
Q_PROPERTY(bool childValid MEMBER childValid CONSTANT)
|
||||
Q_PROPERTY(bool parentValid MEMBER parentValid CONSTANT)
|
||||
Q_PROPERTY(bool canonical MEMBER canonical CONSTANT)
|
||||
|
||||
Q_PROPERTY(bool canEditParent MEMBER canEditParent CONSTANT)
|
||||
Q_PROPERTY(bool canEditChild MEMBER canEditChild CONSTANT)
|
||||
|
||||
public:
|
||||
SpaceItem() {}
|
||||
SpaceItem(QString roomid_,
|
||||
QString name_,
|
||||
int treeIndex_,
|
||||
bool childValid_,
|
||||
bool parentValid_,
|
||||
bool canonical_,
|
||||
bool canEditChild_,
|
||||
bool canEditParent_)
|
||||
: roomid(std::move(roomid_))
|
||||
, name(std::move(name_))
|
||||
, treeIndex(treeIndex_)
|
||||
, childValid(childValid_)
|
||||
, parentValid(parentValid_)
|
||||
, canonical(canonical_)
|
||||
, canEditParent(canEditParent_)
|
||||
, canEditChild(canEditChild_)
|
||||
{}
|
||||
|
||||
QString roomid, name;
|
||||
int treeIndex = 0;
|
||||
bool childValid = false, parentValid = false, canonical = false;
|
||||
bool canEditParent = false, canEditChild = false;
|
||||
};
|
||||
|
||||
class CommunitiesModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -125,6 +166,13 @@ public:
|
|||
return false;
|
||||
}
|
||||
|
||||
Q_INVOKABLE QVariantList spaceChildrenListFromIndex(QString room, int idx = -1) const;
|
||||
Q_INVOKABLE void updateSpaceStatus(QString space,
|
||||
QString room,
|
||||
bool setParent,
|
||||
bool setChild,
|
||||
bool canonical) const;
|
||||
|
||||
public slots:
|
||||
void initializeSidebar();
|
||||
void sync(const mtx::responses::Sync &sync_);
|
||||
|
@ -148,6 +196,7 @@ public slots:
|
|||
}
|
||||
void toggleTagId(QString tagId);
|
||||
void toggleTagMute(QString tagId);
|
||||
|
||||
FilteredCommunitiesModel *filtered() { return new FilteredCommunitiesModel(this, this); }
|
||||
|
||||
signals:
|
||||
|
|
|
@ -546,6 +546,13 @@ RoomlistModel::sync(const mtx::responses::Sync &sync_)
|
|||
}
|
||||
}
|
||||
}
|
||||
for (const auto &e : room.account_data.events) {
|
||||
if (std::holds_alternative<
|
||||
mtx::events::AccountDataEvent<mtx::events::account_data::Tags>>(e)) {
|
||||
if (auto idx = roomidToIndex(qroomid); idx != -1)
|
||||
emit dataChanged(index(idx), index(idx), {Tags});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &[room_id, room] : sync_.rooms.leave) {
|
||||
|
|
Loading…
Reference in a new issue