diff --git a/ui/app/AppLayouts/Chat/ChatColumn/SuggestionFilter.qml b/ui/app/AppLayouts/Chat/ChatColumn/SuggestionFilter.qml index 4b860d0679..4619190a5d 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/SuggestionFilter.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/SuggestionFilter.qml @@ -74,6 +74,7 @@ Item { } let filterWithoutAt = filter.substring(lastAtPosition + 1, this.cursorPosition) + filterWithoutAt = filterWithoutAt.replace(/\*/g, "") return !properties.every(p => item[p].toLowerCase().match(filterWithoutAt.toLowerCase()) === null) } diff --git a/ui/imports/Constants.qml b/ui/imports/Constants.qml index 41d9298009..3e498a192f 100644 --- a/ui/imports/Constants.qml +++ b/ui/imports/Constants.qml @@ -125,4 +125,7 @@ QtObject { readonly property var acceptedImageExtensions: [".png", ".jpg", ".jpeg", ".svg", ".gif"] readonly property var acceptedDragNDropImageExtensions: [".png", ".jpg", ".jpeg", ".heif", "tif", ".tiff"] + + + readonly property string mentionSpanTag: `` } diff --git a/ui/imports/Emoji.qml b/ui/imports/Emoji.qml index efb75bea5b..354814ac2b 100644 --- a/ui/imports/Emoji.qml +++ b/ui/imports/Emoji.qml @@ -27,7 +27,7 @@ QtObject { function fromCodePoint(value) { return Twemoji.twemoji.convert.fromCodePoint(value) } - function deparse(value){ + function deparse(value) { return value.replace(/\"(.+?)\"/g, "$1"); } function deparseFromParse(value) { diff --git a/ui/shared/status/StatusChatInput.qml b/ui/shared/status/StatusChatInput.qml index dd25ae1889..6007f83492 100644 --- a/ui/shared/status/StatusChatInput.qml +++ b/ui/shared/status/StatusChatInput.qml @@ -200,11 +200,29 @@ Rectangle { formatInputMessage() } + function getPlainText() { + const textWithoutMention = messageInputField.text.replace(/(@([a-z\.]+(\ ?[a-z]+\ ?[a-z]+)?))<\/span>/ig, "\[\[mention\]\]$1\[\[mention\]\]") + + const deparsedEmoji = Emoji.deparse(textWithoutMention); + + return chatsModel.plainText(deparsedEmoji); + } + + function removeMentions(currentText) { + return currentText.replace(/\[\[mention\]\]/g, '') + } + + function parseBackText(plainText) { + plainText = plainText.replace(/\[\[mention\]\](@([a-z\.]+(\ ?[a-z]+\ ?[a-z]+)?))\[\[mention\]\]/gi, `${Constants.mentionSpanTag}$1`) + + + return parseMarkdown(Emoji.parse(plainText.replace(/\n/g, "
"))) + } + function formatInputMessage() { const posBeforeEnd = messageInputField.length - messageInputField.cursorPosition; - const deparsedEmoji = Emoji.deparse(messageInputField.text); - const plainText = chatsModel.plainText(deparsedEmoji); - const formatted = parseMarkdown(Emoji.parse(plainText.replace(/\n/g, "
"))) + const plainText = getPlainText() + const formatted = parseBackText(plainText) messageInputField.text = formatted messageInputField.cursorPosition = messageInputField.length - posBeforeEnd; } @@ -271,34 +289,51 @@ Rectangle { // to the actual position in the string. function extrapolateCursorPosition() { // we need only the message part to be html - const text = chatsModel.plainText(Emoji.deparse(messageInputField.text)); + const text = getPlainText() + const completelyPlainText = removeMentions(text) const plainText = Emoji.parse(text); + var bracketEvent = false; + var almostMention = false; + var mentionEvent = false; var length = 0; - for (var i = 0; i < plainText.length;) { - if (length >= messageInputField.cursorPosition) break; - if (!bracketEvent && plainText.charAt(i) !== '<') { - i++; + + // This loop calculates the cursor position inside the plain text which contains the image tags () and the mention tags ([[mention]]) + const cursorPos = messageInputField.cursorPosition + for (var i = 0; i < plainText.length;) { + if (length >= cursorPos) break; + + if (!bracketEvent && plainText.charAt(i) !== '<' && !mentionEvent && plainText.charAt(i) !== '[') { length++; } else if (!bracketEvent && plainText.charAt(i) === '<') { bracketEvent = true; - i++; - } else if (bracketEvent && plainText.charAt(i) !== '>') { - i++; } else if (bracketEvent && plainText.charAt(i) === '>') { bracketEvent = false; - i++; length++; + } else if (!mentionEvent && almostMention && plainText.charAt(i) === '[') { + almostMention = false + mentionEvent = true + } else if (!mentionEvent && !almostMention && plainText.charAt(i) === '[') { + almostMention = true + } else if (!mentionEvent && almostMention && plainText.charAt(i) !== '[') { + almostMention = false + } else if (mentionEvent && !almostMention && plainText.charAt(i) === ']') { + almostMention = true + } else if (mentionEvent && almostMention && plainText.charAt(i) === ']') { + almostMention = false + mentionEvent = false } + i++ } let textBeforeCursor = Emoji.deparseFromParse(plainText.substr(0, i)); + return { - cursor: countEmojiLengths(plainText.substr(0, i)) + messageInputField.cursorPosition, - data: Emoji.deparseFromParse(textBeforeCursor), + cursor: countEmojiLengths(plainText.substr(0, i)) + messageInputField.cursorPosition + text.length - completelyPlainText.length, + data: textBeforeCursor, }; } @@ -361,7 +396,7 @@ Rectangle { .replace(shortname, encodedCodePoint) .replace(/ /g, " "); messageInputField.remove(0, messageInputField.cursorPosition); - insertInTextInput(0, Emoji.parse(newMessage)); + insertInTextInput(0, parseBackText(newMessage)); emojiSuggestions.close() emojiEvent = false } @@ -496,29 +531,38 @@ Rectangle { cursorPosition: messageInputField.cursorPosition property: "ensName, localNickname, alias" onItemSelected: function (item, lastAtPosition, lastCursorPosition) { - let hasEmoji = Emoji.hasEmoji(messageInputField.text) - let currentText = hasEmoji ? - chatsModel.plainText(Emoji.deparse(messageInputField.text)) : - chatsModel.plainText(messageInputField.text); + const hasEmoji = Emoji.hasEmoji(messageInputField.text) + const currentText = getPlainText() - var properties = "ensName, alias"; // Ignore localNickname + const completelyPlainText = removeMentions(currentText) + + lastAtPosition += currentText.length - completelyPlainText.length + lastCursorPosition += currentText.length - completelyPlainText.length + + const properties = "ensName, alias"; // Ignore localNickname let aliasName = item[properties.split(",").map(p => p.trim()).find(p => !!item[p])] aliasName = aliasName.replace(/(\.stateofus)?\.eth/, "") let nameLen = aliasName.length + 2 // We're doing a +2 here because of the `@` and the trailing whitespace let position = 0; let text = "" + const spanPlusAlias = `${Constants.mentionSpanTag}@${aliasName}
` if (currentText === "@") { position = nameLen - text = "@" + aliasName + " " + text = spanPlusAlias } else { let left = currentText.substring(0, lastAtPosition) let right = currentText.substring(hasEmoji ? lastCursorPosition + 2 : lastCursorPosition) - text = `${left} @${aliasName} ${right}` + text = `${left} ${spanPlusAlias}${right}` } - messageInputField.text = hasEmoji ? Emoji.parse(text) : text + messageInputField.text = parseBackText(text) messageInputField.cursorPosition = lastAtPosition + aliasName.length + 2 + if (messageInputField.cursorPosition === 0) { + // It reset to 0 for some reason, go back to the end + messageInputField.cursorPosition = messageInputField.length + } + suggestionsBox.suggestionsModel.clear() } }