Make it possible to send images in threads

Title
This commit is contained in:
James Graham
2025-03-22 18:56:40 +00:00
parent 3b2a6800a0
commit 72edfe1112

View File

@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com> // 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 // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
import QtCore
import QtQuick import QtQuick
import QtQuick.Controls as QQC2 import QtQuick.Controls as QQC2
import QtQuick.Layouts import QtQuick.Layouts
@@ -13,7 +14,7 @@ import org.kde.neochat.chatbar
/** /**
* @brief A component to show a chat bar in a message bubble. * @brief A component to show a chat bar in a message bubble.
*/ */
QQC2.TextArea { QQC2.Control {
id: root id: root
/** /**
@@ -22,16 +23,42 @@ QQC2.TextArea {
required property ChatBarCache chatBarCache required property ChatBarCache chatBarCache
onChatBarCacheChanged: documentHandler.chatBarCache = chatBarCache onChatBarCacheChanged: documentHandler.chatBarCache = chatBarCache
Layout.fillWidth: true readonly property bool isBusy: root.Message.room && root.Message.room.hasFileUploading
Layout.preferredWidth: textMetrics.advanceWidth + rightPadding + Kirigami.Units.smallSpacing + Kirigami.Units.gridUnit
Layout.maximumWidth: Message.maxContentWidth
Layout.minimumHeight: chatButtons.height + topPadding + bottomPadding
Layout.fillWidth: true
Layout.maximumWidth: Message.maxContentWidth
contentItem: ColumnLayout {
Loader {
id: paneLoader
Layout.fillWidth: true
Layout.margins: Kirigami.Units.largeSpacing
Layout.preferredHeight: active ? 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
Component.onCompleted: _private.updateText() Component.onCompleted: _private.updateText()
topPadding: Kirigami.Units.smallSpacing Layout.fillWidth: true
bottomPadding: Kirigami.Units.smallSpacing
rightPadding: chatButtons.width + chatButtons.anchors.rightMargin * 2
color: Kirigami.Theme.textColor color: Kirigami.Theme.textColor
verticalAlignment: TextEdit.AlignVCenter verticalAlignment: TextEdit.AlignVCenter
@@ -45,7 +72,7 @@ QQC2.TextArea {
if (completionMenu.visible) { if (completionMenu.visible) {
completionMenu.complete(); completionMenu.complete();
} else if (event.modifiers & Qt.ShiftModifier) { } else if (event.modifiers & Qt.ShiftModifier) {
root.insert(cursorPosition, "\n"); textArea.insert(cursorPosition, "\n");
} else { } else {
_private.post(); _private.post();
} }
@@ -54,7 +81,7 @@ QQC2.TextArea {
if (completionMenu.visible) { if (completionMenu.visible) {
completionMenu.complete(); completionMenu.complete();
} else if (event.modifiers & Qt.ShiftModifier) { } else if (event.modifiers & Qt.ShiftModifier) {
root.insert(cursorPosition, "\n"); textArea.insert(cursorPosition, "\n");
} else { } else {
_private.post(); _private.post();
} }
@@ -72,16 +99,80 @@ QQC2.TextArea {
} }
} }
/** CompletionMenu {
* This is anchored like this so that control expands properly as the id: completionMenu
* text grows in length. height: implicitHeight
*/ y: -height - 5
RowLayout { z: 10
id: chatButtons connection: root.Message.room.connection
anchors.verticalCenter: root.verticalCenter chatDocumentHandler: documentHandler
anchors.right: root.right Behavior on height {
anchors.rightMargin: Kirigami.Units.smallSpacing NumberAnimation {
spacing: 0 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
ChatDocumentHandler {
id: documentHandler
document: textArea.textDocument
cursorPosition: textArea.cursorPosition
selectionStart: textArea.selectionStart
selectionEnd: textArea.selectionEnd
room: root.Message.room // We don't care about saving for edits so this is OK.
mentionColor: Kirigami.Theme.linkColor
errorColor: Kirigami.Theme.negativeTextColor
}
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
action: Kirigami.Action {
text: i18nc("@action:button", "Attach an image or file")
icon.name: "mail-attachment"
onTriggered: {
let dialog = (Clipboard.hasImage ? attachDialog : openFileDialog).createObject(QQC2.Overlay.overlay);
dialog.chosen.connect(path => root.chatBarCache.attachmentPath = path);
dialog.open();
}
}
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
}
QQC2.ToolButton { QQC2.ToolButton {
display: QQC2.AbstractButton.IconOnly display: QQC2.AbstractButton.IconOnly
action: Kirigami.Action { action: Kirigami.Action {
@@ -108,40 +199,58 @@ QQC2.TextArea {
QQC2.ToolTip.visible: hovered QQC2.ToolTip.visible: hovered
} }
} }
CompletionMenu {
id: completionMenu
height: implicitHeight
y: -height - 5
z: 10
connection: root.Message.room.connection
chatDocumentHandler: documentHandler
Behavior on height {
NumberAnimation {
property: "height"
duration: Kirigami.Units.shortDuration
easing.type: Easing.OutCubic
} }
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)
} }
} }
// opt-out of whatever spell checker a styled TextArea might come with Component {
Kirigami.SpellCheck.enabled: false id: replyPane
Item {
ChatDocumentHandler { implicitWidth: replyComponent.implicitWidth
id: documentHandler implicitHeight: replyComponent.implicitHeight
document: root.textDocument ReplyComponent {
cursorPosition: root.cursorPosition id: replyComponent
selectionStart: root.selectionStart replyEventId: root.chatBarCache.replyId
selectionEnd: root.selectionEnd replyAuthor: root.chatBarCache.relationAuthor
room: root.Message.room // We don't care about saving for edits so this is OK. replyContentModel: root.chatBarCache.relationEventContentModel
mentionColor: Kirigami.Theme.linkColor Message.maxContentWidth: paneLoader.item.width
errorColor: Kirigami.Theme.negativeTextColor
} }
QQC2.Button {
id: cancelButton
TextMetrics { anchors.top: parent.top
id: textMetrics anchors.right: parent.right
text: root.text
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 { QtObject {
@@ -151,16 +260,16 @@ QQC2.TextArea {
// This could possibly be undefined due to some esoteric QtQuick issue. Referencing it somewhere in JS is enough. // This could possibly be undefined due to some esoteric QtQuick issue. Referencing it somewhere in JS is enough.
documentHandler.document; documentHandler.document;
if (chatBarCache?.isEditing && chatBarCache.relationMessage.length > 0) { if (chatBarCache?.isEditing && chatBarCache.relationMessage.length > 0) {
root.text = chatBarCache.relationMessage; textArea.text = chatBarCache.relationMessage;
root.chatBarCache.updateMentions(root.textDocument, documentHandler); root.chatBarCache.updateMentions(root.textDocument, documentHandler);
root.forceActiveFocus(); textArea.forceActiveFocus();
root.cursorPosition = root.text.length; textArea.cursorPosition = textArea.text.length;
} }
} }
function post() { function post() {
root.chatBarCache.postMessage(); root.chatBarCache.postMessage();
root.clear(); textArea.clear();
root.chatBarCache.clearRelations(); root.chatBarCache.clearRelations();
} }
} }