Add ChatBarMessageContentModel and hook up

This commit is contained in:
James Graham
2025-08-04 18:11:21 +01:00
parent 9cbe9f7280
commit c128450cf5
20 changed files with 1825 additions and 648 deletions

View File

@@ -35,7 +35,7 @@ QQC2.Control {
required property NeoChatConnection connection
onActiveFocusChanged: textField.forceActiveFocus()
onActiveFocusChanged: chatContentView.itemAt(contentModel.index(contentModel.focusRow, 0)).forceActiveFocus()
onCurrentRoomChanged: {
_private.chatBarCache = currentRoom.mainCache
@@ -75,6 +75,9 @@ QQC2.Control {
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
Message.room: root.currentRoom
Message.contentModel: contentModel
background: Rectangle {
color: Kirigami.Theme.backgroundColor
Kirigami.Separator {
@@ -84,233 +87,39 @@ QQC2.Control {
}
}
height: Math.max(Math.min(chatScrollView.contentHeight + bottomPadding + topPadding, Kirigami.Units.gridUnit * 10), Kirigami.Units.gridUnit * 5)
leftPadding: rightPadding
rightPadding: (root.width - chatBarSizeHelper.availableWidth) / 2
topPadding: 0
bottomPadding: 0
topPadding: Kirigami.Units.smallSpacing
bottomPadding: Kirigami.Units.smallSpacing
contentItem: ColumnLayout {
spacing: 0
Item {
// Required to adjust for the top separator
Layout.preferredHeight: 1
Layout.fillWidth: true
}
Loader {
id: replyLoader
Layout.fillWidth: true
Layout.margins: Kirigami.Units.largeSpacing
Layout.preferredHeight: active ? (item as Item).implicitHeight : 0
active: visible
visible: root.currentRoom.mainCache.replyId.length > 0
sourceComponent: replyPane
}
RowLayout {
visible: replyLoader.visible && !root.currentRoom.mainCache.relationAuthorIsPresent
contentItem: QQC2.ScrollView {
id: chatScrollView
ColumnLayout {
spacing: Kirigami.Units.smallSpacing
Kirigami.Icon {
source: "help-hint-symbolic"
color: Kirigami.Theme.disabledTextColor
Layout.preferredWidth: Kirigami.Units.iconSizes.small
Layout.preferredHeight: Kirigami.Units.iconSizes.small
}
QQC2.Label {
text: i18nc("@info", "The user you're replying to has left the room, and can't be notified.")
color: Kirigami.Theme.disabledTextColor
}
}
Loader {
id: attachLoader
Layout.fillWidth: true
Layout.margins: Kirigami.Units.largeSpacing
Layout.preferredHeight: active ? (item as Item).implicitHeight : 0
active: visible
visible: root.currentRoom.mainCache.attachmentPath.length > 0
sourceComponent: attachmentPane
}
RowLayout {
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
Layout.minimumHeight: Kirigami.Units.gridUnit * 3
// HACK: This is to stop the ScrollBar flickering on and off as the height is increased
QQC2.ScrollBar.vertical.policy: chatBarHeightAnimation.running && implicitHeight <= height ? QQC2.ScrollBar.AlwaysOff : QQC2.ScrollBar.AsNeeded
Behavior on implicitHeight {
NumberAnimation {
id: chatBarHeightAnimation
duration: Kirigami.Units.shortDuration
easing.type: Easing.InOutCubic
}
Repeater {
id: chatContentView
model: ChatBarMessageContentModel {
id: contentModel
type: ChatBarType.Room
room: root.currentRoom
}
QQC2.TextArea {
id: textField
placeholderText: root.currentRoom.usesEncryption ? i18nc("@placeholder", "Send an encrypted message…") : root.currentRoom.mainCache.attachmentPath.length > 0 ? i18nc("@placeholder", "Set an attachment caption…") : i18nc("@placeholder", "Send a message…")
verticalAlignment: TextEdit.AlignVCenter
wrapMode: TextEdit.Wrap
persistentSelection: true
Accessible.description: placeholderText
Kirigami.SpellCheck.enabled: false
Timer {
id: repeatTimer
interval: 5000
}
onTextChanged: {
if (!repeatTimer.running && NeoChatConfig.typingNotifications) {
var textExists = text.length > 0;
root.currentRoom.sendTypingNotification(textExists);
textExists ? repeatTimer.start() : repeatTimer.stop();
}
}
onSelectedTextChanged: {
if (selectedText.length > 0) {
quickFormatBar.selectionStart = selectionStart;
quickFormatBar.selectionEnd = selectionEnd;
quickFormatBar.open();
} else if (quickFormatBar.visible) {
quickFormatBar.close();
}
}
QuickFormatBar {
id: quickFormatBar
x: textField.cursorRectangle.x
y: textField.cursorRectangle.y - height
onFormattingSelected: (format, selectionStart, selectionEnd) => _private.formatText(format, selectionStart, selectionEnd)
}
Keys.onEnterPressed: event => {
const controlIsPressed = event.modifiers & Qt.ControlModifier;
if (completionMenu.visible) {
completionMenu.complete();
} else if (event.modifiers & Qt.ShiftModifier || Kirigami.Settings.isMobile || NeoChatConfig.sendMessageWith === 1 && !controlIsPressed || NeoChatConfig.sendMessageWith === 0 && controlIsPressed) {
textField.insert(cursorPosition, "\n");
} else if (NeoChatConfig.sendMessageWith === 0 && !controlIsPressed || NeoChatConfig.sendMessageWith === 1 && controlIsPressed) {
_private.postMessage();
}
}
Keys.onReturnPressed: event => {
const controlIsPressed = event.modifiers & Qt.ControlModifier;
if (completionMenu.visible) {
completionMenu.complete();
} else if (event.modifiers & Qt.ShiftModifier || Kirigami.Settings.isMobile || NeoChatConfig.sendMessageWith === 1 && !controlIsPressed || NeoChatConfig.sendMessageWith === 0 && controlIsPressed) {
textField.insert(cursorPosition, "\n");
} else if (NeoChatConfig.sendMessageWith === 0 && !controlIsPressed || NeoChatConfig.sendMessageWith === 1 && controlIsPressed) {
_private.postMessage();
}
}
Keys.onTabPressed: {
if (completionMenu.visible) {
completionMenu.complete();
} else {
contextDrawer.handle.children[0].forceActiveFocus()
}
}
Keys.onPressed: event => {
if (event.key === Qt.Key_V && event.modifiers & Qt.ControlModifier) {
event.accepted = _private.pasteImage();
} else if (event.key === Qt.Key_Up && event.modifiers & Qt.ControlModifier) {
root.currentRoom.replyLastMessage();
} else if (event.key === Qt.Key_Up && textField.text.length === 0) {
root.currentRoom.editLastMessage();
} else if (event.key === Qt.Key_Up && completionMenu.visible) {
completionMenu.decrementIndex();
} else if (event.key === Qt.Key_Down && completionMenu.visible) {
completionMenu.incrementIndex();
} else if (event.key === Qt.Key_Backspace || event.key === Qt.Key_Delete) {
if (textField.text == selectedText || textField.text.length <= 1) {
root.currentRoom.sendTypingNotification(false);
repeatTimer.stop();
}
if (quickFormatBar.visible && selectedText.length > 0) {
quickFormatBar.close();
}
} else if (event.key === Qt.Key_Escape && completionMenu.visible) {
completionMenu.close();
}
}
Keys.onShortcutOverride: event => {
if ((_private.chatBarCache.isReplying || _private.chatBarCache.attachmentPath.length > 0) && event.key === Qt.Key_Escape) {
_private.chatBarCache.attachmentPath = "";
_private.chatBarCache.replyId = "";
_private.chatBarCache.threadId = "";
event.accepted = true;
}
}
background: MouseArea {
acceptedButtons: Qt.NoButton
cursorShape: Qt.IBeamCursor
z: 1
}
}
delegate: MessageComponentChooser {}
}
RowLayout {
id: actionsRow
spacing: 0
Layout.alignment: Qt.AlignBottom
Layout.bottomMargin: Kirigami.Units.smallSpacing * 4
RichEditBar {
id: richEditBar
maxAvailableWidth: chatBarSizeHelper.availableWidth - Kirigami.Units.largeSpacing * 2
Repeater {
model: root.actions
delegate: QQC2.ToolButton {
id: actionDelegate
required property BusyAction modelData
icon.name: modelData.isBusy ? "" : (modelData.icon.name.length > 0 ? modelData.icon.name : modelData.icon.source)
onClicked: if (!pieProgress.visible) {
modelData.trigger()
}
room: root.currentRoom
contentModel: chatContentView.model
padding: Kirigami.Units.smallSpacing
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.text: modelData.tooltip
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
PieProgressBar {
id: pieProgress
anchors.fill: parent
visible: actionDelegate.modelData.isBusy
progress: root.currentRoom.fileUploadingProgress
}
}
}
onClicked: contentModel.refocusCurrentComponent()
}
}
RichEditBar {
id: richEditBar
Layout.maximumWidth: chatBarSizeHelper.availableWidth - Kirigami.Units.largeSpacing * 2
Layout.margins: Kirigami.Units.largeSpacing
Layout.alignment:Qt.AlignHCenter
maxAvailableWidth: chatBarSizeHelper.availableWidth - Kirigami.Units.largeSpacing * 2
room: root.currentRoom
documentHandler: documentHandler
onRequestPostMessage: _private.postMessage()
}
}
LibNeoChat.DelegateSizeHelper {
id: chatBarSizeHelper
parentItem: root
@@ -321,49 +130,6 @@ QQC2.Control {
maxWidth: NeoChatConfig.compactLayout ? root.width - Kirigami.Units.largeSpacing * 2 : Kirigami.Units.gridUnit * 60
}
Component {
id: replyPane
Item {
implicitHeight: replyComponent.implicitHeight
ReplyComponent {
id: replyComponent
replyContentModel: ContentProvider.contentModelForEvent(root.currentRoom, _private.chatBarCache.replyId, true)
Message.maxContentWidth: (replyLoader.item as Item).width
// When the user replies to a message and the preview is loaded, make sure the text field is focused again
Component.onCompleted: textField.forceActiveFocus(Qt.OtherFocusReason)
}
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"
onClicked: {
_private.chatBarCache.replyId = "";
_private.chatBarCache.attachmentPath = "";
}
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
}
}
Component {
id: attachmentPane
AttachmentPane {
attachmentPath: _private.chatBarCache.attachmentPath
onAttachmentCancelled: {
_private.chatBarCache.attachmentPath = "";
root.forceActiveFocus();
}
}
}
QtObject {
id: _private
property ChatBarCache chatBarCache
@@ -371,53 +137,6 @@ QQC2.Control {
richEditBar.chatBarCache = chatBarCache
}
function postMessage() {
_private.chatBarCache.postMessage();
repeatTimer.stop();
textField.clear();
}
function formatText(format, selectionStart, selectionEnd) {
let index = textField.cursorPosition;
/*
* There cannot be white space at the beginning or end of the string for the
* formatting to work so move the sectionStart and sectionEnd markers past any whitespace.
*/
let innerText = textField.text.substr(selectionStart, selectionEnd - selectionStart);
if (innerText.charAt(innerText.length - 1) === " ") {
let trimmedRightString = innerText.replace(/\s*$/, "");
let trimDifference = innerText.length - trimmedRightString.length;
selectionEnd -= trimDifference;
}
if (innerText.charAt(0) === " ") {
let trimmedLeftString = innerText.replace(/^\s*/, "");
let trimDifference = innerText.length - trimmedLeftString.length;
selectionStart = selectionStart + trimDifference;
}
let startText = textField.text.substr(0, selectionStart);
// Needs updating with the new selectionStart and selectionEnd with white space trimmed.
innerText = textField.text.substr(selectionStart, selectionEnd - selectionStart);
let endText = textField.text.substr(selectionEnd);
textField.text = "";
textField.text = startText + format.start + innerText + format.end + format.extra + endText;
/*
* Put the cursor where it was when the popup was opened accounting for the
* new markup.
*
* The exception is for a hyperlink where it is placed ready to start typing
* the url.
*/
if (format.extra !== "") {
textField.cursorPosition = selectionEnd + format.start.length + format.end.length;
} else if (index == selectionStart) {
textField.cursorPosition = index;
} else {
textField.cursorPosition = index + format.start.length + format.end.length;
}
}
function pasteImage() {
let localPath = Clipboard.saveImage();
if (localPath.length === 0) {
@@ -428,16 +147,9 @@ QQC2.Control {
}
}
ChatDocumentHandler {
id: documentHandler
type: ChatBarType.Room
textItem: textField
room: root.currentRoom
}
CompletionMenu {
id: completionMenu
chatDocumentHandler: documentHandler
chatDocumentHandler: contentModel.focusedDocumentHandler
connection: root.connection
x: 1

View File

@@ -18,6 +18,10 @@ QQC2.Popup {
required property NeoChatConnection connection
required property var chatDocumentHandler
onChatDocumentHandlerChanged: if (chatDocumentHandler) {
chatDocumentHandler.completionModel.roomListModel = RoomManager.roomListModel;
chatDocumentHandler.completionModel.userListModel = RoomManager.userListModel;
}
visible: completions.count > 0
@@ -25,11 +29,6 @@ QQC2.Popup {
root.open();
}
Component.onCompleted: {
chatDocumentHandler.completionModel.roomListModel = RoomManager.roomListModel;
chatDocumentHandler.completionModel.userListModel = RoomManager.userListModel;
}
function incrementIndex() {
completions.incrementCurrentIndex();
}

View File

@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2025 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
@@ -8,6 +9,7 @@ import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.neochat.libneochat as LibNeoChat
import org.kde.neochat.messagecontent as MessageContent
QQC2.ToolBar {
id: root
@@ -19,13 +21,23 @@ QQC2.ToolBar {
property LibNeoChat.ChatBarCache chatBarCache
required property LibNeoChat.ChatDocumentHandler documentHandler
required property MessageContent.ChatBarMessageContentModel contentModel
readonly property LibNeoChat.ChatDocumentHandler focusedDocumentHandler: contentModel.focusedDocumentHandler
Connections {
target: contentModel
function onFocusRowChanged() {
console.warn("focus changed", contentModel.focusRow, contentModel.focusType)
}
}
required property real maxAvailableWidth
readonly property real uncompressedImplicitWidth: textFormatRow.implicitWidth +
listRow.implicitWidth +
styleButton.implicitWidth +
codeButton.implicitWidth +
emojiButton.implicitWidth +
linkButton.implicitWidth +
sendRow.implicitWidth +
@@ -36,6 +48,7 @@ QQC2.ToolBar {
readonly property real listCompressedImplicitWidth: textFormatRow.implicitWidth +
compressedListButton.implicitWidth +
styleButton.implicitWidth +
codeButton.implicitWidth +
emojiButton.implicitWidth +
linkButton.implicitWidth +
sendRow.implicitWidth +
@@ -46,6 +59,7 @@ QQC2.ToolBar {
readonly property real textFormatCompressedImplicitWidth: compressedTextFormatButton.implicitWidth +
compressedListButton.implicitWidth +
styleButton.implicitWidth +
codeButton.implicitWidth +
emojiButton.implicitWidth +
linkButton.implicitWidth +
sendRow.implicitWidth +
@@ -53,7 +67,7 @@ QQC2.ToolBar {
buttonRow.spacing * 9 +
3
signal requestPostMessage
signal clicked
RowLayout {
id: buttonRow
@@ -67,11 +81,15 @@ QQC2.ToolBar {
onActivated: boldButton.clicked()
}
icon.name: "format-text-bold"
enabled: root.contentModel.focusType !== LibNeoChat.MessageComponentType.Code
text: i18nc("@action:button", "Bold")
display: QQC2.AbstractButton.IconOnly
checkable: true
checked: root.documentHandler.bold
onClicked: root.documentHandler.bold = checked;
checked: root.focusedDocumentHandler.bold
onClicked: {
root.focusedDocumentHandler.bold = checked;
root.clicked()
}
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
@@ -84,11 +102,15 @@ QQC2.ToolBar {
onActivated: italicButton.clicked()
}
icon.name: "format-text-italic"
enabled: root.contentModel.focusType !== LibNeoChat.MessageComponentType.Code
text: i18nc("@action:button", "Italic")
display: QQC2.AbstractButton.IconOnly
checkable: true
checked: root.documentHandler.italic
onClicked: root.documentHandler.italic = checked;
checked: root.focusedDocumentHandler.italic
onClicked: {
root.focusedDocumentHandler.italic = checked;
root.clicked()
}
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
@@ -101,11 +123,15 @@ QQC2.ToolBar {
onActivated: underlineButton.clicked()
}
icon.name: "format-text-underline"
enabled: root.contentModel.focusType !== LibNeoChat.MessageComponentType.Code
text: i18nc("@action:button", "Underline")
display: QQC2.AbstractButton.IconOnly
checkable: true
checked: root.documentHandler.underline
onClicked: root.documentHandler.underline = checked;
checked: root.focusedDocumentHandler.underline
onClicked: {
root.focusedDocumentHandler.underline = checked;
root.clicked();
}
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
@@ -113,11 +139,15 @@ QQC2.ToolBar {
}
QQC2.ToolButton {
icon.name: "format-text-strikethrough"
enabled: root.contentModel.focusType !== LibNeoChat.MessageComponentType.Code
text: i18nc("@action:button", "Strikethrough")
display: QQC2.AbstractButton.IconOnly
checkable: true
checked: root.documentHandler.strikethrough
onClicked: root.documentHandler.strikethrough = checked;
checked: root.focusedDocumentHandler.strikethrough
onClicked: {
root.focusedDocumentHandler.strikethrough = checked;
root.clicked()
}
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
@@ -128,6 +158,7 @@ QQC2.ToolBar {
id: compressedTextFormatButton
visible: root.maxAvailableWidth < root.listCompressedImplicitWidth
icon.name: "dialog-text-and-font"
enabled: root.contentModel.focusType !== LibNeoChat.MessageComponentType.Code
text: i18nc("@action:button", "Format Text")
display: QQC2.AbstractButton.IconOnly
checkable: true
@@ -144,29 +175,41 @@ QQC2.ToolBar {
icon.name: "format-text-bold"
text: i18nc("@action:button", "Bold")
checkable: true
checked: root.documentHandler.bold
onTriggered: root.documentHandler.bold = checked;
checked: root.focusedDocumentHandler.bold
onTriggered: {
root.focusedDocumentHandler.bold = checked;
root.clicked();
}
}
QQC2.MenuItem {
icon.name: "format-text-italic"
text: i18nc("@action:button", "Italic")
checkable: true
checked: root.documentHandler.italic
onTriggered: root.documentHandler.italic = checked;
checked: root.focusedDocumentHandler.italic
onTriggered: {
root.focusedDocumentHandler.italic = checked;
root.clicked();
}
}
QQC2.MenuItem {
icon.name: "format-text-underline"
text: i18nc("@action:button", "Underline")
checkable: true
checked: root.documentHandler.underline
onTriggered: root.documentHandler.underline = checked;
checked: root.focusedDocumentHandler.underline
onTriggered: {
root.focusedDocumentHandler.underline = checked;
root.clicked();
}
}
QQC2.MenuItem {
icon.name: "format-text-strikethrough"
text: i18nc("@action:button", "Strikethrough")
checkable: true
checked: root.documentHandler.strikethrough
onTriggered: root.documentHandler.strikethrough = checked;
checked: root.focusedDocumentHandler.strikethrough
onTriggered: {
root.focusedDocumentHandler.strikethrough = checked;
root.clicked();
}
}
}
@@ -183,12 +226,14 @@ QQC2.ToolBar {
visible: root.maxAvailableWidth > root.uncompressedImplicitWidth
QQC2.ToolButton {
icon.name: "format-list-unordered"
enabled: root.contentModel.focusType !== LibNeoChat.MessageComponentType.Code
text: i18nc("@action:button", "Unordered List")
display: QQC2.AbstractButton.IconOnly
checkable: true
checked: root.documentHandler.currentListStyle === 1
checked: root.focusedDocumentHandler.currentListStyle === 1
onClicked: {
root.documentHandler.setListStyle(root.documentHandler.currentListStyle === 1 ? 0 : 1)
root.focusedDocumentHandler.setListStyle(root.focusedDocumentHandler.currentListStyle === 1 ? 0 : 1);
root.clicked();
}
QQC2.ToolTip.text: text
@@ -197,11 +242,15 @@ QQC2.ToolBar {
}
QQC2.ToolButton {
icon.name: "format-list-ordered"
enabled: root.contentModel.focusType !== LibNeoChat.MessageComponentType.Code
text: i18nc("@action:button", "Ordered List")
display: QQC2.AbstractButton.IconOnly
checkable: true
checked: root.documentHandler.currentListStyle === 4
onClicked: root.documentHandler.setListStyle(root.documentHandler.currentListStyle === 4 ? 0 : 4)
checked: root.focusedDocumentHandler.currentListStyle === 4
onClicked: {
root.focusedDocumentHandler.setListStyle(root.focusedDocumentHandler.currentListStyle === 4 ? 0 : 4);
root.clicked();
}
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
@@ -213,7 +262,8 @@ QQC2.ToolBar {
text: i18nc("@action:button", "Increase List Level")
display: QQC2.AbstractButton.IconOnly
onClicked: {
root.documentHandler.indentListMore();
root.focusedDocumentHandler.indentListMore();
root.clicked();
}
QQC2.ToolTip.text: text
@@ -226,7 +276,8 @@ QQC2.ToolBar {
text: i18nc("@action:button", "Decrease List Level")
display: QQC2.AbstractButton.IconOnly
onClicked: {
root.documentHandler.indentListLess();
root.focusedDocumentHandler.indentListLess();
root.clicked();
}
QQC2.ToolTip.text: text
@@ -236,6 +287,7 @@ QQC2.ToolBar {
}
QQC2.ToolButton {
id: compressedListButton
enabled: root.contentModel.focusType !== LibNeoChat.MessageComponentType.Code
visible: root.maxAvailableWidth < root.uncompressedImplicitWidth
icon.name: "format-list-unordered"
text: i18nc("@action:button", "List Style")
@@ -253,22 +305,34 @@ QQC2.ToolBar {
QQC2.MenuItem {
icon.name: "format-list-unordered"
text: i18nc("@action:button", "Unordered List")
onTriggered: root.documentHandler.setListStyle(root.documentHandler.currentListStyle === 1 ? 0 : 1);
onTriggered: {
root.focusedDocumentHandler.setListStyle(root.focusedDocumentHandler.currentListStyle === 1 ? 0 : 1);
root.clicked();
}
}
QQC2.MenuItem {
icon.name: "format-list-ordered"
text: i18nc("@action:button", "Ordered List")
onTriggered: root.documentHandler.setListStyle(root.documentHandler.currentListStyle === 4 ? 0 : 4);
onTriggered: {
root.focusedDocumentHandler.setListStyle(root.focusedDocumentHandler.currentListStyle === 4 ? 0 : 4);
root.clicked();
}
}
QQC2.MenuItem {
icon.name: "format-indent-more"
text: i18nc("@action:button", "Increase List Level")
onTriggered: root.documentHandler.indentListMore();
onTriggered: {
root.focusedDocumentHandler.indentListMore();
root.clicked();
}
}
QQC2.MenuItem {
icon.name: "format-indent-less"
text: i18nc("@action:button", "Decrease List Level")
onTriggered: root.documentHandler.indentListLess();
onTriggered: {
root.focusedDocumentHandler.indentListLess();
root.clicked();
}
}
}
@@ -293,31 +357,56 @@ QQC2.ToolBar {
QQC2.MenuItem {
text: i18nc("@item:inmenu no heading", "Paragraph")
onTriggered: root.documentHandler.setHeadingLevel(0);
onTriggered: root.contentModel.insertComponentAtCursor(LibNeoChat.MessageComponentType.Text);
}
QQC2.MenuItem {
text: i18nc("@item:inmenu heading level 1 (largest)", "Heading 1")
onTriggered: root.documentHandler.setHeadingLevel(1);
onTriggered: {
root.focusedDocumentHandler.style = LibNeoChat.ChatDocumentHandler.Heading1;
root.clicked();
}
}
QQC2.MenuItem {
text: i18nc("@item:inmenu heading level 2", "Heading 2")
onTriggered: root.documentHandler.setHeadingLevel(2);
onTriggered: {
root.focusedDocumentHandler.style = LibNeoChat.ChatDocumentHandler.Heading2;
root.clicked();
}
}
QQC2.MenuItem {
text: i18nc("@item:inmenu heading level 3", "Heading 3")
onTriggered: root.documentHandler.setHeadingLevel(3);
onTriggered: {
root.focusedDocumentHandler.style = LibNeoChat.ChatDocumentHandler.Heading3;
root.clicked();
}
}
QQC2.MenuItem {
text: i18nc("@item:inmenu heading level 4", "Heading 4")
onTriggered: root.documentHandler.setHeadingLevel(4);
onTriggered: {
root.focusedDocumentHandler.style = LibNeoChat.ChatDocumentHandler.Heading4;
root.clicked();
}
}
QQC2.MenuItem {
text: i18nc("@item:inmenu heading level 5", "Heading 5")
onTriggered: root.documentHandler.setHeadingLevel(5);
onTriggered: {
root.focusedDocumentHandler.style = LibNeoChat.ChatDocumentHandler.Heading5;
root.clicked();
}
}
QQC2.MenuItem {
text: i18nc("@item:inmenu heading level 6 (smallest)", "Heading 6")
onTriggered: root.documentHandler.setHeadingLevel(6);
onTriggered: {
root.focusedDocumentHandler.style = LibNeoChat.ChatDocumentHandler.Heading6;
root.clicked();
}
}
QQC2.MenuItem {
text: i18nc("@item:inmenu", "Quote")
onTriggered: {
root.contentModel.insertComponentAtCursor(LibNeoChat.MessageComponentType.Quote);
root.clicked();
}
}
}
@@ -325,6 +414,20 @@ QQC2.ToolBar {
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
QQC2.ToolButton {
id: codeButton
icon.name: "format-text-code"
text: i18n("Code")
display: QQC2.AbstractButton.IconOnly
onClicked: {
root.contentModel.insertComponentAtCursor(LibNeoChat.MessageComponentType.Code);
root.clicked();
}
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
QQC2.ToolTip.text: text
}
Kirigami.Separator {
Layout.fillHeight: true
Layout.margins: 0
@@ -354,10 +457,13 @@ QQC2.ToolBar {
display: QQC2.AbstractButton.IconOnly
onClicked: {
let dialog = linkDialog.createObject(QQC2.Overlay.overlay, {
linkText: root.documentHandler.currentLinkText(),
linkUrl: root.documentHandler.currentLinkUrl()
linkText: root.focusedDocumentHandler.currentLinkText(),
linkUrl: root.focusedDocumentHandler.currentLinkUrl()
})
dialog.onAccepted.connect(() => { documentHandler.updateLink(dialog.linkUrl, dialog.linkText) });
dialog.onAccepted.connect(() => {
root.focusedDocumentHandler.updateLink(dialog.linkUrl, dialog.linkText)
root.clicked();
});
dialog.open();
}
@@ -384,7 +490,7 @@ QQC2.ToolBar {
onClicked: {
let dialog = (LibNeoChat.Clipboard.hasImage ? attachDialog : openFileDialog).createObject(QQC2.Overlay.overlay);
dialog.chosen.connect(path => root.chatBarCache.attachmentPath = path);
dialog.chosen.connect(path => root.contentModel.addAttachment(path));
dialog.open();
}
QQC2.ToolTip.visible: hovered
@@ -482,9 +588,8 @@ QQC2.ToolBar {
icon.name: "document-send"
text: i18n("Send message")
display: QQC2.AbstractButton.IconOnly
checkable: true
onClicked: root.requestPostMessage()
onClicked: root.contentModel.postMessage();
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
QQC2.ToolTip.text: text
@@ -543,7 +648,7 @@ QQC2.ToolBar {
currentRoom: root.room
onChosen: emoji => {
root.documentHandler.insertText(emoji);
root.focusedDocumentHandler.insertText(emoji);
close();
}
onClosed: if (emojiButton.checked) {