From 11e9af15f7c7aada558fe0129874aacafc331e7a Mon Sep 17 00:00:00 2001 From: James Graham Date: Sat, 18 Mar 2023 10:44:28 +0000 Subject: [PATCH] Add Quick Formatting Menu in the Chatbar Add a menu to quickly apply text effects when selecting text in the chatbar. ![image](/uploads/aa4d3b6f7e89695e1168aeee536d2df4/image.png) --- src/qml/Component/ChatBox/ChatBar.qml | 84 +++++++++++- src/qml/Component/ChatBox/QuickFormatBar.qml | 136 +++++++++++++++++++ src/res.qrc | 1 + 3 files changed, 218 insertions(+), 3 deletions(-) create mode 100644 src/qml/Component/ChatBox/QuickFormatBar.qml diff --git a/src/qml/Component/ChatBox/ChatBar.qml b/src/qml/Component/ChatBox/ChatBar.qml index f19001739..c98403928 100644 --- a/src/qml/Component/ChatBox/ChatBar.qml +++ b/src/qml/Component/ChatBox/ChatBar.qml @@ -139,7 +139,37 @@ QQC2.Control { currentRoom.chatBoxText = text } onCursorRectangleChanged: chatBarScrollView.ensureVisible(cursorRectangle) + onSelectedTextChanged: { + if (selectedText.length > 0) { + quickFormatBar.selectionStart = selectionStart + quickFormatBar.selectionEnd = selectionEnd + quickFormatBar.open() + } + } + QuickFormatBar { + id: quickFormatBar + + x: textField.cursorRectangle.x + y: textField.cursorRectangle.y - height + + onFormattingSelected: chatBar.formatText(format, selectionStart, selectionEnd) + } + + Keys.onDeletePressed: { + if (selectedText.length > 0) { + remove(selectionStart, selectionEnd) + } else { + remove(cursorPosition, cursorPosition + 1) + } + if (textField.text == selectedText || textField.text.length <= 1) { + currentRoom.sendTypingNotification(false) + repeatTimer.stop() + } + if (quickFormatBar.visible) { + quickFormatBar.close() + } + } Keys.onEnterPressed: { if (completionMenu.visible) { completionMenu.complete() @@ -180,9 +210,14 @@ QQC2.Control { completionMenu.decrementIndex() } else if (event.key === Qt.Key_Down && completionMenu.visible) { completionMenu.incrementIndex() - } else if (event.key === Qt.Key_Backspace && textField.text.length <= 1) { - currentRoom.sendTypingNotification(false) - repeatTimer.stop() + } else if (event.key === Qt.Key_Backspace) { + if (textField.text == selectedText || textField.text.length <= 1) { + currentRoom.sendTypingNotification(false) + repeatTimer.stop() + } + if (quickFormatBar.visible && selectedText.length > 0) { + quickFormatBar.close() + } } } Keys.onShortcutOverride: { @@ -393,4 +428,47 @@ QQC2.Control { currentRoom.chatBoxReplyId = ""; messageSent() } + + 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; + } + } } diff --git a/src/qml/Component/ChatBox/QuickFormatBar.qml b/src/qml/Component/ChatBox/QuickFormatBar.qml new file mode 100644 index 000000000..183260ebf --- /dev/null +++ b/src/qml/Component/ChatBox/QuickFormatBar.qml @@ -0,0 +1,136 @@ +// SPDX-FileCopyrightText: 2022 James Graham +// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + +import QtQuick 2.15 +import QtQuick.Controls 2.15 as QQC2 +import QtQuick.Layouts 1.15 + +import org.kde.kirigami 2.15 as Kirigami + +QQC2.Popup { + id: root + + property var selectionStart + property var selectionEnd + + signal formattingSelected(var format, int selectionStart, int selectionEnd) + + padding: 1 + + contentItem: Flow { + QQC2.ToolButton { + icon.name: "format-text-bold" + text: i18n("Bold") + display: QQC2.AbstractButton.IconOnly + + onClicked: { + const format = { + start: "**", + end: "**", + extra: "", + } + formattingSelected(format, selectionStart, selectionEnd) + root.close() + } + + QQC2.ToolTip.text: text + QQC2.ToolTip.visible: hovered + QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay + } + QQC2.ToolButton { + icon.name: "format-text-italic" + text: i18n("Italic") + display: QQC2.AbstractButton.IconOnly + + onClicked: { + const format = { + start: "*", + end: "*", + extra: "", + } + formattingSelected(format, selectionStart, selectionEnd) + root.close() + } + + QQC2.ToolTip.text: text + QQC2.ToolTip.visible: hovered + QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay + } + QQC2.ToolButton { + icon.name: "format-text-strikethrough" + text: i18n("Strikethrough") + display: QQC2.AbstractButton.IconOnly + + onClicked: { + const format = { + start: "", + end: "", + extra: "", + } + formattingSelected(format, selectionStart, selectionEnd) + root.close() + } + + QQC2.ToolTip.text: text + QQC2.ToolTip.visible: hovered + QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay + } + QQC2.ToolButton { + icon.name: "format-text-code" + text: i18n("Code block") + display: QQC2.AbstractButton.IconOnly + + onClicked: { + const format = { + start: "`", + end: "`", + extra: "", + } + formattingSelected(format, selectionStart, selectionEnd) + root.close() + } + + QQC2.ToolTip.text: text + QQC2.ToolTip.visible: hovered + QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay + } + QQC2.ToolButton { + icon.name: "format-text-blockquote" + text: i18n("Quote") + display: QQC2.AbstractButton.IconOnly + + onClicked: { + const format = { + start: selectionStart == 0 ? ">" : "\n>", + end: "\n\n", + extra: "", + } + formattingSelected(format, selectionStart, selectionEnd) + root.close() + } + + QQC2.ToolTip.text: text + QQC2.ToolTip.visible: hovered + QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay + } + QQC2.ToolButton { + icon.name: "link" + text: i18n("Insert link") + display: QQC2.AbstractButton.IconOnly + + onClicked: { + const format = { + start: "[", + end: "](", + extra: ")", + } + formattingSelected(format, selectionStart, selectionEnd) + root.close() + } + + QQC2.ToolTip.text: text + QQC2.ToolTip.visible: hovered + QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay + } + } +} diff --git a/src/res.qrc b/src/res.qrc index f2cfbc749..622257589 100644 --- a/src/res.qrc +++ b/src/res.qrc @@ -30,6 +30,7 @@ qml/Component/ChatBox/ReplyPane.qml qml/Component/ChatBox/CompletionMenu.qml qml/Component/ChatBox/PieProgressBar.qml + qml/Component/ChatBox/QuickFormatBar.qml qml/Component/Emoji/EmojiPicker.qml qml/Component/Timeline/ReplyComponent.qml qml/Component/Timeline/StateDelegate.qml