Reenable message edits

This commit is contained in:
James Graham
2026-01-30 13:52:04 +00:00
parent 6e28ada1a4
commit cad90d0c4c
7 changed files with 185 additions and 64 deletions

View File

@@ -42,6 +42,8 @@ Item {
} }
} }
property int chatBarType: LibNeoChat.ChatBarType.Room
onActiveFocusChanged: chatContentView.itemAt(contentModel.index(contentModel.focusRow, 0)).forceActiveFocus() onActiveFocusChanged: chatContentView.itemAt(contentModel.index(contentModel.focusRow, 0)).forceActiveFocus()
Connections { Connections {
@@ -127,7 +129,7 @@ Item {
id: chatContentView id: chatContentView
model: ChatBarMessageContentModel { model: ChatBarMessageContentModel {
id: contentModel id: contentModel
type: ChatBarType.Room type: root.chatBarType
room: root.currentRoom room: root.currentRoom
sendMessageWithEnter: NeoChatConfig.sendMessageWith === 0 sendMessageWithEnter: NeoChatConfig.sendMessageWith === 0
} }
@@ -201,7 +203,7 @@ Item {
property LibNeoChat.CompletionModel completionModel: LibNeoChat.CompletionModel { property LibNeoChat.CompletionModel completionModel: LibNeoChat.CompletionModel {
room: root.currentRoom room: root.currentRoom
type: LibNeoChat.ChatBarType.Room type: root.chatBarType
textItem: contentModel.focusedTextItem textItem: contentModel.focusedTextItem
roomListModel: RoomManager.roomListModel roomListModel: RoomManager.roomListModel
userListModel: RoomManager.userListModel userListModel: RoomManager.userListModel

View File

@@ -0,0 +1,96 @@
// SPDX-FileCopyrightText: 2026 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
import QtCore
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.neochat.libneochat as LibNeoChat
QQC2.Control {
id: root
required property real availableWidth
width: root.availableWidth - Kirigami.Units.largeSpacing * 2
topPadding: Kirigami.Units.smallSpacing
bottomPadding: Kirigami.Units.smallSpacing
contentItem: ColumnLayout {
RichEditBar {
id: richEditBar
visible: NeoChatConfig.sendMessageWith === 1
maxAvailableWidth: root.availableWidth - Kirigami.Units.largeSpacing * 2
room: root.currentRoom
contentModel: chatContentView.model
onClicked: contentModel.refocusCurrentComponent()
}
Kirigami.Separator {
Layout.fillWidth: true
visible: NeoChatConfig.sendMessageWith === 1
}
RowLayout {
spacing: 0
QQC2.ScrollView {
id: chatScrollView
Layout.fillWidth: true
Layout.maximumHeight: Kirigami.Units.gridUnit * 8
clip: true
ColumnLayout {
readonly property real visibleTop: chatScrollView.QQC2.ScrollBar.vertical.position * chatScrollView.contentHeight
readonly property real visibleBottom: chatScrollView.QQC2.ScrollBar.vertical.position * chatScrollView.contentHeight + chatScrollView.QQC2.ScrollBar.vertical.size * chatScrollView.contentHeight
readonly property rect cursorRectInColumn: mapFromItem(contentModel.focusedTextItem.textItem, contentModel.focusedTextItem.cursorRectangle);
onCursorRectInColumnChanged: {
if (chatScrollView.QQC2.ScrollBar.vertical.visible) {
if (cursorRectInColumn.y < visibleTop) {
chatScrollView.QQC2.ScrollBar.vertical.position = cursorRectInColumn.y / chatScrollView.contentHeight
} else if (cursorRectInColumn.y + cursorRectInColumn.height > visibleBottom) {
chatScrollView.QQC2.ScrollBar.vertical.position = (cursorRectInColumn.y + cursorRectInColumn.height - (chatScrollView.QQC2.ScrollBar.vertical.size * chatScrollView.contentHeight)) / chatScrollView.contentHeight
}
}
}
width: chatScrollView.width
spacing: Kirigami.Units.smallSpacing
Repeater {
id: chatContentView
model: ChatBarMessageContentModel {
id: contentModel
type: root.chatBarType
room: root.currentRoom
sendMessageWithEnter: NeoChatConfig.sendMessageWith === 0
}
delegate: MessageComponentChooser {
rightAnchorMargin: chatScrollView.QQC2.ScrollBar.vertical.visible ? chatScrollView.QQC2.ScrollBar.vertical.width : 0
}
}
}
}
SendBar {
room: root.currentRoom
contentModel: chatContentView.model
}
}
}
background: Kirigami.ShadowedRectangle {
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
radius: Kirigami.Units.cornerRadius
color: Kirigami.Theme.backgroundColor
border {
color: Kirigami.ColorUtils.linearInterpolation(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, Kirigami.Theme.frameContrast)
width: 1
}
}
}

View File

@@ -104,25 +104,21 @@ QQC2.Control {
} }
} }
CompletionMenu { // CompletionMenu {
id: completionMenu // id: completionMenu
width: Math.max(350, root.width - 1) // width: Math.max(350, root.width - 1)
height: implicitHeight // height: implicitHeight
y: -height - 5 // y: -height - 5
z: 10 // z: 10
// margins: 0
room: root.Message.room // Behavior on height {
type: root.chatBarCache.isEditing ? ChatBarType.Edit : ChatBarType.Thread // NumberAnimation {
// textItem: textArea // property: "height"
margins: 0 // duration: Kirigami.Units.shortDuration
Behavior on height { // easing.type: Easing.OutCubic
NumberAnimation { // }
property: "height" // }
duration: Kirigami.Units.shortDuration // }
easing.type: Easing.OutCubic
}
}
}
// opt-out of whatever spell checker a styled TextArea might come with // opt-out of whatever spell checker a styled TextArea might come with
Kirigami.SpellCheck.enabled: false Kirigami.SpellCheck.enabled: false

View File

@@ -14,6 +14,7 @@
#include "enums/messagecomponenttype.h" #include "enums/messagecomponenttype.h"
#include "enums/richformat.h" #include "enums/richformat.h"
#include "messagecontentmodel.h" #include "messagecontentmodel.h"
#include "neochatroom.h"
namespace namespace
{ {
@@ -27,18 +28,86 @@ ChatBarMessageContentModel::ChatBarMessageContentModel(QObject *parent)
{ {
m_editableActive = true; m_editableActive = true;
connect(this, &ChatBarMessageContentModel::roomChanged, this, [this]() { connect(this, &ChatBarMessageContentModel::roomChanged, this, [this](NeoChatRoom *oldRoom) {
if (m_type == ChatBarType::None || !m_room) { if (m_type == ChatBarType::None || !m_room) {
return; return;
} }
connectCache(oldRoom ? oldRoom->cacheForType(m_type) : nullptr);
initializeFromCache();
});
connect(this, &ChatBarMessageContentModel::focusRowChanged, this, [this]() {
m_markdownHelper->setTextItem(focusedTextItem());
m_keyHelper->textItem = focusedTextItem();
});
connect(this, &ChatBarMessageContentModel::roomChanged, this, [this]() {
for (const auto &component : m_components) {
if (const auto textItem = textItemForComponent(component)) {
textItem->setRoom(m_room);
}
}
m_keyHelper->room = m_room;
});
connect(this, &ChatBarMessageContentModel::typeChanged, this, [this](ChatBarType::Type oldType) {
for (const auto &component : m_components) {
if (const auto textItem = textItemForComponent(component)) {
textItem->setType(m_type);
}
}
if (!m_room) {
return;
}
connectCache(m_room->cacheForType(oldType));
initializeFromCache();
});
connect(m_markdownHelper, &ChatMarkdownHelper::unhandledBlockFormat, this, &ChatBarMessageContentModel::insertStyleAtCursor);
connectCache();
connectKeyHelper();
initializeModel();
}
void ChatBarMessageContentModel::connectCache(ChatBarCache *oldCache)
{
if (m_type == ChatBarType::None || !m_room) {
return;
}
if (oldCache) {
oldCache->disconnect(this);
}
connect(m_room->cacheForType(m_type), &ChatBarCache::relationIdChanged, this, &ChatBarMessageContentModel::updateReplyModel); connect(m_room->cacheForType(m_type), &ChatBarCache::relationIdChanged, this, &ChatBarMessageContentModel::updateReplyModel);
connect(m_room->cacheForType(m_type), &ChatBarCache::attachmentPathChanged, this, [this]() { connect(m_room->cacheForType(m_type), &ChatBarCache::attachmentPathChanged, this, [this]() {
if (m_room->cacheForType(m_type)->attachmentPath().length() > 0) { if (m_room->cacheForType(m_type)->attachmentPath().length() > 0) {
addAttachment(QUrl(m_room->cacheForType(m_type)->attachmentPath())); addAttachment(QUrl(m_room->cacheForType(m_type)->attachmentPath()));
} }
}); });
}
void ChatBarMessageContentModel::initializeModel(const QString &initialText)
{
updateReplyModel();
beginInsertRows({}, rowCount(), rowCount());
const auto textItem = new ChatTextItemHelper(this);
textItem->setRoom(m_room);
textItem->setType(m_type);
textItem->setInitialText(initialText);
connectTextItem(textItem);
m_components += MessageComponent{
.type = MessageComponentType::Text,
.display = {},
.attributes = {{TextItemKey, QVariant::fromValue<ChatTextItemHelper *>(textItem)}},
};
endInsertRows();
m_currentFocusComponent = QPersistentModelIndex(index(0));
Q_EMIT focusRowChanged();
}
void ChatBarMessageContentModel::initializeFromCache()
{
if (m_room->cacheForType(m_type)->attachmentPath().length() > 0) { if (m_room->cacheForType(m_type)->attachmentPath().length() > 0) {
addAttachment(QUrl(m_room->cacheForType(m_type)->attachmentPath())); addAttachment(QUrl(m_room->cacheForType(m_type)->attachmentPath()));
} }
@@ -67,51 +136,6 @@ ChatBarMessageContentModel::ChatBarMessageContentModel(QObject *parent)
m_currentFocusComponent = QPersistentModelIndex(index(rowCount() - 1)); m_currentFocusComponent = QPersistentModelIndex(index(rowCount() - 1));
Q_EMIT focusRowChanged(); Q_EMIT focusRowChanged();
});
connect(this, &ChatBarMessageContentModel::focusRowChanged, this, [this]() {
m_markdownHelper->setTextItem(focusedTextItem());
m_keyHelper->textItem = focusedTextItem();
});
connect(this, &ChatBarMessageContentModel::roomChanged, this, [this]() {
for (const auto &component : m_components) {
if (const auto textItem = textItemForComponent(component)) {
textItem->setRoom(m_room);
}
}
m_keyHelper->room = m_room;
});
connect(this, &ChatBarMessageContentModel::typeChanged, this, [this]() {
for (const auto &component : m_components) {
if (const auto textItem = textItemForComponent(component)) {
textItem->setType(m_type);
}
}
});
connect(m_markdownHelper, &ChatMarkdownHelper::unhandledBlockFormat, this, &ChatBarMessageContentModel::insertStyleAtCursor);
connectKeyHelper();
initializeModel();
}
void ChatBarMessageContentModel::initializeModel(const QString &initialText)
{
updateReplyModel();
beginInsertRows({}, rowCount(), rowCount());
const auto textItem = new ChatTextItemHelper(this);
textItem->setRoom(m_room);
textItem->setType(m_type);
textItem->setInitialText(initialText);
connectTextItem(textItem);
m_components += MessageComponent{
.type = MessageComponentType::Text,
.display = {},
.attributes = {{TextItemKey, QVariant::fromValue<ChatTextItemHelper *>(textItem)}},
};
endInsertRows();
m_currentFocusComponent = QPersistentModelIndex(index(0));
Q_EMIT focusRowChanged();
} }
ChatBarType::Type ChatBarMessageContentModel::type() const ChatBarType::Type ChatBarMessageContentModel::type() const
@@ -124,8 +148,8 @@ void ChatBarMessageContentModel::setType(ChatBarType::Type type)
if (type == m_type) { if (type == m_type) {
return; return;
} }
m_type = type; const auto oldType = std::exchange(m_type, type);
Q_EMIT typeChanged(); Q_EMIT typeChanged(oldType, m_type);
} }
ChatKeyHelper *ChatBarMessageContentModel::keyHelper() const ChatKeyHelper *ChatBarMessageContentModel::keyHelper() const

View File

@@ -6,6 +6,7 @@
#include <QAbstractListModel> #include <QAbstractListModel>
#include <QQmlEngine> #include <QQmlEngine>
#include "chatbarcache.h"
#include "chatkeyhelper.h" #include "chatkeyhelper.h"
#include "chatmarkdownhelper.h" #include "chatmarkdownhelper.h"
#include "chattextitemhelper.h" #include "chattextitemhelper.h"
@@ -100,15 +101,17 @@ public:
Q_INVOKABLE void postMessage(); Q_INVOKABLE void postMessage();
Q_SIGNALS: Q_SIGNALS:
void typeChanged(); void typeChanged(ChatBarType::Type oldType, ChatBarType::Type newType);
void focusRowChanged(); void focusRowChanged();
void hasRichFormattingChanged(); void hasRichFormattingChanged();
void sendMessageWithEnterChanged(); void sendMessageWithEnterChanged();
private: private:
ChatBarType::Type m_type = ChatBarType::None; ChatBarType::Type m_type = ChatBarType::None;
void connectCache(ChatBarCache *oldCache = nullptr);
void initializeModel(const QString &initialText = {}); void initializeModel(const QString &initialText = {});
void initializeFromCache();
std::optional<QString> getReplyEventId() override; std::optional<QString> getReplyEventId() override;

View File

@@ -67,7 +67,7 @@ void MessageContentModel::setRoom(NeoChatRoom *room)
m_room->disconnect(this); m_room->disconnect(this);
} }
m_room = room; const auto oldRoom = std::exchange(m_room, room);
if (m_room) { if (m_room) {
connect(m_room, &NeoChatRoom::newFileTransfer, this, [this](const QString &eventId) { connect(m_room, &NeoChatRoom::newFileTransfer, this, [this](const QString &eventId) {
@@ -103,7 +103,7 @@ void MessageContentModel::setRoom(NeoChatRoom *room)
connect(m_room, &NeoChatRoom::urlPreviewEnabledChanged, this, &MessageContentModel::componentsUpdated); connect(m_room, &NeoChatRoom::urlPreviewEnabledChanged, this, &MessageContentModel::componentsUpdated);
} }
Q_EMIT roomChanged(); Q_EMIT roomChanged(oldRoom, m_room);
} }
QString MessageContentModel::eventId() const QString MessageContentModel::eventId() const

View File

@@ -123,7 +123,7 @@ public:
Q_INVOKABLE void toggleSpoiler(QModelIndex index); Q_INVOKABLE void toggleSpoiler(QModelIndex index);
Q_SIGNALS: Q_SIGNALS:
void roomChanged(); void roomChanged(NeoChatRoom *oldRoom, NeoChatRoom *newRoom);
void authorChanged(); void authorChanged();
/** /**