Really reenable editing messages, don't just do half a job.
This commit is contained in:
@@ -151,11 +151,6 @@ DelegateChooser {
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MessageComponentType.ChatBar
|
||||
delegate: ChatBarComponent {}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MessageComponentType.ReplyButton
|
||||
delegate: ReplyButtonComponent {}
|
||||
|
||||
@@ -11,7 +11,6 @@ ecm_add_qml_module(MessageContent GENERATE_PLUGIN_SOURCE
|
||||
ReplyMessageComponentChooser.qml
|
||||
AuthorComponent.qml
|
||||
AudioComponent.qml
|
||||
ChatBarComponent.qml
|
||||
CodeComponent.qml
|
||||
EncryptedComponent.qml
|
||||
FetchButtonComponent.qml
|
||||
|
||||
@@ -1,269 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtCore
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
import org.kde.neochat
|
||||
import org.kde.neochat.chatbar
|
||||
|
||||
/**
|
||||
* @brief A component to show a chat bar in a message bubble.
|
||||
*/
|
||||
QQC2.Control {
|
||||
id: root
|
||||
|
||||
/**
|
||||
* @brief The ChatBarCache to use.
|
||||
*/
|
||||
required property ChatBarCache chatBarCache
|
||||
|
||||
readonly property bool isBusy: root.Message.room && root.Message.room.hasFileUploading
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: Message.maxContentWidth
|
||||
implicitWidth: Message.maxContentWidth
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
Loader {
|
||||
id: paneLoader
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Kirigami.Units.largeSpacing
|
||||
Layout.preferredHeight: active ? (item as Item).implicitHeight : 0
|
||||
|
||||
active: visible
|
||||
visible: root.chatBarCache.replyId.length > 0 || root.chatBarCache.attachmentPath.length > 0
|
||||
sourceComponent: root.chatBarCache.replyId.length > 0 ? replyPane : attachmentPane
|
||||
}
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
QQC2.ScrollView {
|
||||
id: chatBarScrollView
|
||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||
Layout.bottomMargin: Kirigami.Units.smallSpacing
|
||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumHeight: Kirigami.Units.gridUnit * 8
|
||||
|
||||
QQC2.TextArea {
|
||||
id: textArea
|
||||
|
||||
// Work around for BUG: 503846
|
||||
// Seems to crash when we try to access textArea's text here. Even though we add a slight delay it's still instantaneous in the UI.
|
||||
Component.onCompleted: Qt.callLater(() => _private.updateText())
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
color: Kirigami.Theme.textColor
|
||||
verticalAlignment: TextEdit.AlignVCenter
|
||||
wrapMode: TextEdit.Wrap
|
||||
|
||||
onTextChanged: {
|
||||
root.chatBarCache.text = text;
|
||||
}
|
||||
|
||||
Keys.onEnterPressed: event => {
|
||||
if (completionMenu.visible) {
|
||||
completionMenu.completeCurrent();
|
||||
} else if (event.modifiers & Qt.ShiftModifier) {
|
||||
textArea.insert(cursorPosition, "\n");
|
||||
} else {
|
||||
_private.post();
|
||||
}
|
||||
}
|
||||
Keys.onReturnPressed: event => {
|
||||
if (completionMenu.visible) {
|
||||
completionMenu.completeCurrent();
|
||||
} else if (event.modifiers & Qt.ShiftModifier) {
|
||||
textArea.insert(cursorPosition, "\n");
|
||||
} else {
|
||||
_private.post();
|
||||
}
|
||||
}
|
||||
Keys.onTabPressed: {
|
||||
if (completionMenu.visible) {
|
||||
completionMenu.completeCurrent();
|
||||
}
|
||||
}
|
||||
Keys.onPressed: event => {
|
||||
if (event.key === Qt.Key_Up && completionMenu.visible) {
|
||||
completionMenu.decrementIndex();
|
||||
} else if (event.key === Qt.Key_Down && completionMenu.visible) {
|
||||
completionMenu.incrementIndex();
|
||||
}
|
||||
}
|
||||
|
||||
// CompletionMenu {
|
||||
// id: completionMenu
|
||||
// width: Math.max(350, root.width - 1)
|
||||
// height: implicitHeight
|
||||
// y: -height - 5
|
||||
// z: 10
|
||||
// margins: 0
|
||||
// Behavior on height {
|
||||
// NumberAnimation {
|
||||
// property: "height"
|
||||
// duration: Kirigami.Units.shortDuration
|
||||
// easing.type: Easing.OutCubic
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// opt-out of whatever spell checker a styled TextArea might come with
|
||||
Kirigami.SpellCheck.enabled: false
|
||||
|
||||
TextMetrics {
|
||||
id: textMetrics
|
||||
text: textArea.text
|
||||
}
|
||||
|
||||
Component {
|
||||
id: openFileDialog
|
||||
|
||||
OpenFileDialog {
|
||||
parentWindow: Window.window
|
||||
currentFolder: StandardPaths.standardLocations(StandardPaths.HomeLocation)[0]
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: attachDialog
|
||||
|
||||
AttachDialog {
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
|
||||
background: null
|
||||
}
|
||||
}
|
||||
PieProgressBar {
|
||||
visible: root.isBusy
|
||||
progress: root.Message.room.fileUploadingProgress
|
||||
}
|
||||
QQC2.ToolButton {
|
||||
visible: !root.isBusy
|
||||
display: QQC2.AbstractButton.IconOnly
|
||||
text: i18nc("@action:button", "Attach an image or file")
|
||||
icon.name: "mail-attachment"
|
||||
onClicked: {
|
||||
let dialog = (Clipboard.hasImage ? attachDialog : openFileDialog).createObject(QQC2.Overlay.overlay) as QQC2.Dialog;
|
||||
dialog.chosen.connect(path => root.chatBarCache.attachmentPath = path);
|
||||
dialog.open();
|
||||
}
|
||||
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
}
|
||||
QQC2.ToolButton {
|
||||
display: QQC2.AbstractButton.IconOnly
|
||||
text: root.chatBarCache.isEditing ? i18nc("@action:button", "Confirm edit") : i18nc("@action:button", "Post message in thread")
|
||||
icon.name: "document-send"
|
||||
onClicked: _private.post()
|
||||
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
}
|
||||
QQC2.ToolButton {
|
||||
id: cancelButton
|
||||
display: QQC2.AbstractButton.IconOnly
|
||||
text: i18nc("@action:button", "Cancel")
|
||||
icon.name: "dialog-close"
|
||||
onClicked: {
|
||||
root.chatBarCache.clearRelations();
|
||||
}
|
||||
|
||||
Kirigami.Action {
|
||||
shortcut: "Escape"
|
||||
onTriggered: cancelButton.clicked()
|
||||
}
|
||||
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
radius: Kirigami.Units.cornerRadius
|
||||
border {
|
||||
width: 1
|
||||
color: Kirigami.ColorUtils.linearInterpolation(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, Kirigami.Theme.frameContrast)
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: replyPane
|
||||
Item {
|
||||
implicitWidth: replyComponent.implicitWidth
|
||||
implicitHeight: replyComponent.implicitHeight
|
||||
ReplyComponent {
|
||||
id: replyComponent
|
||||
replyContentModel: ContentProvider.contentModelForEvent(root.Message.room, root.chatBarCache.replyId, true)
|
||||
Message.maxContentWidth: paneLoader.item.width
|
||||
}
|
||||
QQC2.Button {
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
|
||||
display: QQC2.AbstractButton.IconOnly
|
||||
text: i18nc("@action:button", "Cancel reply")
|
||||
icon.name: "dialog-close"
|
||||
onClicked: {
|
||||
root.chatBarCache.replyId = "";
|
||||
root.chatBarCache.attachmentPath = "";
|
||||
}
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
}
|
||||
}
|
||||
}
|
||||
Component {
|
||||
id: attachmentPane
|
||||
AttachmentPane {
|
||||
attachmentPath: root.chatBarCache.attachmentPath
|
||||
|
||||
onAttachmentCancelled: {
|
||||
root.chatBarCache.attachmentPath = "";
|
||||
root.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: _private
|
||||
|
||||
function updateText() {
|
||||
// This could possibly be undefined due to some esoteric QtQuick issue. Referencing it somewhere in JS is enough.
|
||||
documentHandler.document;
|
||||
if (root.chatBarCache?.isEditing && root.chatBarCache.relationMessage.length > 0) {
|
||||
textArea.text = root.chatBarCache.relationMessage;
|
||||
documentHandler.updateMentions(root.chatBarCache.editId);
|
||||
textArea.forceActiveFocus();
|
||||
textArea.cursorPosition = textArea.text.length;
|
||||
}
|
||||
}
|
||||
|
||||
function post() {
|
||||
root.chatBarCache.postMessage();
|
||||
textArea.clear();
|
||||
root.chatBarCache.clearRelations();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,11 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Qt.labs.qmlmodels
|
||||
|
||||
import org.kde.neochat
|
||||
import org.kde.neochat.libneochat as LibNeoChat
|
||||
|
||||
/**
|
||||
* @brief Select a message component based on a MessageComponentType.
|
||||
@@ -23,4 +25,15 @@ BaseMessageComponentChooser {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MessageComponentType.ChatBar
|
||||
delegate: ChatBarCore {
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: Message.maxContentWidth
|
||||
room: Message.room
|
||||
chatBarType: LibNeoChat.ChatBarType.Edit
|
||||
maxAvailableWidth: Message.maxContentWidth
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,17 @@ void ChatBarMessageContentModel::connectCache(ChatBarCache *oldCache)
|
||||
oldCache->disconnect(this);
|
||||
}
|
||||
|
||||
connect(m_room->cacheForType(m_type), &ChatBarCache::relationIdChanged, this, &ChatBarMessageContentModel::updateReplyModel);
|
||||
connect(m_room->cacheForType(m_type), &ChatBarCache::relationIdChanged, this, [this]() {
|
||||
if (!m_room || m_type == ChatBarType::None) {
|
||||
return;
|
||||
}
|
||||
const auto currentCache = m_room->cacheForType(m_type);
|
||||
if (currentCache->isReplying()) {
|
||||
updateReplyModel();
|
||||
} else if (currentCache->isEditing()) {
|
||||
initializeFromCache();
|
||||
}
|
||||
});
|
||||
connect(m_room->cacheForType(m_type), &ChatBarCache::attachmentPathChanged, this, [this]() {
|
||||
if (m_room->cacheForType(m_type)->attachmentPath().length() > 0) {
|
||||
addAttachment(QUrl(m_room->cacheForType(m_type)->attachmentPath()));
|
||||
@@ -114,7 +124,8 @@ void ChatBarMessageContentModel::initializeFromCache()
|
||||
|
||||
clearModel();
|
||||
|
||||
const auto textSections = m_room->cacheForType(m_type)->text().split(u"\n\n"_s);
|
||||
const auto currentCache = m_room->cacheForType(m_type);
|
||||
const auto textSections = (m_type == ChatBarType::Room ? currentCache->text() : currentCache->relationMessage()).split(u"\n\n"_s);
|
||||
if (textSections.length() == 1 && textSections[0].isEmpty()) {
|
||||
initializeModel();
|
||||
return;
|
||||
@@ -334,6 +345,11 @@ bool ChatBarMessageContentModel::hasRichFormatting() const
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ChatBarMessageContentModel::hasAttachment() const
|
||||
{
|
||||
return hasComponentType({MessageComponentType::File, MessageComponentType::Audio, MessageComponentType::Image, MessageComponentType::Video});
|
||||
}
|
||||
|
||||
void ChatBarMessageContentModel::addAttachment(const QUrl &path)
|
||||
{
|
||||
if (m_type == ChatBarType::None || !m_room) {
|
||||
@@ -360,6 +376,7 @@ void ChatBarMessageContentModel::addAttachment(const QUrl &path)
|
||||
it->display = path.fileName();
|
||||
Q_EMIT dataChanged(index(std::distance(m_components.begin(), it)), index(std::distance(m_components.begin(), it)), {DisplayRole});
|
||||
m_room->cacheForType(m_type)->setAttachmentPath(path.toString());
|
||||
Q_EMIT hasAttachmentChanged();
|
||||
}
|
||||
|
||||
ChatBarMessageContentModel::ComponentIt
|
||||
@@ -477,6 +494,7 @@ void ChatBarMessageContentModel::removeAttachment()
|
||||
if (m_room) {
|
||||
m_room->cacheForType(m_type)->setAttachmentPath({});
|
||||
}
|
||||
Q_EMIT hasAttachmentChanged();
|
||||
}
|
||||
|
||||
bool ChatBarMessageContentModel::sendMessageWithEnter() const
|
||||
|
||||
@@ -67,6 +67,11 @@ class ChatBarMessageContentModel : public MessageContentModel
|
||||
*/
|
||||
Q_PROPERTY(bool hasRichFormatting READ hasRichFormatting NOTIFY hasRichFormattingChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether the model has an attachment..
|
||||
*/
|
||||
Q_PROPERTY(bool hasAttachment READ hasAttachment NOTIFY hasAttachmentChanged)
|
||||
|
||||
/**
|
||||
* @brief The UserListModel to be used for room completions.
|
||||
*/
|
||||
@@ -89,6 +94,7 @@ public:
|
||||
Q_INVOKABLE void insertComponentAtCursor(MessageComponentType::Type type);
|
||||
|
||||
bool hasRichFormatting() const;
|
||||
bool hasAttachment() const;
|
||||
Q_INVOKABLE void addAttachment(const QUrl &path);
|
||||
|
||||
Q_INVOKABLE void removeComponent(int row, bool removeLast = false);
|
||||
@@ -104,6 +110,7 @@ Q_SIGNALS:
|
||||
void typeChanged(ChatBarType::Type oldType, ChatBarType::Type newType);
|
||||
void focusRowChanged();
|
||||
void hasRichFormattingChanged();
|
||||
void hasAttachmentChanged();
|
||||
void sendMessageWithEnterChanged();
|
||||
|
||||
private:
|
||||
|
||||
@@ -244,7 +244,7 @@ QHash<int, QByteArray> MessageContentModel::roleNamesStatic()
|
||||
return roles;
|
||||
}
|
||||
|
||||
bool MessageContentModel::hasComponentType(MessageComponentType::Type type)
|
||||
bool MessageContentModel::hasComponentType(MessageComponentType::Type type) const
|
||||
{
|
||||
return std::find_if(m_components.cbegin(),
|
||||
m_components.cend(),
|
||||
@@ -254,7 +254,7 @@ bool MessageContentModel::hasComponentType(MessageComponentType::Type type)
|
||||
!= m_components.cend();
|
||||
}
|
||||
|
||||
bool MessageContentModel::hasComponentType(QList<MessageComponentType::Type> types)
|
||||
bool MessageContentModel::hasComponentType(QList<MessageComponentType::Type> types) const
|
||||
{
|
||||
for (const auto &type : types) {
|
||||
if (hasComponentType(type)) {
|
||||
|
||||
@@ -164,8 +164,8 @@ protected:
|
||||
using ComponentIt = QList<MessageComponent>::iterator;
|
||||
|
||||
QList<MessageComponent> m_components;
|
||||
bool hasComponentType(MessageComponentType::Type type);
|
||||
bool hasComponentType(QList<MessageComponentType::Type> types);
|
||||
bool hasComponentType(MessageComponentType::Type type) const;
|
||||
bool hasComponentType(QList<MessageComponentType::Type> types) const;
|
||||
void forEachComponentOfType(MessageComponentType::Type type, std::function<ComponentIt(ComponentIt)> function);
|
||||
void forEachComponentOfType(QList<MessageComponentType::Type> types, std::function<ComponentIt(ComponentIt)> function);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user