Make it possible to send images in threads
Title
This commit is contained in:
@@ -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,126 +23,234 @@ QQC2.TextArea {
|
|||||||
required property ChatBarCache chatBarCache
|
required property ChatBarCache chatBarCache
|
||||||
onChatBarCacheChanged: documentHandler.chatBarCache = chatBarCache
|
onChatBarCacheChanged: documentHandler.chatBarCache = chatBarCache
|
||||||
|
|
||||||
|
readonly property bool isBusy: root.Message.room && root.Message.room.hasFileUploading
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredWidth: textMetrics.advanceWidth + rightPadding + Kirigami.Units.smallSpacing + Kirigami.Units.gridUnit
|
|
||||||
Layout.maximumWidth: Message.maxContentWidth
|
Layout.maximumWidth: Message.maxContentWidth
|
||||||
Layout.minimumHeight: chatButtons.height + topPadding + bottomPadding
|
|
||||||
|
|
||||||
Component.onCompleted: _private.updateText()
|
contentItem: ColumnLayout {
|
||||||
|
Loader {
|
||||||
|
id: paneLoader
|
||||||
|
|
||||||
topPadding: Kirigami.Units.smallSpacing
|
Layout.fillWidth: true
|
||||||
bottomPadding: Kirigami.Units.smallSpacing
|
Layout.margins: Kirigami.Units.largeSpacing
|
||||||
rightPadding: chatButtons.width + chatButtons.anchors.rightMargin * 2
|
Layout.preferredHeight: active ? item.implicitHeight : 0
|
||||||
|
|
||||||
color: Kirigami.Theme.textColor
|
active: visible
|
||||||
verticalAlignment: TextEdit.AlignVCenter
|
visible: root.chatBarCache.replyId.length > 0 || root.chatBarCache.attachmentPath.length > 0
|
||||||
wrapMode: TextEdit.Wrap
|
sourceComponent: root.chatBarCache.replyId.length > 0 ? replyPane : attachmentPane
|
||||||
|
|
||||||
onTextChanged: {
|
|
||||||
root.chatBarCache.text = text;
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onEnterPressed: {
|
|
||||||
if (completionMenu.visible) {
|
|
||||||
completionMenu.complete();
|
|
||||||
} else if (event.modifiers & Qt.ShiftModifier) {
|
|
||||||
root.insert(cursorPosition, "\n");
|
|
||||||
} else {
|
|
||||||
_private.post();
|
|
||||||
}
|
}
|
||||||
}
|
RowLayout {
|
||||||
Keys.onReturnPressed: {
|
Layout.fillWidth: true
|
||||||
if (completionMenu.visible) {
|
spacing: 0
|
||||||
completionMenu.complete();
|
|
||||||
} else if (event.modifiers & Qt.ShiftModifier) {
|
|
||||||
root.insert(cursorPosition, "\n");
|
|
||||||
} else {
|
|
||||||
_private.post();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Keys.onTabPressed: {
|
|
||||||
if (completionMenu.visible) {
|
|
||||||
completionMenu.complete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Keys.onPressed: event => {
|
|
||||||
if (event.key === Qt.Key_Up && completionMenu.visible) {
|
|
||||||
completionMenu.decrementIndex();
|
|
||||||
} else if (event.key === Qt.Key_Down && completionMenu.visible) {
|
|
||||||
completionMenu.incrementIndex();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
QQC2.ScrollView {
|
||||||
* This is anchored like this so that control expands properly as the
|
id: chatBarScrollView
|
||||||
* text grows in length.
|
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||||
*/
|
Layout.bottomMargin: Kirigami.Units.smallSpacing
|
||||||
RowLayout {
|
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||||
id: chatButtons
|
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||||
anchors.verticalCenter: root.verticalCenter
|
|
||||||
anchors.right: root.right
|
Layout.fillWidth: true
|
||||||
anchors.rightMargin: Kirigami.Units.smallSpacing
|
Layout.maximumHeight: Kirigami.Units.gridUnit * 8
|
||||||
spacing: 0
|
|
||||||
QQC2.ToolButton {
|
QQC2.TextArea {
|
||||||
display: QQC2.AbstractButton.IconOnly
|
id: textArea
|
||||||
action: Kirigami.Action {
|
Component.onCompleted: _private.updateText()
|
||||||
text: root.chatBarCache.isEditing ? i18nc("@action:button", "Confirm edit") : i18nc("@action:button", "Post message in thread")
|
|
||||||
icon.name: "document-send"
|
Layout.fillWidth: true
|
||||||
onTriggered: {
|
|
||||||
_private.post();
|
color: Kirigami.Theme.textColor
|
||||||
|
verticalAlignment: TextEdit.AlignVCenter
|
||||||
|
wrapMode: TextEdit.Wrap
|
||||||
|
|
||||||
|
onTextChanged: {
|
||||||
|
root.chatBarCache.text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onEnterPressed: {
|
||||||
|
if (completionMenu.visible) {
|
||||||
|
completionMenu.complete();
|
||||||
|
} else if (event.modifiers & Qt.ShiftModifier) {
|
||||||
|
textArea.insert(cursorPosition, "\n");
|
||||||
|
} else {
|
||||||
|
_private.post();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Keys.onReturnPressed: {
|
||||||
|
if (completionMenu.visible) {
|
||||||
|
completionMenu.complete();
|
||||||
|
} else if (event.modifiers & Qt.ShiftModifier) {
|
||||||
|
textArea.insert(cursorPosition, "\n");
|
||||||
|
} else {
|
||||||
|
_private.post();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Keys.onTabPressed: {
|
||||||
|
if (completionMenu.visible) {
|
||||||
|
completionMenu.complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
QQC2.ToolTip.text: text
|
PieProgressBar {
|
||||||
QQC2.ToolTip.visible: hovered
|
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 {
|
||||||
|
display: QQC2.AbstractButton.IconOnly
|
||||||
|
action: Kirigami.Action {
|
||||||
|
text: root.chatBarCache.isEditing ? i18nc("@action:button", "Confirm edit") : i18nc("@action:button", "Post message in thread")
|
||||||
|
icon.name: "document-send"
|
||||||
|
onTriggered: {
|
||||||
|
_private.post();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QQC2.ToolTip.text: text
|
||||||
|
QQC2.ToolTip.visible: hovered
|
||||||
|
}
|
||||||
|
QQC2.ToolButton {
|
||||||
|
display: QQC2.AbstractButton.IconOnly
|
||||||
|
action: Kirigami.Action {
|
||||||
|
text: i18nc("@action:button", "Cancel")
|
||||||
|
icon.name: "dialog-close"
|
||||||
|
onTriggered: {
|
||||||
|
root.chatBarCache.clearRelations();
|
||||||
|
}
|
||||||
|
shortcut: "Escape"
|
||||||
|
}
|
||||||
|
QQC2.ToolTip.text: text
|
||||||
|
QQC2.ToolTip.visible: hovered
|
||||||
|
}
|
||||||
}
|
}
|
||||||
QQC2.ToolButton {
|
}
|
||||||
display: QQC2.AbstractButton.IconOnly
|
|
||||||
action: Kirigami.Action {
|
background: Rectangle {
|
||||||
text: i18nc("@action:button", "Cancel")
|
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
|
||||||
|
replyEventId: root.chatBarCache.replyId
|
||||||
|
replyAuthor: root.chatBarCache.relationAuthor
|
||||||
|
replyContentModel: root.chatBarCache.relationEventContentModel
|
||||||
|
Message.maxContentWidth: paneLoader.item.width
|
||||||
|
}
|
||||||
|
QQC2.Button {
|
||||||
|
id: cancelButton
|
||||||
|
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.right: parent.right
|
||||||
|
|
||||||
|
display: QQC2.AbstractButton.IconOnly
|
||||||
|
text: i18nc("@action:button", "Cancel reply")
|
||||||
icon.name: "dialog-close"
|
icon.name: "dialog-close"
|
||||||
onTriggered: {
|
onClicked: {
|
||||||
root.chatBarCache.clearRelations();
|
root.chatBarCache.replyId = "";
|
||||||
|
root.chatBarCache.attachmentPath = "";
|
||||||
}
|
}
|
||||||
shortcut: "Escape"
|
QQC2.ToolTip.text: text
|
||||||
}
|
QQC2.ToolTip.visible: hovered
|
||||||
QQC2.ToolTip.text: text
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Component {
|
||||||
|
id: attachmentPane
|
||||||
|
AttachmentPane {
|
||||||
|
attachmentPath: root.chatBarCache.attachmentPath
|
||||||
|
|
||||||
// opt-out of whatever spell checker a styled TextArea might come with
|
onAttachmentCancelled: {
|
||||||
Kirigami.SpellCheck.enabled: false
|
root.chatBarCache.attachmentPath = "";
|
||||||
|
root.forceActiveFocus();
|
||||||
ChatDocumentHandler {
|
}
|
||||||
id: documentHandler
|
}
|
||||||
document: root.textDocument
|
|
||||||
cursorPosition: root.cursorPosition
|
|
||||||
selectionStart: root.selectionStart
|
|
||||||
selectionEnd: root.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: root.text
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user