Use singleton to pass edit/reply content to chatbox
This significantly reduce the complexity of everything.
This commit is contained in:
@@ -14,17 +14,14 @@ import org.kde.neochat 1.0
|
|||||||
Loader {
|
Loader {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property string attachmentPath: ""
|
property var attachmentMimetype: FileType.mimeTypeForUrl(ChatBoxHelper.attachmentPath)
|
||||||
property var attachmentMimetype: FileType.mimeTypeForUrl(attachmentPath)
|
|
||||||
readonly property bool hasImage: attachmentMimetype.valid && FileType.supportedImageFormats.includes(attachmentMimetype.preferredSuffix)
|
readonly property bool hasImage: attachmentMimetype.valid && FileType.supportedImageFormats.includes(attachmentMimetype.preferredSuffix)
|
||||||
|
|
||||||
signal clearAttachmentTriggered()
|
|
||||||
|
|
||||||
active: visible
|
active: visible
|
||||||
sourceComponent: Component {
|
sourceComponent: Component {
|
||||||
Pane {
|
Pane {
|
||||||
id: attachmentPane
|
id: attachmentPane
|
||||||
property string baseFileName: attachmentPath.toString().substring(attachmentPath.toString().lastIndexOf('/') + 1, attachmentPath.length)
|
property string baseFileName: ChatBoxHelper.attachmentPath.toString().substring(ChatBoxHelper.attachmentPath.toString().lastIndexOf('/') + 1, ChatBoxHelper.attachmentPath.length)
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||||
|
|
||||||
contentItem: Item {
|
contentItem: Item {
|
||||||
@@ -49,7 +46,7 @@ Loader {
|
|||||||
asynchronous: true
|
asynchronous: true
|
||||||
cache: false // Cache is not needed. Images will rarely be shown repeatedly.
|
cache: false // Cache is not needed. Images will rarely be shown repeatedly.
|
||||||
smooth: height == preferredHeight && parent.height == parent.implicitHeight // Don't smooth until height animation stops
|
smooth: height == preferredHeight && parent.height == parent.implicitHeight // Don't smooth until height animation stops
|
||||||
source: hasImage ? attachmentPath : ""
|
source: hasImage ? ChatBoxHelper.attachmentPath : ""
|
||||||
visible: hasImage
|
visible: hasImage
|
||||||
fillMode: Image.PreserveAspectFit
|
fillMode: Image.PreserveAspectFit
|
||||||
|
|
||||||
@@ -165,14 +162,14 @@ Loader {
|
|||||||
Component {
|
Component {
|
||||||
id: imageEditorPage
|
id: imageEditorPage
|
||||||
ImageEditorPage {
|
ImageEditorPage {
|
||||||
imagePath: attachmentPath
|
imagePath: ChatBoxHelper.attachmentPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onClicked: {
|
onClicked: {
|
||||||
let imageEditor = applicationWindow().pageStack.layers.push(imageEditorPage);
|
let imageEditor = applicationWindow().pageStack.layers.push(imageEditorPage);
|
||||||
imageEditor.newPathChanged.connect(function(newPath) {
|
imageEditor.newPathChanged.connect(function(newPath) {
|
||||||
applicationWindow().pageStack.layers.pop();
|
applicationWindow().pageStack.layers.pop();
|
||||||
attachmentPath = newPath;
|
ChatBoxHelper.attachmentPath = newPath;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
ToolTip.text: text
|
ToolTip.text: text
|
||||||
@@ -183,9 +180,7 @@ Loader {
|
|||||||
icon.name: "dialog-cancel"
|
icon.name: "dialog-cancel"
|
||||||
text: i18n("Cancel")
|
text: i18n("Cancel")
|
||||||
display: AbstractButton.IconOnly
|
display: AbstractButton.IconOnly
|
||||||
onClicked: {
|
onClicked: ChatBoxHelper.clearAttachment();
|
||||||
clearAttachmentTriggered();
|
|
||||||
}
|
|
||||||
ToolTip.text: text
|
ToolTip.text: text
|
||||||
ToolTip.visible: hovered
|
ToolTip.visible: hovered
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ ToolBar {
|
|||||||
id: chatBar
|
id: chatBar
|
||||||
property string replyEventId: ""
|
property string replyEventId: ""
|
||||||
property string editEventId: ""
|
property string editEventId: ""
|
||||||
property string inputFieldText: currentRoom ? currentRoom.cachedInput : ""
|
property alias inputFieldText: inputField.text
|
||||||
property alias textField: inputField
|
property alias textField: inputField
|
||||||
property alias emojiPaneOpened: emojiButton.checked
|
property alias emojiPaneOpened: emojiButton.checked
|
||||||
|
|
||||||
@@ -24,7 +24,6 @@ ToolBar {
|
|||||||
// This use an hack to define: https://doc.qt.io/qt-5/qml-var.html#property-value-initialization-semantics
|
// This use an hack to define: https://doc.qt.io/qt-5/qml-var.html#property-value-initialization-semantics
|
||||||
property var userAutocompleted: ({})
|
property var userAutocompleted: ({})
|
||||||
|
|
||||||
signal attachTriggered(string localPath)
|
|
||||||
signal closeAllTriggered()
|
signal closeAllTriggered()
|
||||||
signal inputFieldForceActiveFocusTriggered()
|
signal inputFieldForceActiveFocusTriggered()
|
||||||
signal messageSent()
|
signal messageSent()
|
||||||
@@ -220,14 +219,14 @@ ToolBar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
visible: !isReply && (!hasAttachment || uploadingBusySpinner.running)
|
visible: !ChatBoxHelper.isReplying && (!ChatBoxHelper.hasAttachment || uploadingBusySpinner.running)
|
||||||
implicitWidth: uploadButton.implicitWidth
|
implicitWidth: uploadButton.implicitWidth
|
||||||
implicitHeight: uploadButton.implicitHeight
|
implicitHeight: uploadButton.implicitHeight
|
||||||
ToolButton {
|
ToolButton {
|
||||||
id: uploadButton
|
id: uploadButton
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
// Matrix does not allow sending attachments in replies
|
// Matrix does not allow sending attachments in replies
|
||||||
visible: !isReply && !hasAttachment && !uploadingBusySpinner.running
|
visible: !ChatBoxHelper.isReplying && !ChatBoxHelper.hasAttachment && !uploadingBusySpinner.running
|
||||||
icon.name: "mail-attachment"
|
icon.name: "mail-attachment"
|
||||||
text: i18n("Attach an image or file")
|
text: i18n("Attach an image or file")
|
||||||
display: AbstractButton.IconOnly
|
display: AbstractButton.IconOnly
|
||||||
@@ -239,7 +238,7 @@ ToolBar {
|
|||||||
var fileDialog = openFileDialog.createObject(ApplicationWindow.overlay)
|
var fileDialog = openFileDialog.createObject(ApplicationWindow.overlay)
|
||||||
fileDialog.chosen.connect((path) => {
|
fileDialog.chosen.connect((path) => {
|
||||||
if (!path) { return }
|
if (!path) { return }
|
||||||
attachTriggered(path)
|
ChatBoxHelper.attachmentPath = path;
|
||||||
})
|
})
|
||||||
fileDialog.open()
|
fileDialog.open()
|
||||||
}
|
}
|
||||||
@@ -318,13 +317,21 @@ ToolBar {
|
|||||||
if (!Clipboard.saveImage(localPath)) {
|
if (!Clipboard.saveImage(localPath)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
attachTriggered(localPath)
|
ChatBoxHelper.attachmentPath = localPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
function postMessage() {
|
function postMessage() {
|
||||||
checkForFancyEffectsReason();
|
checkForFancyEffectsReason();
|
||||||
roomManager.actionsHandler.postMessage(inputField.text.trim(), attachmentPath,
|
if (ChatBoxHelper.hasAttachment) {
|
||||||
replyEventId, editEventId, userAutocompleted);
|
// send attachment but don't reset the text
|
||||||
|
roomManager.actionsHandler.postMessage("", ChatBoxHelper.attachmentPath,
|
||||||
|
ChatBoxHelper.replyEventId, ChatBoxHelper.editEventId, {});
|
||||||
|
currentRoom.markAllMessagesAsRead();
|
||||||
|
messageSent();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
roomManager.actionsHandler.postMessage(inputField.text.trim(), ChatBoxHelper.attachmentPath,
|
||||||
|
ChatBoxHelper.replyEventId, ChatBoxHelper.editEventId, userAutocompleted);
|
||||||
currentRoom.markAllMessagesAsRead();
|
currentRoom.markAllMessagesAsRead();
|
||||||
inputField.clear();
|
inputField.clear();
|
||||||
inputField.text = Qt.binding(function() {
|
inputField.text = Qt.binding(function() {
|
||||||
|
|||||||
@@ -13,19 +13,8 @@ import org.kde.neochat 1.0
|
|||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
readonly property bool isReply: replyEventId.length > 0
|
|
||||||
property var replyUser
|
|
||||||
property alias replyEventId: chatBar.replyEventId
|
|
||||||
property string replyContent: ""
|
|
||||||
|
|
||||||
readonly property bool hasAttachment: attachmentPath.length > 0
|
|
||||||
property string attachmentPath: ""
|
|
||||||
|
|
||||||
property alias inputFieldText: chatBar.inputFieldText
|
property alias inputFieldText: chatBar.inputFieldText
|
||||||
|
|
||||||
readonly property bool isEdit: editEventId.length > 0
|
|
||||||
property alias editEventId: chatBar.editEventId
|
|
||||||
|
|
||||||
signal fancyEffectsReasonFound(string fancyEffect)
|
signal fancyEffectsReasonFound(string fancyEffect)
|
||||||
|
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||||
@@ -76,6 +65,7 @@ Item {
|
|||||||
sourceComponent: EmojiPicker{
|
sourceComponent: EmojiPicker{
|
||||||
textArea: chatBar.textField
|
textArea: chatBar.textField
|
||||||
emojiModel: EmojiModel { id: emojiModel }
|
emojiModel: EmojiModel { id: emojiModel }
|
||||||
|
onChosen: addText(emoji)
|
||||||
}
|
}
|
||||||
Behavior on height {
|
Behavior on height {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
@@ -97,10 +87,8 @@ Item {
|
|||||||
|
|
||||||
ReplyPane {
|
ReplyPane {
|
||||||
id: replyPane
|
id: replyPane
|
||||||
visible: isReply || isEdit
|
visible: ChatBoxHelper.isReplying || ChatBoxHelper.isEditing
|
||||||
isEdit: root.isEdit
|
user: ChatBoxHelper.replyUser
|
||||||
user: root.replyUser
|
|
||||||
content: root.replyContent
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: visible ? implicitHeight : 0
|
height: visible ? implicitHeight : 0
|
||||||
anchors.bottom: attachmentSeparator.top
|
anchors.bottom: attachmentSeparator.top
|
||||||
@@ -123,8 +111,7 @@ Item {
|
|||||||
|
|
||||||
AttachmentPane {
|
AttachmentPane {
|
||||||
id: attachmentPane
|
id: attachmentPane
|
||||||
attachmentPath: root.attachmentPath
|
visible: ChatBoxHelper.hasAttachment
|
||||||
visible: hasAttachment
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: visible ? implicitHeight : 0
|
height: visible ? implicitHeight : 0
|
||||||
anchors.bottom: chatBarSeparator.top
|
anchors.bottom: chatBarSeparator.top
|
||||||
@@ -159,36 +146,9 @@ Item {
|
|||||||
easing.type: Easing.OutCubic
|
easing.type: Easing.OutCubic
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
onCloseAllTriggered: closeAll()
|
||||||
target: replyPane
|
onMessageSent: {
|
||||||
function onClearEditReplyTriggered() {
|
|
||||||
if (isEdit) {
|
|
||||||
clearEdit()
|
|
||||||
}
|
|
||||||
if (isReply) {
|
|
||||||
clearReply()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: attachmentPane
|
|
||||||
function onClearAttachmentTriggered() {
|
|
||||||
clearAttachment()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: chatBar
|
|
||||||
function onAttachTriggered(localPath) {
|
|
||||||
attach(localPath)
|
|
||||||
}
|
|
||||||
function onCloseAllTriggered() {
|
|
||||||
closeAll()
|
|
||||||
}
|
|
||||||
function onMessageSent() {
|
|
||||||
closeAll()
|
closeAll()
|
||||||
checkForFancyEffectsReason()
|
checkForFancyEffectsReason()
|
||||||
}
|
}
|
||||||
@@ -225,70 +185,41 @@ Item {
|
|||||||
root.inputFieldText = inputFieldText.substr(0, inputField.cursorPosition) + str + inputFieldText.substr(inputField.cursorPosition)
|
root.inputFieldText = inputFieldText.substr(0, inputField.cursorPosition) + str + inputFieldText.substr(inputField.cursorPosition)
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearText() {
|
|
||||||
// ChatBar's TextArea syncs currentRoom.cachedInput with the TextArea's text property
|
|
||||||
root.inputFieldText = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
function focusInputField() {
|
function focusInputField() {
|
||||||
chatBar.inputFieldForceActiveFocusTriggered()
|
chatBar.inputFieldForceActiveFocusTriggered()
|
||||||
}
|
}
|
||||||
|
|
||||||
function edit(editContent, editFormatedContent, editEventId) {
|
Connections {
|
||||||
// Set the input field in edit mode
|
target: ChatBoxHelper
|
||||||
root.inputFieldText = editContent;
|
|
||||||
root.editEventId = editEventId;
|
|
||||||
root.replyContent = editContent;
|
|
||||||
|
|
||||||
// clean autocompletion list
|
function onShouldClearText() {
|
||||||
chatBar.userAutocompleted = {};
|
root.inputFieldText = "";
|
||||||
|
|
||||||
// Fill autocompletion list with values extracted from message.
|
|
||||||
// We can't just iterate on every user in the list and try to
|
|
||||||
// find matching display name since some users have display name
|
|
||||||
// matching frequent words and this will marks too many words as
|
|
||||||
// mentions.
|
|
||||||
const regex = /<a href=\"https:\/\/matrix.to\/#\/(@[a-zA-Z09]*:[a-zA-Z09.]*)\">([^<]*)<\/a>/g;
|
|
||||||
|
|
||||||
let match;
|
|
||||||
while ((match = regex.exec(editFormatedContent.toString())) !== null) {
|
|
||||||
chatBar.userAutocompleted[match[2]] = match[1];
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function clearEdit() {
|
function onEditing(editContent, editFormatedContent) {
|
||||||
// Clear input when edits are cancelled.
|
// Set the input field in edit mode
|
||||||
// Cached input will be
|
root.inputFieldText = editContent;
|
||||||
clearText()
|
//root.replyContent = editContent;
|
||||||
clearReply()
|
|
||||||
root.editEventId = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function attach(localPath) {
|
// clean autocompletion list
|
||||||
root.attachmentPath = localPath
|
chatBar.userAutocompleted = {};
|
||||||
}
|
|
||||||
|
|
||||||
function clearAttachment() {
|
// Fill autocompletion list with values extracted from message.
|
||||||
root.attachmentPath = ""
|
// We can't just iterate on every user in the list and try to
|
||||||
}
|
// find matching display name since some users have display name
|
||||||
|
// matching frequent words and this will marks too many words as
|
||||||
|
// mentions.
|
||||||
|
const regex = /<a href=\"https:\/\/matrix.to\/#\/(@[a-zA-Z09]*:[a-zA-Z09.]*)\">([^<]*)<\/a>/g;
|
||||||
|
|
||||||
function clearReply() {
|
let match;
|
||||||
replyUser = null;
|
while ((match = regex.exec(editFormatedContent.toString())) !== null) {
|
||||||
root.replyContent = "";
|
chatBar.userAutocompleted[match[2]] = match[1];
|
||||||
root.replyEventId = "";
|
}
|
||||||
// Don't clear input when replies are cancelled
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeAll() {
|
function closeAll() {
|
||||||
if (hasAttachment) {
|
ChatBoxHelper.clear();
|
||||||
clearAttachment();
|
|
||||||
}
|
|
||||||
if (isEdit) {
|
|
||||||
clearEdit();
|
|
||||||
}
|
|
||||||
if (isReply) {
|
|
||||||
clearReply();
|
|
||||||
}
|
|
||||||
chatBar.emojiPaneOpened = false;
|
chatBar.emojiPaneOpened = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,15 +7,13 @@ import QtQuick 2.15
|
|||||||
import QtQuick.Layouts 1.15
|
import QtQuick.Layouts 1.15
|
||||||
import QtQuick.Controls 2.15
|
import QtQuick.Controls 2.15
|
||||||
import org.kde.kirigami 2.14 as Kirigami
|
import org.kde.kirigami 2.14 as Kirigami
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: root
|
id: root
|
||||||
property bool isEdit: false
|
readonly property bool isEdit: ChatBoxHelper.isEditing
|
||||||
property var user: null
|
property var user: null
|
||||||
property string content: ""
|
property string avatarMediaUrl: user ? "image://mxc/" + user.avatarMediaId : ""
|
||||||
property string avatarMediaUrl: user ? "image://mxc/" + replyUser.avatarMediaId : ""
|
|
||||||
|
|
||||||
signal clearEditReplyTriggered()
|
|
||||||
|
|
||||||
active: visible
|
active: visible
|
||||||
sourceComponent: Pane {
|
sourceComponent: Pane {
|
||||||
@@ -78,9 +76,9 @@ Loader {
|
|||||||
topPadding: 0
|
topPadding: 0
|
||||||
bottomPadding: 0
|
bottomPadding: 0
|
||||||
text: {
|
text: {
|
||||||
let stylesheet = "<style> a{color:"+Kirigami.Theme.linkColor+";}.user-pill{}</style>"
|
const stylesheet = "<style> a{color:"+Kirigami.Theme.linkColor+";}.user-pill{}</style>";
|
||||||
let userName = user ? "<font color=\""+ user.color +"\">" + user.displayName + "</font>" : ""
|
const content = ChatBoxHelper.isReplying ? ChatBoxHelper.replyEventContent : ChatBoxHelper.editContent;
|
||||||
return stylesheet + content
|
return stylesheet + content;
|
||||||
}
|
}
|
||||||
selectByMouse: true
|
selectByMouse: true
|
||||||
selectByKeyboard: true
|
selectByKeyboard: true
|
||||||
@@ -101,9 +99,7 @@ Loader {
|
|||||||
icon.name: "dialog-cancel"
|
icon.name: "dialog-cancel"
|
||||||
text: i18n("Cancel")
|
text: i18n("Cancel")
|
||||||
display: AbstractButton.IconOnly
|
display: AbstractButton.IconOnly
|
||||||
onClicked: {
|
onClicked: ChatBoxHelper.clearEditReply()
|
||||||
clearEditReplyTriggered()
|
|
||||||
}
|
|
||||||
ToolTip.text: text
|
ToolTip.text: text
|
||||||
ToolTip.visible: hovered
|
ToolTip.visible: hovered
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
*
|
*
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
* SPDX-License-Identifier: GPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import QtQuick 2.12
|
import QtQuick 2.15
|
||||||
import QtQuick.Controls 2.12 as QQC2
|
import QtQuick.Controls 2.12 as QQC2
|
||||||
import QtQuick.Layouts 1.12
|
import QtQuick.Layouts 1.12
|
||||||
|
|
||||||
|
|||||||
@@ -33,8 +33,6 @@ Item {
|
|||||||
signal saveFileAs()
|
signal saveFileAs()
|
||||||
signal openExternally()
|
signal openExternally()
|
||||||
signal replyClicked(string eventID)
|
signal replyClicked(string eventID)
|
||||||
signal replyToMessageClicked(var replyUser, string replyContent, string eventID)
|
|
||||||
signal edit(string message, string formattedBody, string eventId)
|
|
||||||
|
|
||||||
property alias hovered: controlContainer.hovered
|
property alias hovered: controlContainer.hovered
|
||||||
|
|
||||||
@@ -49,7 +47,7 @@ Item {
|
|||||||
updateHoverComponent();
|
updateHoverComponent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// updates the global hover component to point to this delegate, and update its position
|
// updates the global hover component to point to this delegate, and update its position
|
||||||
function updateHoverComponent() {
|
function updateHoverComponent() {
|
||||||
hoverComponent.x = column.mapToItem(page, hoverComponentX, hoverComponentY).x;
|
hoverComponent.x = column.mapToItem(page, hoverComponentX, hoverComponentY).x;
|
||||||
@@ -57,14 +55,14 @@ Item {
|
|||||||
hoverComponent.hovered = Qt.binding(() => controlContainer.hovered);
|
hoverComponent.hovered = Qt.binding(() => controlContainer.hovered);
|
||||||
hoverComponent.showEdit = author.id === Controller.activeConnection.localUserId && (model.eventType === "emote" || model.eventType === "message");
|
hoverComponent.showEdit = author.id === Controller.activeConnection.localUserId && (model.eventType === "emote" || model.eventType === "message");
|
||||||
hoverComponent.updateFunction = updateHoverComponent;
|
hoverComponent.updateFunction = updateHoverComponent;
|
||||||
|
|
||||||
hoverComponent.editClicked = () => {
|
hoverComponent.editClicked = () => {
|
||||||
if (hoverComponent.showEdit) {
|
if (hoverComponent.showEdit) {
|
||||||
edit(message, model.formattedBody, eventId);
|
ChatBoxHelper.edit(message, formattedBody, eventId)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
hoverComponent.replyClicked = () => {
|
hoverComponent.replyClicked = () => {
|
||||||
replyToMessage(author, message, eventId);
|
ChatBoxHelper.replyToMessage(eventId, message, author);
|
||||||
};
|
};
|
||||||
hoverComponent.reacted = emoji => {
|
hoverComponent.reacted = emoji => {
|
||||||
currentRoom.toggleReaction(eventId, emoji);
|
currentRoom.toggleReaction(eventId, emoji);
|
||||||
@@ -85,6 +83,7 @@ Item {
|
|||||||
parent.x = 0;
|
parent.x = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onXChanged: if (x !== 0) {
|
onXChanged: if (x !== 0) {
|
||||||
applicationWindow().pageStack.interactive = false;
|
applicationWindow().pageStack.interactive = false;
|
||||||
} else {
|
} else {
|
||||||
@@ -146,7 +145,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// bubble
|
// bubble
|
||||||
QQC2.Control {
|
QQC2.ItemDelegate {
|
||||||
id: controlContainer
|
id: controlContainer
|
||||||
Layout.maximumWidth: mainColumn.width - Kirigami.Units.gridUnit * 2 - Kirigami.Units.largeSpacing * 2
|
Layout.maximumWidth: mainColumn.width - Kirigami.Units.gridUnit * 2 - Kirigami.Units.largeSpacing * 2
|
||||||
implicitHeight: contentItem.implicitHeight
|
implicitHeight: contentItem.implicitHeight
|
||||||
|
|||||||
@@ -255,7 +255,7 @@ Kirigami.ScrollablePage {
|
|||||||
fileDialog.chosen.connect(function(path) {
|
fileDialog.chosen.connect(function(path) {
|
||||||
if (!path) return
|
if (!path) return
|
||||||
|
|
||||||
chatBox.attach(path)
|
ChatBoxHelper.attachmentPath = path;
|
||||||
})
|
})
|
||||||
|
|
||||||
fileDialog.open()
|
fileDialog.open()
|
||||||
@@ -273,10 +273,12 @@ Kirigami.ScrollablePage {
|
|||||||
icon.name: 'insert-image'
|
icon.name: 'insert-image'
|
||||||
text: i18n("Clipboard image")
|
text: i18n("Clipboard image")
|
||||||
onClicked: {
|
onClicked: {
|
||||||
var localPath = Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/screenshots/" + (new Date()).getTime() + ".png"
|
const localPath = Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/screenshots/" + (new Date()).getTime() + ".png"
|
||||||
if (!Clipboard.saveImage(localPath)) return
|
if (!Clipboard.saveImage(localPath)) {
|
||||||
chatBox.attach(localPath)
|
return;
|
||||||
attachDialog.close()
|
}
|
||||||
|
ChatBoxHelper.attachmentPath = localPath;
|
||||||
|
attachDialog.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -356,8 +358,6 @@ Kirigami.ScrollablePage {
|
|||||||
isLoaded: timelineDelegateChooser.delegateLoaded
|
isLoaded: timelineDelegateChooser.delegateLoaded
|
||||||
isEmote: true
|
isEmote: true
|
||||||
onReplyClicked: goToEvent(eventID)
|
onReplyClicked: goToEvent(eventID)
|
||||||
onReplyToMessageClicked: replyToMessage(replyUser, replyContent, eventId);
|
|
||||||
onEdit: chatBox.edit(message, formattedBody, eventId)
|
|
||||||
|
|
||||||
hoverComponent: hoverActions
|
hoverComponent: hoverActions
|
||||||
|
|
||||||
@@ -386,8 +386,6 @@ Kirigami.ScrollablePage {
|
|||||||
|
|
||||||
isLoaded: timelineDelegateChooser.delegateLoaded
|
isLoaded: timelineDelegateChooser.delegateLoaded
|
||||||
onReplyClicked: goToEvent(eventID)
|
onReplyClicked: goToEvent(eventID)
|
||||||
onReplyToMessageClicked: replyToMessage(replyUser, replyContent, eventId);
|
|
||||||
onEdit: chatBox.edit(message, formattedBody, eventId)
|
|
||||||
|
|
||||||
hoverComponent: hoverActions
|
hoverComponent: hoverActions
|
||||||
|
|
||||||
@@ -413,8 +411,6 @@ Kirigami.ScrollablePage {
|
|||||||
width: messageListView.width - Kirigami.Units.largeSpacing
|
width: messageListView.width - Kirigami.Units.largeSpacing
|
||||||
isLoaded: timelineDelegateChooser.delegateLoaded
|
isLoaded: timelineDelegateChooser.delegateLoaded
|
||||||
onReplyClicked: goToEvent(eventID)
|
onReplyClicked: goToEvent(eventID)
|
||||||
onReplyToMessageClicked: replyToMessage(replyUser, replyContent, eventId);
|
|
||||||
onEdit: chatBox.edit(message, formattedBody, eventId)
|
|
||||||
|
|
||||||
hoverComponent: hoverActions
|
hoverComponent: hoverActions
|
||||||
innerObject: TextDelegate {
|
innerObject: TextDelegate {
|
||||||
@@ -432,7 +428,6 @@ Kirigami.ScrollablePage {
|
|||||||
|
|
||||||
isLoaded: timelineDelegateChooser.delegateLoaded
|
isLoaded: timelineDelegateChooser.delegateLoaded
|
||||||
onReplyClicked: goToEvent(eventID)
|
onReplyClicked: goToEvent(eventID)
|
||||||
onReplyToMessageClicked: replyToMessage(replyUser, replyContent, eventId);
|
|
||||||
|
|
||||||
hoverComponent: hoverActions
|
hoverComponent: hoverActions
|
||||||
|
|
||||||
@@ -453,7 +448,6 @@ Kirigami.ScrollablePage {
|
|||||||
|
|
||||||
isLoaded: timelineDelegateChooser.delegateLoaded
|
isLoaded: timelineDelegateChooser.delegateLoaded
|
||||||
onReplyClicked: goToEvent(eventID)
|
onReplyClicked: goToEvent(eventID)
|
||||||
onReplyToMessageClicked: replyToMessage(replyUser, replyContent, eventId);
|
|
||||||
|
|
||||||
hoverComponent: hoverActions
|
hoverComponent: hoverActions
|
||||||
cardBackground: false
|
cardBackground: false
|
||||||
@@ -476,7 +470,6 @@ Kirigami.ScrollablePage {
|
|||||||
|
|
||||||
isLoaded: timelineDelegateChooser.delegateLoaded
|
isLoaded: timelineDelegateChooser.delegateLoaded
|
||||||
onReplyClicked: goToEvent(eventID)
|
onReplyClicked: goToEvent(eventID)
|
||||||
onReplyToMessageClicked: replyToMessage(replyUser, replyContent, eventId);
|
|
||||||
|
|
||||||
innerObject: AudioDelegate {
|
innerObject: AudioDelegate {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@@ -501,7 +494,6 @@ Kirigami.ScrollablePage {
|
|||||||
|
|
||||||
isLoaded: timelineDelegateChooser.delegateLoaded
|
isLoaded: timelineDelegateChooser.delegateLoaded
|
||||||
onReplyClicked: goToEvent(eventID)
|
onReplyClicked: goToEvent(eventID)
|
||||||
onReplyToMessageClicked: replyToMessage(replyUser, replyContent, eventId);
|
|
||||||
|
|
||||||
innerObject: VideoDelegate {
|
innerObject: VideoDelegate {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@@ -532,7 +524,6 @@ Kirigami.ScrollablePage {
|
|||||||
|
|
||||||
isLoaded: timelineDelegateChooser.delegateLoaded
|
isLoaded: timelineDelegateChooser.delegateLoaded
|
||||||
onReplyClicked: goToEvent(eventID)
|
onReplyClicked: goToEvent(eventID)
|
||||||
onReplyToMessageClicked: replyToMessage(replyUser, replyContent, eventId);
|
|
||||||
|
|
||||||
innerObject: FileDelegate {
|
innerObject: FileDelegate {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@@ -613,7 +604,7 @@ Kirigami.ScrollablePage {
|
|||||||
DropArea {
|
DropArea {
|
||||||
id: dropAreaFile
|
id: dropAreaFile
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
onDropped: chatBox.attach(drop.urls[0])
|
onDropped: ChatBoxHelper.attachmentPath = drop.urls[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
QQC2.Pane {
|
QQC2.Pane {
|
||||||
@@ -767,7 +758,7 @@ Kirigami.ScrollablePage {
|
|||||||
}).open();
|
}).open();
|
||||||
});
|
});
|
||||||
contextMenu.reply.connect(function(replyUser, replyContent) {
|
contextMenu.reply.connect(function(replyUser, replyContent) {
|
||||||
replyToMessage(replyUser, replyContent, eventId);
|
ChatBoxHelper.replyToMessage(eventId, replyContent, replyUser);
|
||||||
})
|
})
|
||||||
contextMenu.remove.connect(function() {
|
contextMenu.remove.connect(function() {
|
||||||
currentRoom.redactEvent(eventId);
|
currentRoom.redactEvent(eventId);
|
||||||
@@ -788,19 +779,11 @@ Kirigami.ScrollablePage {
|
|||||||
}).open();
|
}).open();
|
||||||
});
|
});
|
||||||
contextMenu.reply.connect(function(replyUser, replyContent) {
|
contextMenu.reply.connect(function(replyUser, replyContent) {
|
||||||
replyToMessage(replyUser, replyContent, eventId);
|
ChatBoxHelper.replyToMessage(eventId, replyContent, replyUser);
|
||||||
})
|
})
|
||||||
contextMenu.remove.connect(function() {
|
contextMenu.remove.connect(function() {
|
||||||
currentRoom.redactEvent(eventId);
|
currentRoom.redactEvent(eventId);
|
||||||
})
|
})
|
||||||
contextMenu.open();
|
contextMenu.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
function replyToMessage(replyUser, replyContent, eventId) {
|
|
||||||
chatBox.editEventId = "";
|
|
||||||
chatBox.replyUser = replyUser;
|
|
||||||
chatBox.replyEventId = eventId;
|
|
||||||
chatBox.replyContent = replyContent;
|
|
||||||
chatBox.focusInputField();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ add_executable(neochat
|
|||||||
filetypesingleton.cpp
|
filetypesingleton.cpp
|
||||||
login.cpp
|
login.cpp
|
||||||
stickerevent.cpp
|
stickerevent.cpp
|
||||||
|
chatboxhelper.cpp
|
||||||
../res.qrc
|
../res.qrc
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
162
src/chatboxhelper.cpp
Normal file
162
src/chatboxhelper.cpp
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2020 Carl Schwan <carlschwan@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPl-3.0-or-later
|
||||||
|
|
||||||
|
#include "chatboxhelper.h"
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
ChatBoxHelper::ChatBoxHelper(QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChatBoxHelper::isEditing() const
|
||||||
|
{
|
||||||
|
return !m_editEventId.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ChatBoxHelper::editEventId() const
|
||||||
|
{
|
||||||
|
return m_editEventId;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatBoxHelper::setEditEventId(const QString& editEventId)
|
||||||
|
{
|
||||||
|
if (m_editEventId == editEventId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_editEventId = editEventId;
|
||||||
|
Q_EMIT editEventIdChanged(m_editEventId);
|
||||||
|
Q_EMIT isEditingChanged(!m_editEventId.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ChatBoxHelper::editContent() const
|
||||||
|
{
|
||||||
|
return m_editContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatBoxHelper::setEditContent(const QString& editContent)
|
||||||
|
{
|
||||||
|
if (m_editContent == editContent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_editContent = editContent;
|
||||||
|
Q_EMIT editContentChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ChatBoxHelper::replyEventId() const
|
||||||
|
{
|
||||||
|
return m_replyEventId;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatBoxHelper::setReplyEventId(const QString& replyEventId)
|
||||||
|
{
|
||||||
|
if (m_replyEventId == replyEventId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_replyEventId = replyEventId;
|
||||||
|
Q_EMIT replyEventIdChanged(m_replyEventId);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ChatBoxHelper::replyEventContent() const
|
||||||
|
{
|
||||||
|
return m_replyEventContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatBoxHelper::setReplyEventContent(const QString& replyEventContent)
|
||||||
|
{
|
||||||
|
if (m_replyEventContent == replyEventContent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_replyEventContent = replyEventContent;
|
||||||
|
Q_EMIT replyEventContentChanged(m_replyEventContent);
|
||||||
|
Q_EMIT isReplyingChanged(!m_replyEventContent.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChatBoxHelper::isReplying() const
|
||||||
|
{
|
||||||
|
return !m_replyEventId.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ChatBoxHelper::attachmentPath() const
|
||||||
|
{
|
||||||
|
return m_attachmentPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatBoxHelper::setAttachmentPath(const QString& attachmentPath)
|
||||||
|
{
|
||||||
|
if (m_attachmentPath == attachmentPath) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_attachmentPath = attachmentPath;
|
||||||
|
Q_EMIT attachmentPathChanged(m_attachmentPath);
|
||||||
|
Q_EMIT hasAttachmentChanged(!m_attachmentPath.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChatBoxHelper::hasAttachment() const
|
||||||
|
{
|
||||||
|
return !m_attachmentPath.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatBoxHelper::replyToMessage(const QString &replyEventId, const QString &replyEvent, const QVariant &replyUser)
|
||||||
|
{
|
||||||
|
setEditEventId(QString());
|
||||||
|
setEditContent(QString());
|
||||||
|
setReplyEventId(replyEventId);
|
||||||
|
setReplyEventContent(replyEvent);
|
||||||
|
setReplyUser(replyUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant ChatBoxHelper::replyUser() const
|
||||||
|
{
|
||||||
|
return m_replyUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatBoxHelper::setReplyUser(const QVariant &replyUser)
|
||||||
|
{
|
||||||
|
if (m_replyUser == replyUser) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_replyUser = replyUser;
|
||||||
|
Q_EMIT replyUserChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatBoxHelper::clear()
|
||||||
|
{
|
||||||
|
setEditEventId(QString());
|
||||||
|
setEditContent(QString());
|
||||||
|
setReplyEventId(QString());
|
||||||
|
setReplyEventContent(QString());
|
||||||
|
setAttachmentPath(QString());
|
||||||
|
setReplyUser(QVariant());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatBoxHelper::edit(const QString& message, const QString& formattedBody, const QString& eventId)
|
||||||
|
{
|
||||||
|
setEditEventId(eventId);
|
||||||
|
setEditContent(message);
|
||||||
|
Q_EMIT editing(message, formattedBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatBoxHelper::clearEditReply()
|
||||||
|
{
|
||||||
|
setEditEventId(QString());
|
||||||
|
setEditContent(QString());
|
||||||
|
setReplyEventId(QString());
|
||||||
|
setReplyEventContent(QString());
|
||||||
|
setReplyUser(QVariant());
|
||||||
|
Q_EMIT shouldClearText();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatBoxHelper::clearAttachment()
|
||||||
|
{
|
||||||
|
setAttachmentPath(QString());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
75
src/chatboxhelper.h
Normal file
75
src/chatboxhelper.h
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2020 Carl Schwan <carlschwan@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPl-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QVariant>
|
||||||
|
|
||||||
|
/// Helper singleton for keeping the chatbar state in sync in the application.
|
||||||
|
class ChatBoxHelper : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
/// True, iff the user is currently editing one of their previous message.
|
||||||
|
Q_PROPERTY(bool isEditing READ isEditing NOTIFY isEditingChanged)
|
||||||
|
Q_PROPERTY(QString editEventId READ editEventId WRITE setEditEventId NOTIFY editEventIdChanged)
|
||||||
|
Q_PROPERTY(QString editContent READ editContent WRITE setEditContent NOTIFY editContentChanged)
|
||||||
|
|
||||||
|
Q_PROPERTY(bool isReplying READ isReplying NOTIFY isReplyingChanged)
|
||||||
|
Q_PROPERTY(QString replyEventId READ replyEventId WRITE setReplyEventId NOTIFY replyEventIdChanged)
|
||||||
|
Q_PROPERTY(QString replyEventContent READ replyEventContent WRITE setReplyEventContent NOTIFY replyEventContentChanged)
|
||||||
|
Q_PROPERTY(QVariant replyUser READ replyUser WRITE setReplyUser NOTIFY replyUserChanged)
|
||||||
|
|
||||||
|
Q_PROPERTY(QString attachmentPath READ attachmentPath WRITE setAttachmentPath NOTIFY attachmentPathChanged)
|
||||||
|
Q_PROPERTY(bool hasAttachment READ hasAttachment NOTIFY hasAttachmentChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
ChatBoxHelper(QObject *parent = nullptr);
|
||||||
|
~ChatBoxHelper() = default;
|
||||||
|
|
||||||
|
bool isEditing() const;
|
||||||
|
QString editEventId() const;
|
||||||
|
QString editContent() const;
|
||||||
|
|
||||||
|
QString replyEventId() const;
|
||||||
|
QString replyEventContent() const;
|
||||||
|
QVariant replyUser() const;
|
||||||
|
bool isReplying() const;
|
||||||
|
|
||||||
|
QString attachmentPath() const;
|
||||||
|
bool hasAttachment() const;
|
||||||
|
|
||||||
|
void setEditEventId(const QString& editEventId);
|
||||||
|
void setEditContent(const QString& editContent);
|
||||||
|
void setReplyEventId(const QString& replyEventId);
|
||||||
|
void setReplyEventContent(const QString& replyEventContent);
|
||||||
|
void setAttachmentPath(const QString& attachmentPath);
|
||||||
|
void setReplyUser(const QVariant &replyUser);
|
||||||
|
|
||||||
|
Q_INVOKABLE void replyToMessage(const QString &replyEventid, const QString &replyEvent, const QVariant &replyUser);
|
||||||
|
Q_INVOKABLE void edit(const QString &message, const QString &formattedBody, const QString &eventId);
|
||||||
|
Q_INVOKABLE void clear();
|
||||||
|
Q_INVOKABLE void clearEditReply();
|
||||||
|
Q_INVOKABLE void clearAttachment();
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void isEditingChanged(bool isEditing);
|
||||||
|
void editEventIdChanged(const QString& editEventId);
|
||||||
|
void editContentChanged();
|
||||||
|
void replyEventIdChanged(const QString& replyEventId);
|
||||||
|
void replyEventContentChanged(const QString& replyEventContent);
|
||||||
|
void replyUserChanged();
|
||||||
|
void isReplyingChanged(bool isReplying);
|
||||||
|
void attachmentPathChanged(const QString& attachmentPath);
|
||||||
|
void hasAttachmentChanged(bool hasAttachment);
|
||||||
|
void editing(const QString &message, const QString &formattedBody);
|
||||||
|
void shouldClearText();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_editEventId;
|
||||||
|
QString m_editContent;
|
||||||
|
QString m_replyEventId;
|
||||||
|
QString m_replyEventContent;
|
||||||
|
QVariant m_replyUser;
|
||||||
|
QString m_attachmentPath;
|
||||||
|
};
|
||||||
@@ -47,6 +47,7 @@
|
|||||||
#include "userdirectorylistmodel.h"
|
#include "userdirectorylistmodel.h"
|
||||||
#include "userlistmodel.h"
|
#include "userlistmodel.h"
|
||||||
#include "actionshandler.h"
|
#include "actionshandler.h"
|
||||||
|
#include "chatboxhelper.h"
|
||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
|
|
||||||
@@ -108,12 +109,14 @@ int main(int argc, char *argv[])
|
|||||||
FileTypeSingleton fileTypeSingleton;
|
FileTypeSingleton fileTypeSingleton;
|
||||||
|
|
||||||
Login *login = new Login();
|
Login *login = new Login();
|
||||||
|
ChatBoxHelper chatBoxHelper;
|
||||||
|
|
||||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "Controller", &Controller::instance());
|
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "Controller", &Controller::instance());
|
||||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "Clipboard", &clipboard);
|
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "Clipboard", &clipboard);
|
||||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "Config", config);
|
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "Config", config);
|
||||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "FileType", &fileTypeSingleton);
|
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "FileType", &fileTypeSingleton);
|
||||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "LoginHelper", login);
|
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "LoginHelper", login);
|
||||||
|
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "ChatBoxHelper", &chatBoxHelper);
|
||||||
qmlRegisterType<AccountListModel>("org.kde.neochat", 1, 0, "AccountListModel");
|
qmlRegisterType<AccountListModel>("org.kde.neochat", 1, 0, "AccountListModel");
|
||||||
qmlRegisterType<ActionsHandler>("org.kde.neochat", 1, 0, "ActionsHandler");
|
qmlRegisterType<ActionsHandler>("org.kde.neochat", 1, 0, "ActionsHandler");
|
||||||
qmlRegisterType<ChatDocumentHandler>("org.kde.neochat", 1, 0, "ChatDocumentHandler");
|
qmlRegisterType<ChatDocumentHandler>("org.kde.neochat", 1, 0, "ChatDocumentHandler");
|
||||||
|
|||||||
Reference in New Issue
Block a user