Add Quick Formatting Menu in the Chatbar
Add a menu to quickly apply text effects when selecting text in the chatbar. 
This commit is contained in:
@@ -139,7 +139,37 @@ QQC2.Control {
|
|||||||
currentRoom.chatBoxText = text
|
currentRoom.chatBoxText = text
|
||||||
}
|
}
|
||||||
onCursorRectangleChanged: chatBarScrollView.ensureVisible(cursorRectangle)
|
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: {
|
Keys.onEnterPressed: {
|
||||||
if (completionMenu.visible) {
|
if (completionMenu.visible) {
|
||||||
completionMenu.complete()
|
completionMenu.complete()
|
||||||
@@ -180,9 +210,14 @@ QQC2.Control {
|
|||||||
completionMenu.decrementIndex()
|
completionMenu.decrementIndex()
|
||||||
} else if (event.key === Qt.Key_Down && completionMenu.visible) {
|
} else if (event.key === Qt.Key_Down && completionMenu.visible) {
|
||||||
completionMenu.incrementIndex()
|
completionMenu.incrementIndex()
|
||||||
} else if (event.key === Qt.Key_Backspace && textField.text.length <= 1) {
|
} else if (event.key === Qt.Key_Backspace) {
|
||||||
currentRoom.sendTypingNotification(false)
|
if (textField.text == selectedText || textField.text.length <= 1) {
|
||||||
repeatTimer.stop()
|
currentRoom.sendTypingNotification(false)
|
||||||
|
repeatTimer.stop()
|
||||||
|
}
|
||||||
|
if (quickFormatBar.visible && selectedText.length > 0) {
|
||||||
|
quickFormatBar.close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Keys.onShortcutOverride: {
|
Keys.onShortcutOverride: {
|
||||||
@@ -393,4 +428,47 @@ QQC2.Control {
|
|||||||
currentRoom.chatBoxReplyId = "";
|
currentRoom.chatBoxReplyId = "";
|
||||||
messageSent()
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
136
src/qml/Component/ChatBox/QuickFormatBar.qml
Normal file
136
src/qml/Component/ChatBox/QuickFormatBar.qml
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2022 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 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: "<del>",
|
||||||
|
end: "</del>",
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,6 +30,7 @@
|
|||||||
<file alias="ReplyPane.qml">qml/Component/ChatBox/ReplyPane.qml</file>
|
<file alias="ReplyPane.qml">qml/Component/ChatBox/ReplyPane.qml</file>
|
||||||
<file alias="CompletionMenu.qml">qml/Component/ChatBox/CompletionMenu.qml</file>
|
<file alias="CompletionMenu.qml">qml/Component/ChatBox/CompletionMenu.qml</file>
|
||||||
<file alias="PieProgressBar.qml">qml/Component/ChatBox/PieProgressBar.qml</file>
|
<file alias="PieProgressBar.qml">qml/Component/ChatBox/PieProgressBar.qml</file>
|
||||||
|
<file alias="QuickFormatBar.qml">qml/Component/ChatBox/QuickFormatBar.qml</file>
|
||||||
<file alias="EmojiPicker.qml">qml/Component/Emoji/EmojiPicker.qml</file>
|
<file alias="EmojiPicker.qml">qml/Component/Emoji/EmojiPicker.qml</file>
|
||||||
<file alias="ReplyComponent.qml">qml/Component/Timeline/ReplyComponent.qml</file>
|
<file alias="ReplyComponent.qml">qml/Component/Timeline/ReplyComponent.qml</file>
|
||||||
<file alias="StateDelegate.qml">qml/Component/Timeline/StateDelegate.qml</file>
|
<file alias="StateDelegate.qml">qml/Component/Timeline/StateDelegate.qml</file>
|
||||||
|
|||||||
Reference in New Issue
Block a user