Canonical Parent
So the original space parent and child stuff was technically a bit naughty in that it allowed multiple rooms to be set as the canonical parent. Because while a room can have multiple parents only one should be canonical. This adds the following: - When adding a child or parent there is an extra check to select if the new parent should be canonical - Any parent can be selected as the canonical one from the room settings - All functions ensure that there is only ever one canonical parent by ensuring all others are false when a new one is set.
This commit is contained in:
@@ -118,6 +118,7 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
|
|||||||
connect(this, &Room::changed, this, [this] {
|
connect(this, &Room::changed, this, [this] {
|
||||||
Q_EMIT canEncryptRoomChanged();
|
Q_EMIT canEncryptRoomChanged();
|
||||||
Q_EMIT parentIdsChanged();
|
Q_EMIT parentIdsChanged();
|
||||||
|
Q_EMIT canonicalParentChanged();
|
||||||
});
|
});
|
||||||
connect(connection, &Connection::capabilitiesLoaded, this, &NeoChatRoom::maxRoomVersionChanged);
|
connect(connection, &Connection::capabilitiesLoaded, this, &NeoChatRoom::maxRoomVersionChanged);
|
||||||
connect(this, &Room::changed, this, [this]() {
|
connect(this, &Room::changed, this, [this]() {
|
||||||
@@ -1117,12 +1118,41 @@ QVector<QString> NeoChatRoom::parentIds() const
|
|||||||
return parentIds;
|
return parentIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NeoChatRoom::isCanonicalParent(const QString &parentId) const
|
QString NeoChatRoom::canonicalParent() const
|
||||||
{
|
{
|
||||||
if (auto parentEvent = currentState().get("m.space.parent"_ls, parentId)) {
|
auto parentEvents = currentState().eventsOfType("m.space.parent"_ls);
|
||||||
return parentEvent->contentPart<bool>("canonical"_ls);
|
for (const auto &parentEvent : parentEvents) {
|
||||||
|
if (parentEvent->contentJson().contains("via"_ls) && !parentEvent->contentPart<QJsonArray>("via"_ls).isEmpty()) {
|
||||||
|
if (parentEvent->contentPart<bool>("canonical"_ls)) {
|
||||||
|
return parentEvent->stateKey();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::setCanonicalParent(const QString &parentId)
|
||||||
|
{
|
||||||
|
if (!canModifyParent(parentId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (const auto &parent = currentState().get("m.space.parent"_ls, parentId)) {
|
||||||
|
auto content = parent->contentJson();
|
||||||
|
content.insert("canonical"_ls, true);
|
||||||
|
setState("m.space.parent"_ls, parentId, content);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only one canonical parent can exist so make sure others are set false.
|
||||||
|
auto parentEvents = currentState().eventsOfType("m.space.parent"_ls);
|
||||||
|
for (const auto &parentEvent : parentEvents) {
|
||||||
|
if (parentEvent->contentPart<bool>("canonical"_ls) && parentEvent->stateKey() != parentId) {
|
||||||
|
auto content = parentEvent->contentJson();
|
||||||
|
content.insert("canonical"_ls, false);
|
||||||
|
setState("m.space.parent"_ls, parentEvent->stateKey(), content);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NeoChatRoom::canModifyParent(const QString &parentId) const
|
bool NeoChatRoom::canModifyParent(const QString &parentId) const
|
||||||
@@ -1151,13 +1181,29 @@ bool NeoChatRoom::canModifyParent(const QString &parentId) const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NeoChatRoom::addParent(const QString &parentId)
|
void NeoChatRoom::addParent(const QString &parentId, bool canonical, bool setParentChild)
|
||||||
{
|
{
|
||||||
if (!canModifyParent(parentId)) {
|
if (!canModifyParent(parentId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (auto parent = static_cast<NeoChatRoom *>(connection()->room(parentId))) {
|
if (canonical) {
|
||||||
setState("m.space.parent"_ls, parentId, QJsonObject{{"canonical"_ls, true}, {"via"_ls, QJsonArray{connection()->domain()}}});
|
// Only one canonical parent can exist so make sure others are set false.
|
||||||
|
auto parentEvents = currentState().eventsOfType("m.space.parent"_ls);
|
||||||
|
for (const auto &parentEvent : parentEvents) {
|
||||||
|
if (parentEvent->contentPart<bool>("canonical"_ls)) {
|
||||||
|
auto content = parentEvent->contentJson();
|
||||||
|
content.insert("canonical"_ls, false);
|
||||||
|
setState("m.space.parent"_ls, parentEvent->stateKey(), content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setState("m.space.parent"_ls, parentId, QJsonObject{{"canonical"_ls, canonical}, {"via"_ls, QJsonArray{connection()->domain()}}});
|
||||||
|
|
||||||
|
if (setParentChild) {
|
||||||
|
if (auto parent = static_cast<NeoChatRoom *>(connection()->room(parentId))) {
|
||||||
|
parent->setState("m.space.child"_ls, id(), QJsonObject{{QLatin1String("via"), QJsonArray{connection()->domain()}}});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1184,7 +1230,7 @@ bool NeoChatRoom::isSpace()
|
|||||||
return creationEvent->roomType() == RoomType::Space;
|
return creationEvent->roomType() == RoomType::Space;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NeoChatRoom::addChild(const QString &childId, bool setChildParent)
|
void NeoChatRoom::addChild(const QString &childId, bool setChildParent, bool canonical)
|
||||||
{
|
{
|
||||||
if (!isSpace()) {
|
if (!isSpace()) {
|
||||||
return;
|
return;
|
||||||
@@ -1197,7 +1243,19 @@ void NeoChatRoom::addChild(const QString &childId, bool setChildParent)
|
|||||||
if (setChildParent) {
|
if (setChildParent) {
|
||||||
if (auto child = static_cast<NeoChatRoom *>(connection()->room(childId))) {
|
if (auto child = static_cast<NeoChatRoom *>(connection()->room(childId))) {
|
||||||
if (child->canSendState("m.space.parent"_ls)) {
|
if (child->canSendState("m.space.parent"_ls)) {
|
||||||
child->setState("m.space.parent"_ls, id(), QJsonObject{{"canonical"_ls, true}, {"via"_ls, QJsonArray{connection()->domain()}}});
|
child->setState("m.space.parent"_ls, id(), QJsonObject{{"canonical"_ls, canonical}, {"via"_ls, QJsonArray{connection()->domain()}}});
|
||||||
|
|
||||||
|
if (canonical) {
|
||||||
|
// Only one canonical parent can exist so make sure others are set to false.
|
||||||
|
auto parentEvents = child->currentState().eventsOfType("m.space.parent"_ls);
|
||||||
|
for (const auto &parentEvent : parentEvents) {
|
||||||
|
if (parentEvent->contentPart<bool>("canonical"_ls)) {
|
||||||
|
auto content = parentEvent->contentJson();
|
||||||
|
content.insert("canonical"_ls, false);
|
||||||
|
setState("m.space.parent"_ls, parentEvent->stateKey(), content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,6 +133,21 @@ class NeoChatRoom : public Quotient::Room
|
|||||||
*/
|
*/
|
||||||
Q_PROPERTY(QVector<QString> parentIds READ parentIds NOTIFY parentIdsChanged)
|
Q_PROPERTY(QVector<QString> parentIds READ parentIds NOTIFY parentIdsChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The current canonical parent for the room.
|
||||||
|
*
|
||||||
|
* Empty if no canonical parent is set. The write method can only be used to
|
||||||
|
* set an existing parent as canonical; If you wish to add a new parent and set
|
||||||
|
* it as canonical use the addParent method and pass true to the canonical
|
||||||
|
* parameter.
|
||||||
|
*
|
||||||
|
* Setting will fail if the user doesn't have the required privileges (see
|
||||||
|
* canModifyParent) or if the given room ID is not a parent room.
|
||||||
|
*
|
||||||
|
* @sa canModifyParent, addParent
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(QString canonicalParent READ canonicalParent WRITE setCanonicalParent NOTIFY canonicalParentChanged)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief If the room is a space.
|
* @brief If the room is a space.
|
||||||
*/
|
*/
|
||||||
@@ -601,10 +616,8 @@ public:
|
|||||||
|
|
||||||
QVector<QString> parentIds() const;
|
QVector<QString> parentIds() const;
|
||||||
|
|
||||||
/**
|
QString canonicalParent() const;
|
||||||
* @brief Whether the given parent is the canonical parent of the room.
|
void setCanonicalParent(const QString &parentId);
|
||||||
*/
|
|
||||||
Q_INVOKABLE bool isCanonicalParent(const QString &parentId) const;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Whether the local user has permission to set the given space as a parent.
|
* @brief Whether the local user has permission to set the given space as a parent.
|
||||||
@@ -622,7 +635,7 @@ public:
|
|||||||
*
|
*
|
||||||
* @sa canModifyParent()
|
* @sa canModifyParent()
|
||||||
*/
|
*/
|
||||||
Q_INVOKABLE void addParent(const QString &parentId);
|
Q_INVOKABLE void addParent(const QString &parentId, bool canonical = false, bool setParentChild = false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Remove the given room as a parent.
|
* @brief Remove the given room as a parent.
|
||||||
@@ -642,7 +655,7 @@ public:
|
|||||||
* Will fail if the user doesn't have the required privileges or this room is
|
* Will fail if the user doesn't have the required privileges or this room is
|
||||||
* not a space.
|
* not a space.
|
||||||
*/
|
*/
|
||||||
Q_INVOKABLE void addChild(const QString &childId, bool setChildParent = false);
|
Q_INVOKABLE void addChild(const QString &childId, bool setChildParent = false, bool canonical = false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Remove the given room as a child.
|
* @brief Remove the given room as a child.
|
||||||
@@ -927,6 +940,7 @@ Q_SIGNALS:
|
|||||||
void backgroundChanged();
|
void backgroundChanged();
|
||||||
void readMarkerLoadedChanged();
|
void readMarkerLoadedChanged();
|
||||||
void parentIdsChanged();
|
void parentIdsChanged();
|
||||||
|
void canonicalParentChanged();
|
||||||
void lastActiveTimeChanged();
|
void lastActiveTimeChanged();
|
||||||
void isInviteChanged();
|
void isInviteChanged();
|
||||||
void displayNameChanged();
|
void displayNameChanged();
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ FormCard.FormCardPage {
|
|||||||
|
|
||||||
required property NeoChatConnection connection
|
required property NeoChatConnection connection
|
||||||
|
|
||||||
signal addChild(string childId, bool setChildParent)
|
signal addChild(string childId, bool setChildParent, bool canonical)
|
||||||
signal newChild(string childName)
|
signal newChild(string childName)
|
||||||
|
|
||||||
title: isSpace ? i18nc("@title", "Create a Space") : i18nc("@title", "Create a Room")
|
title: isSpace ? i18nc("@title", "Create a Space") : i18nc("@title", "Create a Room")
|
||||||
@@ -214,11 +214,18 @@ FormCard.FormCardPage {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
FormCard.FormCheckDelegate {
|
||||||
|
id: makeCanonicalCheck
|
||||||
|
text: i18n("Make this space the canonical parent")
|
||||||
|
checked: enabled
|
||||||
|
|
||||||
|
enabled: existingOfficialCheck.enabled
|
||||||
|
}
|
||||||
FormCard.FormButtonDelegate {
|
FormCard.FormButtonDelegate {
|
||||||
text: i18nc("@action:button", "Ok")
|
text: i18nc("@action:button", "Ok")
|
||||||
enabled: chosenRoomDelegate.visible
|
enabled: chosenRoomDelegate.visible
|
||||||
onClicked: {
|
onClicked: {
|
||||||
root.addChild(chosenRoomDelegate.roomId, existingOfficialCheck.checked);
|
root.addChild(chosenRoomDelegate.roomId, existingOfficialCheck.checked, makeCanonicalCheck.checked);
|
||||||
root.closeDialog();
|
root.closeDialog();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -309,18 +309,38 @@ FormCard.FormCardPage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
contentItem.children: QQC2.ToolButton {
|
contentItem.children: RowLayout {
|
||||||
visible: officalParentDelegate?.space.canSendState("m.space.child") && root.room.canSendState("m.space.parent")
|
QQC2.Label {
|
||||||
display: QQC2.AbstractButton.IconOnly
|
visible: root.room.canonicalParent === officalParentDelegate.modelData
|
||||||
action: Kirigami.Action {
|
text: i18n("Canonical")
|
||||||
id: removeParentAction
|
|
||||||
text: i18n("Remove parent")
|
|
||||||
icon.name: "edit-delete-remove"
|
|
||||||
onTriggered: root.room.removeParent(officalParentDelegate.modelData)
|
|
||||||
}
|
}
|
||||||
QQC2.ToolTip {
|
QQC2.ToolButton {
|
||||||
text: removeParentAction.text
|
visible: root.room.canSendState("m.space.parent") && root.room.canonicalParent !== officalParentDelegate.modelData
|
||||||
delay: Kirigami.Units.toolTipDelay
|
display: QQC2.AbstractButton.IconOnly
|
||||||
|
action: Kirigami.Action {
|
||||||
|
id: canonicalParentAction
|
||||||
|
text: i18n("Make canonical parent")
|
||||||
|
icon.name: "checkmark"
|
||||||
|
onTriggered: root.room.canonicalParent = officalParentDelegate.modelData
|
||||||
|
}
|
||||||
|
QQC2.ToolTip {
|
||||||
|
text: canonicalParentAction.text
|
||||||
|
delay: Kirigami.Units.toolTipDelay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QQC2.ToolButton {
|
||||||
|
visible: officalParentDelegate?.space.canSendState("m.space.child") && root.room.canSendState("m.space.parent")
|
||||||
|
display: QQC2.AbstractButton.IconOnly
|
||||||
|
action: Kirigami.Action {
|
||||||
|
id: removeParentAction
|
||||||
|
text: i18n("Remove parent")
|
||||||
|
icon.name: "edit-delete-remove"
|
||||||
|
onTriggered: root.room.removeParent(officalParentDelegate.modelData)
|
||||||
|
}
|
||||||
|
QQC2.ToolTip {
|
||||||
|
text: removeParentAction.text
|
||||||
|
delay: Kirigami.Units.toolTipDelay
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -445,11 +465,18 @@ FormCard.FormCardPage {
|
|||||||
text: existingOfficialCheck.space ? (existingOfficialCheck.space.isSpace ? i18n("You do not have a high enough privilege level in the parent to set this state") : i18n("The selected room is not a space")) : i18n("You do not have the privileges to complete this action")
|
text: existingOfficialCheck.space ? (existingOfficialCheck.space.isSpace ? i18n("You do not have a high enough privilege level in the parent to set this state") : i18n("The selected room is not a space")) : i18n("You do not have the privileges to complete this action")
|
||||||
textItem.color: Kirigami.Theme.negativeTextColor
|
textItem.color: Kirigami.Theme.negativeTextColor
|
||||||
}
|
}
|
||||||
|
FormCard.FormCheckDelegate {
|
||||||
|
id: makeCanonicalCheck
|
||||||
|
text: i18n("Make this space the canonical parent")
|
||||||
|
checked: enabled
|
||||||
|
|
||||||
|
enabled: chosenRoomDelegate.visible
|
||||||
|
}
|
||||||
FormCard.FormButtonDelegate {
|
FormCard.FormButtonDelegate {
|
||||||
text: i18nc("@action:button", "Ok")
|
text: i18nc("@action:button", "Ok")
|
||||||
enabled: chosenRoomDelegate.visible && root.room.canModifyParent(chosenRoomDelegate.roomId)
|
enabled: chosenRoomDelegate.visible && root.room.canModifyParent(chosenRoomDelegate.roomId)
|
||||||
onClicked: {
|
onClicked: {
|
||||||
root.room.addParent(chosenRoomDelegate.roomId)
|
root.room.addParent(chosenRoomDelegate.roomId, makeCanonicalCheck.checked, existingOfficialCheck.checked)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -157,12 +157,12 @@ Kirigami.Page {
|
|||||||
}, {
|
}, {
|
||||||
title: i18nc("@title", "Create a Child")
|
title: i18nc("@title", "Create a Child")
|
||||||
})
|
})
|
||||||
dialog.addChild.connect((childId, setChildParent) => {
|
dialog.addChild.connect((childId, setChildParent, canonical) => {
|
||||||
// We have to get a room object from the connection as we may not
|
// We have to get a room object from the connection as we may not
|
||||||
// be adding to the top level parent.
|
// be adding to the top level parent.
|
||||||
let parent = root.currentRoom.connection.room(parentId)
|
let parent = root.currentRoom.connection.room(parentId)
|
||||||
if (parent) {
|
if (parent) {
|
||||||
parent.addChild(childId, setChildParent)
|
parent.addChild(childId, setChildParent, canonical)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
dialog.newChild.connect(childName => {spaceChildrenModel.addPendingChild(childName)})
|
dialog.newChild.connect(childName => {spaceChildrenModel.addPendingChild(childName)})
|
||||||
|
|||||||
Reference in New Issue
Block a user