fix(StatusChatInput): New handler method for large messages
Instead of silently cutting off user text beyond the 2000 char limit, handle it more gracefully within certain limits. Closes #11767
This commit is contained in:
parent
57cfc425fc
commit
e532abb448
|
@ -44,8 +44,9 @@ Rectangle {
|
|||
property bool isImage: false
|
||||
property bool isEdit: false
|
||||
|
||||
property int messageLimit: 2000
|
||||
property int messageLimitVisible: 200
|
||||
readonly property int messageLimit: 2000 // actual message limit, we don't allow sending more than that
|
||||
readonly property int messageLimitSoft: 200 // we start showing a char counter when this no. of chars left in the message
|
||||
readonly property int messageLimitHard: 20000 // still cut-off attempts to paste beyond this limit, for app usability reasons
|
||||
|
||||
property int chatType
|
||||
|
||||
|
@ -55,7 +56,7 @@ Rectangle {
|
|||
|
||||
property var fileUrlsAndSources: []
|
||||
|
||||
property var imageErrorMessageLocation: StatusChatInput.ImageErrorMessageLocation.Top // TODO: Remove this proeprty?
|
||||
property var imageErrorMessageLocation: StatusChatInput.ImageErrorMessageLocation.Top // TODO: Remove this property?
|
||||
|
||||
property alias suggestions: suggestionsBox
|
||||
|
||||
|
@ -274,7 +275,7 @@ Rectangle {
|
|||
}
|
||||
|
||||
function insertInTextInput(start, text) {
|
||||
// Repace new lines with entities because `insert` gets rid of them
|
||||
// Replace new lines with entities because `insert` gets rid of them
|
||||
messageInputField.insert(start, text.replace(/\n/g, "<br/>"));
|
||||
}
|
||||
|
||||
|
@ -325,26 +326,33 @@ Rectangle {
|
|||
}
|
||||
|
||||
function onKeyPress(event) {
|
||||
// get text without HTML formatting
|
||||
const messageLength = messageInputField.getText(0, messageInputField.length).length;
|
||||
|
||||
if (event.modifiers === Qt.NoModifier && (event.key === Qt.Key_Enter || event.key === Qt.Key_Return)) {
|
||||
if (checkTextInsert()) {
|
||||
event.accepted = true;
|
||||
return
|
||||
}
|
||||
|
||||
if (messageInputField.length <= messageLimit) {
|
||||
if (messageLength <= messageLimit) {
|
||||
checkForInlineEmojis(true);
|
||||
control.sendMessage(event)
|
||||
control.sendMessage(event);
|
||||
control.hideExtendedArea();
|
||||
event.accepted = true
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
if (event) {
|
||||
event.accepted = true
|
||||
console.error("Attempting to send a message exceeding length limit")
|
||||
else {
|
||||
// pop-up a warning message when trying to send a message over the limit
|
||||
messageLengthLimitTooltip.open();
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
} else if (event.key === Qt.Key_Escape && control.isReply) {
|
||||
control.isReply = false
|
||||
event.accepted = true
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_Escape && control.isReply) {
|
||||
control.isReply = false;
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const symbolPressed = event.text.length > 0 &&
|
||||
|
@ -427,6 +435,16 @@ Rectangle {
|
|||
validateImagesAndShowImageArea([clipboardImage])
|
||||
event.accepted = true
|
||||
} else if (QClipboardProxy.hasText) {
|
||||
const clipboardText = Utils.plainText(QClipboardProxy.text)
|
||||
// prevent repetitive & huge clipboard paste, where huge is total char count > than messageLimitHard
|
||||
const selectionLength = messageInputField.selectionEnd - messageInputField.selectionStart;
|
||||
if ((messageLength + clipboardText.length - selectionLength) > control.messageLimitHard)
|
||||
{
|
||||
messageLengthLimitTooltip.open();
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
messageInputField.remove(messageInputField.selectionStart, messageInputField.selectionEnd)
|
||||
|
||||
// cursor position must be stored in a helper property because setting readonly to true causes change
|
||||
|
@ -434,7 +452,6 @@ Rectangle {
|
|||
d.copyTextStart = messageInputField.cursorPosition
|
||||
messageInputField.readOnly = true
|
||||
|
||||
const clipboardText = Utils.plainText(QClipboardProxy.text)
|
||||
const copiedText = Utils.plainText(d.copiedTextPlain)
|
||||
if (copiedText === clipboardText) {
|
||||
if (d.copiedTextPlain.includes("@")) {
|
||||
|
@ -467,6 +484,7 @@ Rectangle {
|
|||
("<div style='white-space: pre-wrap'>" + StatusQUtils.StringUtils.escapeHtml(QClipboardProxy.text) + "</div>")
|
||||
: StatusQUtils.Emoji.deparse(QClipboardProxy.html)));
|
||||
}
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -529,7 +547,7 @@ Rectangle {
|
|||
function unwrapSelection(unwrapWith, selectedTextWithFormationChars) {
|
||||
if (messageInputField.selectionStart - messageInputField.selectionEnd === 0) return
|
||||
|
||||
// calulate the new selection start and end positions
|
||||
// Calculate the new selection start and end positions
|
||||
var newSelectionStart = messageInputField.selectionStart - unwrapWith.length
|
||||
var newSelectionEnd = messageInputField.selectionEnd-messageInputField.selectionStart + newSelectionStart
|
||||
|
||||
|
@ -601,51 +619,6 @@ Rectangle {
|
|||
return result
|
||||
}
|
||||
|
||||
function setFormatInInput(formationFunction, startTag, endTag, formationChar, numFormationChars) {
|
||||
const inputText = getFormattedText()
|
||||
const plainInputText = messageInputField.getText(0, messageInputField.length)
|
||||
|
||||
let lengthDifference
|
||||
|
||||
try {
|
||||
const result = formationFunction(inputText)
|
||||
|
||||
if (!result) {
|
||||
return
|
||||
}
|
||||
const parsed = JSON.parse(result)
|
||||
|
||||
let substring
|
||||
let nbEmojis
|
||||
parsed.forEach(function (match) {
|
||||
match[1] += 1
|
||||
const truncatedInputText = inputText.substring(0, match[1] + numFormationChars)
|
||||
const truncatedPlainText = plainInputText.substring(0, messageInputField.cursorPosition)
|
||||
|
||||
const lengthDifference = truncatedInputText.length - truncatedPlainText.length
|
||||
|
||||
nbEmojis = StatusQUtils.Emoji.nbEmojis(truncatedInputText)
|
||||
|
||||
|
||||
match[1] += (nbEmojis * -2)
|
||||
match[0] += (nbEmojis * -2)
|
||||
substring = inputText.substring(match[0], match[1])
|
||||
|
||||
if (plainInputText.charAt(match[0] - 1) !== formationChar) {
|
||||
match[0] -= lengthDifference
|
||||
match[1] -= lengthDifference
|
||||
} else {
|
||||
match[1] -= lengthDifference
|
||||
}
|
||||
|
||||
messageInputField.remove(match[0], match[1])
|
||||
insertInTextInput(match[0], `${startTag}${substring}${endTag}`)
|
||||
})
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
function onRelease(event) {
|
||||
if ((event.modifiers & Qt.ControlModifier) || (event.modifiers & Qt.MetaModifier)) // these are likely shortcuts with no meaningful text
|
||||
return
|
||||
|
@ -910,7 +883,7 @@ Rectangle {
|
|||
control.fileUrlsAndSources = imageArea.imageSource
|
||||
}
|
||||
|
||||
// Use this to validate and show the images. The concatanation of previous selected images is done automatically
|
||||
// Use this to validate and show the images. The concatenation of previous selected images is done automatically
|
||||
// Returns true if the images were valid and added
|
||||
function validateImagesAndShowImageArea(imagePaths) {
|
||||
const validImages = validateImages(imagePaths)
|
||||
|
@ -1068,16 +1041,21 @@ Rectangle {
|
|||
Rectangle {
|
||||
id: messageInput
|
||||
|
||||
readonly property int defaultInputFieldHeight: 40
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
implicitHeight: inputLayout.implicitHeight + inputLayout.anchors.topMargin + inputLayout.anchors.bottomMargin
|
||||
implicitWidth: inputLayout.implicitWidth + inputLayout.anchors.leftMargin + inputLayout.anchors.rightMargin
|
||||
|
||||
color: isEdit ? Theme.palette.statusChatInput.secondaryBackgroundColor : Style.current.inputBackground
|
||||
radius: 20
|
||||
|
||||
StatusQ.StatusToolTip {
|
||||
id: messageLengthLimitTooltip
|
||||
text: messageInputField.length >= control.messageLimitHard ? qsTr("Please reduce the message length")
|
||||
: qsTr("Maximum message character count is %n", "", control.messageLimit)
|
||||
orientation: StatusQ.StatusToolTip.Orientation.Top
|
||||
timeout: 3000 // show for 3 seconds
|
||||
}
|
||||
|
||||
Component {
|
||||
id: textFormatMenuComponent
|
||||
|
||||
|
@ -1203,7 +1181,7 @@ Rectangle {
|
|||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: (messageInputField.contentHeight + messageInputField.topPadding + messageInputField.bottomPadding)
|
||||
Layout.maximumHeight: 112
|
||||
Layout.maximumHeight: 200
|
||||
spacing: Style.current.radius
|
||||
StatusScrollView {
|
||||
id: inputScrollView
|
||||
|
@ -1294,9 +1272,10 @@ Rectangle {
|
|||
} else {
|
||||
checkForInlineEmojis()
|
||||
}
|
||||
} else {
|
||||
const removeFrom = (cursorPosition < messageLimit) ? cursorWhenPressed : messageLimit;
|
||||
} else if (length > control.messageLimitHard) {
|
||||
const removeFrom = (cursorPosition < messageLimitHard) ? cursorWhenPressed : messageLimitHard;
|
||||
remove(removeFrom, cursorPosition);
|
||||
messageLengthLimitTooltip.open();
|
||||
}
|
||||
|
||||
d.updateMentionsPositions()
|
||||
|
@ -1400,9 +1379,26 @@ Rectangle {
|
|||
property int remainingChars: -1
|
||||
leftPadding: Style.current.halfPadding
|
||||
rightPadding: Style.current.halfPadding
|
||||
visible: ((messageInputField.length >= control.messageLimitVisible) && (messageInputField.length <= control.messageLimit))
|
||||
color: (remainingChars <= messageLimitVisible) ? Style.current.danger : Style.current.textColor
|
||||
visible: messageInputField.length >= control.messageLimit - control.messageLimitSoft
|
||||
color: {
|
||||
if (remainingChars >= 0)
|
||||
return Style.current.textColor
|
||||
else
|
||||
return Style.current.danger
|
||||
}
|
||||
text: visible ? remainingChars.toString() : ""
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onEntered: {
|
||||
messageLengthLimitTooltip.open()
|
||||
}
|
||||
onExited: {
|
||||
messageLengthLimitTooltip.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
|
|
Loading…
Reference in New Issue