feat: Keyboard shortcuts
Add keyboard shortcuts according to https://notes.status.im/02cfVf1KQLeQU2SqrIi9tw fix: update chat message bubbles - Align emojis to middle of text - Add line-height as per design - Properly support RTL languages (right-aligned) and LTR languages (left-aligned) - Remove unneeded non-breaking space at the beginning of current user messages - Properly support markdown for bold, strikethrough, and italic - Fix text being removed when in between strikethrough markdown (~~) fix: emoji resolution update for high resolution monitors - Emojis now use the 72x72 original set, but are down-scaled to 20x20 (in chat bubbles) or 22x22 in other places, effectively tripling their pixel density feat: handle new lines in blockquote Handle new lines in blockquote so that messages display correctly. Also, add functionality when a new line is entered in to the chat input, if it's inside a blockquote, a new ">" will be added automatically. This is also handled when backspace is entered. feat: update xss to support full qt html4 table and table-cell attributes
This commit is contained in:
parent
e2ec5fa84e
commit
417194e7b4
|
@ -1,4 +1,6 @@
|
||||||
import sequtils
|
import sequtils, re
|
||||||
|
|
||||||
|
let NEW_LINE = re"\n|\r"
|
||||||
|
|
||||||
proc sectionIdentifier(message: Message): string =
|
proc sectionIdentifier(message: Message): string =
|
||||||
result = message.fromAuthor
|
result = message.fromAuthor
|
||||||
|
@ -15,15 +17,16 @@ proc mention(self: ChatMessageList, pubKey: string): string =
|
||||||
|
|
||||||
# See render-inline in status-react/src/status_im/ui/screens/chat/message/message.cljs
|
# See render-inline in status-react/src/status_im/ui/screens/chat/message/message.cljs
|
||||||
proc renderInline(self: ChatMessageList, elem: TextItem): string =
|
proc renderInline(self: ChatMessageList, elem: TextItem): string =
|
||||||
let value = escape_html(elem.literal.strip)
|
let value = escape_html(elem.literal)
|
||||||
case elem.textType:
|
case elem.textType:
|
||||||
of "": result = value
|
of "": result = value
|
||||||
of "code": result = fmt(" <code>{value}</code> ")
|
of "code": result = fmt("<code>{value}</code>")
|
||||||
of "emph": result = fmt(" <em>{value}</em> ")
|
of "emph": result = fmt("<em>{value}</em>")
|
||||||
of "strong": result = fmt(" <strong>{value}</strong> ")
|
of "strong": result = fmt("<strong>{value}</strong>")
|
||||||
of "link": result = fmt(" {elem.destination} ")
|
of "link": result = fmt("{elem.destination}")
|
||||||
of "mention": result = fmt(" <a href=\"//{value}\" class=\"mention\">{self.mention(value)}</a> ")
|
of "mention": result = fmt("<a href=\"//{value}\" class=\"mention\">{self.mention(value)}</a>")
|
||||||
of "status-tag": result = fmt(" <a href=\"#{value}\" class=\"status-tag\">#{value}</a> ")
|
of "status-tag": result = fmt("<a href=\"#{value}\" class=\"status-tag\">#{value}</a>")
|
||||||
|
of "del": result = fmt("<del>{value}</del>")
|
||||||
|
|
||||||
# See render-block in status-react/src/status_im/ui/screens/chat/message/message.cljs
|
# See render-block in status-react/src/status_im/ui/screens/chat/message/message.cljs
|
||||||
proc renderBlock(self: ChatMessageList, message: Message): string =
|
proc renderBlock(self: ChatMessageList, message: Message): string =
|
||||||
|
@ -31,14 +34,26 @@ proc renderBlock(self: ChatMessageList, message: Message): string =
|
||||||
case pMsg.textType:
|
case pMsg.textType:
|
||||||
of "paragraph":
|
of "paragraph":
|
||||||
result = result & "<p>"
|
result = result & "<p>"
|
||||||
if message.isCurrentUser:
|
|
||||||
result = result & " "
|
|
||||||
for children in pMsg.children:
|
for children in pMsg.children:
|
||||||
result = result & self.renderInline(children)
|
result = result & self.renderInline(children)
|
||||||
result = result & "</p>"
|
result = result & "</p>"
|
||||||
of "blockquote":
|
of "blockquote":
|
||||||
result = result & pMsg.literal.strip.split("\n").mapIt("<span>▍ " & escape_html(it) & "</span>").join("<br />")
|
var
|
||||||
|
blockquote = escape_html(pMsg.literal)
|
||||||
|
lines = toSeq(blockquote.split(NEW_LINE))
|
||||||
|
for i in 0..(lines.len - 1):
|
||||||
|
if i + 1 >= lines.len:
|
||||||
|
continue
|
||||||
|
if lines[i + 1] != "":
|
||||||
|
lines[i] = lines[i] & "<br/>"
|
||||||
|
blockquote = lines.join("")
|
||||||
|
result = result & fmt(
|
||||||
|
"<table class=\"blockquote\">" &
|
||||||
|
"<tr>" &
|
||||||
|
"<td class=\"quoteline\" valign=\"middle\"></td>" &
|
||||||
|
"<td>{blockquote}</td>" &
|
||||||
|
"</tr>" &
|
||||||
|
"</table>")
|
||||||
of "codeblock":
|
of "codeblock":
|
||||||
result = result & "<code>" & escape_html(pMsg.literal.strip) & "</code>"
|
result = result & "<code>" & escape_html(pMsg.literal) & "</code>"
|
||||||
result = result.strip()
|
result = result.strip()
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,7 @@ Loader {
|
||||||
visible: repliedMessageType != Constants.imageType
|
visible: repliedMessageType != Constants.imageType
|
||||||
anchors.top: lblReplyAuthor.bottom
|
anchors.top: lblReplyAuthor.bottom
|
||||||
anchors.topMargin: 5
|
anchors.topMargin: 5
|
||||||
text: Emoji.parse(Utils.linkifyAndXSS(repliedMessageContent), "26x26");
|
text: Emoji.parse(Utils.linkifyAndXSS(repliedMessageContent));
|
||||||
textFormat: Text.RichText
|
textFormat: Text.RichText
|
||||||
color: root.elementsColor
|
color: root.elementsColor
|
||||||
readOnly: true
|
readOnly: true
|
||||||
|
|
|
@ -40,7 +40,6 @@ Item {
|
||||||
StyledTextEdit {
|
StyledTextEdit {
|
||||||
id: chatText
|
id: chatText
|
||||||
textFormat: Text.RichText
|
textFormat: Text.RichText
|
||||||
horizontalAlignment: Text.AlignLeft
|
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
font.pixelSize: Style.current.primaryTextFontSize
|
font.pixelSize: Style.current.primaryTextFontSize
|
||||||
readOnly: true
|
readOnly: true
|
||||||
|
@ -66,35 +65,41 @@ Item {
|
||||||
if(contentType === Constants.stickerType) return "";
|
if(contentType === Constants.stickerType) return "";
|
||||||
let msg = Utils.linkifyAndXSS(message);
|
let msg = Utils.linkifyAndXSS(message);
|
||||||
if(isEmoji) {
|
if(isEmoji) {
|
||||||
return Emoji.parse(msg, "72x72");
|
return Emoji.parse(msg, Emoji.size.big);
|
||||||
} else {
|
} else {
|
||||||
return `<html>`+
|
return `<style type="text/css">` +
|
||||||
`<head>`+
|
`p, img, a, del, code, blockquote { margin: 0; padding: 0; }` +
|
||||||
`<style type="text/css">`+
|
`code {` +
|
||||||
`code {`+
|
`background-color: ${Style.current.codeBackground};` +
|
||||||
`background-color: #1a356b;`+
|
`color: ${Style.current.white};` +
|
||||||
`color: #FFFFFF;`+
|
`white-space: pre;` +
|
||||||
`white-space: pre;`+
|
`}` +
|
||||||
`}`+
|
`p {` +
|
||||||
`p {`+
|
`line-height: 22px;` +
|
||||||
`white-space: pre-wrap;`+
|
`}` +
|
||||||
`}`+
|
`a {` +
|
||||||
`a {`+
|
`color: ${isCurrentUser && !appSettings.compactMode ? Style.current.white : Style.current.textColor};` +
|
||||||
`color: ${isCurrentUser && !appSettings.compactMode ? Style.current.white : Style.current.textColor};`+
|
`}` +
|
||||||
`}`+
|
`a.mention {` +
|
||||||
`a.mention {`+
|
`color: ${isCurrentUser ? Style.current.cyan : Style.current.turquoise};` +
|
||||||
`color: ${isCurrentUser ? Style.current.cyan : Style.current.turquoise};`+
|
`}` +
|
||||||
`}`+
|
`del {` +
|
||||||
`blockquote {`+
|
`text-decoration: line-through;` +
|
||||||
`margin: 0;`+
|
`}` +
|
||||||
`padding: 0;`+
|
`table.blockquote td {` +
|
||||||
`}`+
|
`padding-left: 10px;` +
|
||||||
`</style>`+
|
`color: ${isCurrentUser ? Style.current.chatReplyCurrentUser : Style.current.secondaryText};` +
|
||||||
`</head>`+
|
`}` +
|
||||||
`<body>`+
|
`table.blockquote td.quoteline {` +
|
||||||
`${Emoji.parse(msg, "26x26")}`+
|
`background-color: ${isCurrentUser ? Style.current.chatReplyCurrentUser : Style.current.secondaryText};` +
|
||||||
`</body>`+
|
`height: 100%;` +
|
||||||
`</html>`;
|
`padding-left: 0;` +
|
||||||
|
`}` +
|
||||||
|
`.emoji {` +
|
||||||
|
`vertical-align: bottom;` +
|
||||||
|
`}` +
|
||||||
|
`</style>` +
|
||||||
|
`${Emoji.parse(msg)}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ Item {
|
||||||
readonly property int defaultMaxMessageChars: 54
|
readonly property int defaultMaxMessageChars: 54
|
||||||
readonly property int messageWidth: Math.max(defaultMessageWidth, parent.width / 2)
|
readonly property int messageWidth: Math.max(defaultMessageWidth, parent.width / 2)
|
||||||
readonly property int maxMessageChars: (defaultMaxMessageChars * messageWidth) / defaultMessageWidth
|
readonly property int maxMessageChars: (defaultMaxMessageChars * messageWidth) / defaultMessageWidth
|
||||||
property int chatVerticalPadding: isImage ? 4 : 7
|
property int chatVerticalPadding: isImage ? 4 : 6
|
||||||
property int chatHorizontalPadding: isImage ? 0 : 12
|
property int chatHorizontalPadding: isImage ? 0 : 12
|
||||||
property bool longReply: chatReply.visible && repliedMessageContent.length > maxMessageChars
|
property bool longReply: chatReply.visible && repliedMessageContent.length > maxMessageChars
|
||||||
property bool longChatText: chatsModel.plainText(message).split('\n').some(function (messagePart) {
|
property bool longChatText: chatsModel.plainText(message).split('\n').some(function (messagePart) {
|
||||||
|
@ -125,7 +125,6 @@ Item {
|
||||||
anchors.leftMargin: chatBox.chatHorizontalPadding
|
anchors.leftMargin: chatBox.chatHorizontalPadding
|
||||||
anchors.right: chatBox.longChatText ? parent.right : undefined
|
anchors.right: chatBox.longChatText ? parent.right : undefined
|
||||||
anchors.rightMargin: chatBox.longChatText ? chatBox.chatHorizontalPadding : 0
|
anchors.rightMargin: chatBox.longChatText ? chatBox.chatHorizontalPadding : 0
|
||||||
textField.horizontalAlignment: !isCurrentUser ? Text.AlignLeft : Text.AlignRight
|
|
||||||
textField.color: !isCurrentUser ? Style.current.textColor : Style.current.currentUserTextColor
|
textField.color: !isCurrentUser ? Style.current.textColor : Style.current.currentUserTextColor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,7 @@ Rectangle {
|
||||||
StyledText {
|
StyledText {
|
||||||
id: contactInfo
|
id: contactInfo
|
||||||
text: wrapper.chatType !== Constants.chatTypePublic ?
|
text: wrapper.chatType !== Constants.chatTypePublic ?
|
||||||
Emoji.parse(Utils.removeStatusEns(Utils.filterXSS(wrapper.name)), "26x26") :
|
Emoji.parse(Utils.removeStatusEns(Utils.filterXSS(wrapper.name))) :
|
||||||
"#" + Utils.filterXSS(wrapper.name)
|
"#" + Utils.filterXSS(wrapper.name)
|
||||||
anchors.right: contactTime.left
|
anchors.right: contactTime.left
|
||||||
anchors.rightMargin: Style.current.smallPadding
|
anchors.rightMargin: Style.current.smallPadding
|
||||||
|
@ -89,7 +89,7 @@ Rectangle {
|
||||||
//% "Sticker"
|
//% "Sticker"
|
||||||
case Constants.stickerType: return qsTrId("sticker");
|
case Constants.stickerType: return qsTrId("sticker");
|
||||||
//% "No messages"
|
//% "No messages"
|
||||||
default: return lastMessage ? Emoji.parse(Utils.filterXSS(lastMessage), "26x26").replace(/\n|\r/g, ' ') : qsTrId("no-messages")
|
default: return lastMessage ? Emoji.parse(Utils.filterXSS(lastMessage)).replace(/\n|\r/g, ' ') : qsTrId("no-messages")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
textFormat: Text.RichText
|
textFormat: Text.RichText
|
||||||
|
|
|
@ -39,6 +39,23 @@ RowLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Action {
|
||||||
|
shortcut: "Ctrl+1"
|
||||||
|
onTriggered: changeAppSection(Constants.chat)
|
||||||
|
}
|
||||||
|
Action {
|
||||||
|
shortcut: "Ctrl+2"
|
||||||
|
onTriggered: changeAppSection(Constants.browser)
|
||||||
|
}
|
||||||
|
Action {
|
||||||
|
shortcut: "Ctrl+3"
|
||||||
|
onTriggered: changeAppSection(Constants.wallet)
|
||||||
|
}
|
||||||
|
Action {
|
||||||
|
shortcut: "Ctrl+4, Ctrl+,"
|
||||||
|
onTriggered: changeAppSection(Constants.profile)
|
||||||
|
}
|
||||||
|
|
||||||
function changeAppSection(section) {
|
function changeAppSection(section) {
|
||||||
let sectionId = -1
|
let sectionId = -1
|
||||||
switch (section) {
|
switch (section) {
|
||||||
|
|
|
@ -5,28 +5,38 @@ import "./twemoji/twemoji.js" as Twemoji
|
||||||
import "../shared/status/emojiList.js" as EmojiJSON
|
import "../shared/status/emojiList.js" as EmojiJSON
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
|
readonly property var size: {
|
||||||
|
"big": "72x72",
|
||||||
|
"small": "20x20"
|
||||||
|
}
|
||||||
property string base: Qt.resolvedUrl("twemoji/")
|
property string base: Qt.resolvedUrl("twemoji/")
|
||||||
function parse(text, size) {
|
function parse(text, renderSize = size.small) {
|
||||||
|
const renderSizes = renderSize.split("x");
|
||||||
|
if (!renderSize.includes("x") || renderSizes.length !== 2) {
|
||||||
|
throw new Error("Invalid value for 'renderSize' parameter: ", renderSize);
|
||||||
|
}
|
||||||
Twemoji.twemoji.base = base
|
Twemoji.twemoji.base = base
|
||||||
Twemoji.twemoji.ext = ".png"
|
Twemoji.twemoji.ext = ".png"
|
||||||
Twemoji.twemoji.size = size
|
Twemoji.twemoji.size = size.big // source size in filesystem - get 72x72 and downscale for increased pixel density
|
||||||
return Twemoji.twemoji.parse(text)
|
return Twemoji.twemoji.parse(text, {
|
||||||
|
attributes: function() { return { width: renderSizes[0], height: renderSizes[1] }}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
function fromCodePoint(value) {
|
function fromCodePoint(value) {
|
||||||
return Twemoji.twemoji.convert.fromCodePoint(value)
|
return Twemoji.twemoji.convert.fromCodePoint(value)
|
||||||
}
|
}
|
||||||
function deparse(value){
|
function deparse(value){
|
||||||
return value.replace(/<img src=\"qrc:\/imports\/twemoji\/.+?" alt=\"(.+?)\" \/>/g, "$1");
|
return value.replace(/<img src=\"qrc:\/imports\/twemoji\/.+?" alt=\"(.+?)\" width=\"[0-9]*\" height=\"[0-9]*\" \/>/g, "$1");
|
||||||
}
|
}
|
||||||
function deparseFromParse(value) {
|
function deparseFromParse(value) {
|
||||||
return value.replace(/<img class=\"emoji\" draggable=\"false\" alt=\"(.+?)\" src=\"qrc:\/imports\/twemoji\/.+?"\/>/g, "$1");
|
return value.replace(/<img class=\"emoji\" draggable=\"false\" alt=\"(.+?)\" src=\"qrc:\/imports\/twemoji\/.+?" width=\"[0-9]*\" height=\"[0-9]*\"\/>/g, "$1");
|
||||||
}
|
}
|
||||||
function hasEmoji(value) {
|
function hasEmoji(value) {
|
||||||
let match = value.match(/<img src=\"qrc:\/imports\/twemoji\/.+?" alt=\"(.+?)\" \/>/g)
|
let match = value.match(/<img src=\"qrc:\/imports\/twemoji\/.+?" alt=\"(.+?)\" width=\"[0-9]*\" height=\"[0-9]*\"\ \/>/g)
|
||||||
return match && match.length > 0
|
return match && match.length > 0
|
||||||
}
|
}
|
||||||
function getEmojis(value) {
|
function getEmojis(value) {
|
||||||
return value.match(/<img class=\"emoji\" draggable=\"false\" alt=\"(.+?)\" src=\"qrc:\/imports\/twemoji\/.+?"\/>/g, "$1");
|
return value.match(/<img class=\"emoji\" draggable=\"false\" alt=\"(.+?)\" src=\"qrc:\/imports\/twemoji\/.+?" width=\"[0-9]*\" height=\"[0-9]*\"\/>/g, "$1");
|
||||||
}
|
}
|
||||||
function getEmojiUnicode(shortname) {
|
function getEmojiUnicode(shortname) {
|
||||||
var _emoji;
|
var _emoji;
|
||||||
|
|
|
@ -49,6 +49,7 @@ Theme {
|
||||||
property color pillButtonTextColor: almostBlack
|
property color pillButtonTextColor: almostBlack
|
||||||
property color chatReplyCurrentUser: evenDarkerGrey
|
property color chatReplyCurrentUser: evenDarkerGrey
|
||||||
property color topBarChatInfoColor: evenDarkerGrey
|
property color topBarChatInfoColor: evenDarkerGrey
|
||||||
|
property color codeBackground: "#2E386B"
|
||||||
|
|
||||||
property color buttonForegroundColor: blue
|
property color buttonForegroundColor: blue
|
||||||
property color buttonBackgroundColor: secondaryBackground
|
property color buttonBackgroundColor: secondaryBackground
|
||||||
|
|
|
@ -48,6 +48,7 @@ Theme {
|
||||||
property color pillButtonTextColor: white
|
property color pillButtonTextColor: white
|
||||||
property color chatReplyCurrentUser: lighterDarkGrey
|
property color chatReplyCurrentUser: lighterDarkGrey
|
||||||
property color topBarChatInfoColor: grey
|
property color topBarChatInfoColor: grey
|
||||||
|
property color codeBackground: "#2E386B"
|
||||||
|
|
||||||
property color buttonForegroundColor: blue
|
property color buttonForegroundColor: blue
|
||||||
property color buttonBackgroundColor: secondaryBackground
|
property color buttonBackgroundColor: secondaryBackground
|
||||||
|
|
|
@ -35,6 +35,7 @@ QtObject {
|
||||||
property color currentUserTextColor
|
property color currentUserTextColor
|
||||||
property color secondaryBackground
|
property color secondaryBackground
|
||||||
property color modalBackground
|
property color modalBackground
|
||||||
|
property color codeBackground
|
||||||
|
|
||||||
property color buttonForegroundColor
|
property color buttonForegroundColor
|
||||||
property color buttonBackgroundColor
|
property color buttonBackgroundColor
|
||||||
|
|
22
ui/main.qml
22
ui/main.qml
|
@ -37,6 +37,28 @@ ApplicationWindow {
|
||||||
}
|
}
|
||||||
visible: true
|
visible: true
|
||||||
|
|
||||||
|
Action {
|
||||||
|
shortcut: StandardKey.FullScreen
|
||||||
|
onTriggered: {
|
||||||
|
if (visibility === Window.FullScreen) {
|
||||||
|
showNormal()
|
||||||
|
} else {
|
||||||
|
showFullScreen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Action {
|
||||||
|
shortcut: "Ctrl+M"
|
||||||
|
onTriggered: {
|
||||||
|
if (visibility === Window.Minimized) {
|
||||||
|
showNormal()
|
||||||
|
} else {
|
||||||
|
showMinimized()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
// Change the theme to the system theme (dark/light) until we get the
|
// Change the theme to the system theme (dark/light) until we get the
|
||||||
// user's saved setting from status-go (after login)
|
// user's saved setting from status-go (after login)
|
||||||
|
|
|
@ -79,6 +79,10 @@ Rectangle {
|
||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isUploadFilePressed(event) {
|
||||||
|
return (event.key === Qt.Key_U) && (event.modifiers & Qt.ControlModifier) && imageBtn.visible && !imageDialog.visible
|
||||||
|
}
|
||||||
|
|
||||||
function onKeyPress(event){
|
function onKeyPress(event){
|
||||||
if (event.modifiers === Qt.NoModifier && (event.key === Qt.Key_Enter || event.key === Qt.Key_Return)) {
|
if (event.modifiers === Qt.NoModifier && (event.key === Qt.Key_Enter || event.key === Qt.Key_Return)) {
|
||||||
if (emojiSuggestions.visible) {
|
if (emojiSuggestions.visible) {
|
||||||
|
@ -99,10 +103,42 @@ Rectangle {
|
||||||
messageTooLongDialog.open()
|
messageTooLongDialog.open()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handle new line in blockquote
|
||||||
|
if ((event.key === Qt.Key_Enter || event.key === Qt.Key_Return) && (event.modifiers & Qt.ShiftModifier)) {
|
||||||
|
const message = control.extrapolateCursorPosition();
|
||||||
|
if(message.data.startsWith(">") && !message.data.endsWith("\n\n")) {
|
||||||
|
let newMessage = ""
|
||||||
|
if (message.data.endsWith("\n> ")) {
|
||||||
|
newMessage = message.data.substr(0, message.data.lastIndexOf("> ")) + "\n\n"
|
||||||
|
} else {
|
||||||
|
newMessage = message.data + "\n> ";
|
||||||
|
}
|
||||||
|
messageInputField.remove(0, messageInputField.cursorPosition);
|
||||||
|
insertInTextInput(0, Emoji.parse(newMessage));
|
||||||
|
}
|
||||||
|
event.accepted = true
|
||||||
|
}
|
||||||
|
// handle backspace when entering an existing blockquote
|
||||||
|
if ((event.key === Qt.Key_Backspace || event.key === Qt.Key_Delete)) {
|
||||||
|
const message = control.extrapolateCursorPosition();
|
||||||
|
if(message.data.startsWith(">") && message.data.endsWith("\n\n")) {
|
||||||
|
const newMessage = message.data.substr(0, message.data.lastIndexOf("\n")) + "> ";
|
||||||
|
messageInputField.remove(0, messageInputField.cursorPosition);
|
||||||
|
insertInTextInput(0, Emoji.parse(newMessage));
|
||||||
|
event.accepted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ((event.key === Qt.Key_V) && (event.modifiers & Qt.ControlModifier)) {
|
if ((event.key === Qt.Key_V) && (event.modifiers & Qt.ControlModifier)) {
|
||||||
paste = true;
|
paste = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ⌘⇧U
|
||||||
|
if (isUploadFilePressed(event)) {
|
||||||
|
imageBtn.clicked()
|
||||||
|
event.accepted = true
|
||||||
|
}
|
||||||
|
|
||||||
if (event.key === Qt.Key_Down) {
|
if (event.key === Qt.Key_Down) {
|
||||||
suggestionsBox.listView.incrementCurrentIndex()
|
suggestionsBox.listView.incrementCurrentIndex()
|
||||||
return emojiSuggestions.listView.incrementCurrentIndex()
|
return emojiSuggestions.listView.incrementCurrentIndex()
|
||||||
|
@ -118,6 +154,12 @@ Rectangle {
|
||||||
isColonPressed = (event.key === Qt.Key_Colon) && (event.modifiers & Qt.ShiftModifier);
|
isColonPressed = (event.key === Qt.Key_Colon) && (event.modifiers & Qt.ShiftModifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function wrapSelection(wrapWith) {
|
||||||
|
if (messageInputField.selectionStart - messageInputField.selectionEnd === 0) return
|
||||||
|
insertInTextInput(messageInputField.selectionStart, wrapWith);
|
||||||
|
insertInTextInput(messageInputField.selectionEnd, wrapWith);
|
||||||
|
messageInputField.deselect()
|
||||||
|
}
|
||||||
|
|
||||||
function onRelease(event) {
|
function onRelease(event) {
|
||||||
// the text doesn't get registered to the textarea fast enough
|
// the text doesn't get registered to the textarea fast enough
|
||||||
|
@ -160,7 +202,7 @@ Rectangle {
|
||||||
|
|
||||||
if (madeChanges) {
|
if (madeChanges) {
|
||||||
messageInputField.remove(0, messageInputField.length);
|
messageInputField.remove(0, messageInputField.length);
|
||||||
insertInTextInput(0, Emoji.parse(words.join(' '), '26x26'));
|
insertInTextInput(0, Emoji.parse(words.join(' ')));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,7 +211,7 @@ Rectangle {
|
||||||
function extrapolateCursorPosition() {
|
function extrapolateCursorPosition() {
|
||||||
// we need only the message part to be html
|
// we need only the message part to be html
|
||||||
const text = chatsModel.plainText(Emoji.deparse(messageInputField.text));
|
const text = chatsModel.plainText(Emoji.deparse(messageInputField.text));
|
||||||
const plainText = Emoji.parse(text, '26x26');
|
const plainText = Emoji.parse(text);
|
||||||
|
|
||||||
var bracketEvent = false;
|
var bracketEvent = false;
|
||||||
var length = 0;
|
var length = 0;
|
||||||
|
@ -258,7 +300,7 @@ Rectangle {
|
||||||
.replace(shortname, encodedCodePoint)
|
.replace(shortname, encodedCodePoint)
|
||||||
.replace(/ /g, " ");
|
.replace(/ /g, " ");
|
||||||
messageInputField.remove(0, messageInputField.cursorPosition);
|
messageInputField.remove(0, messageInputField.cursorPosition);
|
||||||
insertInTextInput(0, Emoji.parse(newMessage, '26x26'));
|
insertInTextInput(0, Emoji.parse(newMessage));
|
||||||
emojiSuggestions.close()
|
emojiSuggestions.close()
|
||||||
emojiEvent = false
|
emojiEvent = false
|
||||||
}
|
}
|
||||||
|
@ -391,7 +433,7 @@ Rectangle {
|
||||||
text = `${left} @${aliasName} ${right}`
|
text = `${left} @${aliasName} ${right}`
|
||||||
}
|
}
|
||||||
|
|
||||||
messageInputField.text = hasEmoji ? Emoji.parse(text, "26x26") : text
|
messageInputField.text = hasEmoji ? Emoji.parse(text) : text
|
||||||
messageInputField.cursorPosition = lastAtPosition + aliasName.length + 2
|
messageInputField.cursorPosition = lastAtPosition + aliasName.length + 2
|
||||||
suggestionsBox.suggestionsModel.clear()
|
suggestionsBox.suggestionsModel.clear()
|
||||||
}
|
}
|
||||||
|
@ -592,11 +634,40 @@ Rectangle {
|
||||||
bottomPadding: 12
|
bottomPadding: 12
|
||||||
Keys.onPressed: onKeyPress(event)
|
Keys.onPressed: onKeyPress(event)
|
||||||
Keys.onReleased: onRelease(event) // gives much more up to date cursorPosition
|
Keys.onReleased: onRelease(event) // gives much more up to date cursorPosition
|
||||||
|
Keys.onShortcutOverride: event.accepted = isUploadFilePressed(event)
|
||||||
leftPadding: 0
|
leftPadding: 0
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Action {
|
||||||
|
shortcut: StandardKey.Bold
|
||||||
|
onTriggered: wrapSelection("**")
|
||||||
|
}
|
||||||
|
Action {
|
||||||
|
shortcut: StandardKey.Italic
|
||||||
|
onTriggered: wrapSelection("*")
|
||||||
|
}
|
||||||
|
Action {
|
||||||
|
shortcut: "Ctrl+Shift+Alt+C"
|
||||||
|
onTriggered: wrapSelection("```")
|
||||||
|
}
|
||||||
|
Action {
|
||||||
|
shortcut: "Ctrl+Shift+C"
|
||||||
|
onTriggered: wrapSelection("`")
|
||||||
|
}
|
||||||
|
Action {
|
||||||
|
shortcut: "Ctrl+Alt+-"
|
||||||
|
onTriggered: wrapSelection("~~")
|
||||||
|
}
|
||||||
|
Action {
|
||||||
|
shortcut: "Ctrl+Shift+X"
|
||||||
|
onTriggered: wrapSelection("~~")
|
||||||
|
}
|
||||||
|
Action {
|
||||||
|
shortcut: "Ctrl+Meta+Space"
|
||||||
|
onTriggered: emojiBtn.clicked()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
|
|
@ -40,7 +40,7 @@ Rectangle {
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
id: replyText
|
id: replyText
|
||||||
text: Emoji.parse(message, "26x26")
|
text: Emoji.parse(message)
|
||||||
anchors.left: replyToUsername.left
|
anchors.left: replyToUsername.left
|
||||||
anchors.top: replyToUsername.bottom
|
anchors.top: replyToUsername.bottom
|
||||||
anchors.topMargin: 2
|
anchors.topMargin: 2
|
||||||
|
|
|
@ -63,7 +63,7 @@ Popup {
|
||||||
emojiSectionsRepeater.itemAt(0).allEmojis = recentEmojis
|
emojiSectionsRepeater.itemAt(0).allEmojis = recentEmojis
|
||||||
appSettings.recentEmojis = recentEmojis
|
appSettings.recentEmojis = recentEmojis
|
||||||
|
|
||||||
popup.emojiSelected(Emoji.parse(encodedIcon, "26x26") + ' ', true) // Adding a space because otherwise, some emojis would fuse since emoji is just a string
|
popup.emojiSelected(Emoji.parse(encodedIcon) + ' ', true) // Adding a space because otherwise, some emojis would fuse since emoji is just a string
|
||||||
popup.close()
|
popup.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,7 +135,7 @@ Popup {
|
||||||
anchors.verticalCenter: searchBox.verticalCenter
|
anchors.verticalCenter: searchBox.verticalCenter
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: emojiHeader.headerMargin
|
anchors.rightMargin: emojiHeader.headerMargin
|
||||||
source: "../../../../imports/twemoji/26x26/1f590.png"
|
source: "../../../../imports/twemoji/72x72/1f590.png"
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
|
|
@ -90,7 +90,7 @@ Item {
|
||||||
SVGImage {
|
SVGImage {
|
||||||
width: emojiSection.imageWidth
|
width: emojiSection.imageWidth
|
||||||
height: emojiSection.imageWidth
|
height: emojiSection.imageWidth
|
||||||
source: "../../imports/twemoji/26x26/" + modelData.filename
|
source: "../../imports/twemoji/72x72/" + modelData.filename
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
|
|
@ -10,7 +10,7 @@ StatusInputListPopup {
|
||||||
|
|
||||||
id: emojiSuggestions
|
id: emojiSuggestions
|
||||||
getImageSource: function (modelData) {
|
getImageSource: function (modelData) {
|
||||||
return `../../imports/twemoji/26x26/${modelData.unicode}.png`
|
return `../../imports/twemoji/72x72/${modelData.unicode}.png`
|
||||||
}
|
}
|
||||||
getText: function (modelData) {
|
getText: function (modelData) {
|
||||||
return modelData.shortname
|
return modelData.shortname
|
||||||
|
|
|
@ -11,6 +11,8 @@ Popup {
|
||||||
property var getImageSource: function () {}
|
property var getImageSource: function () {}
|
||||||
property var getText: function () {}
|
property var getText: function () {}
|
||||||
property var onClicked: function () {}
|
property var onClicked: function () {}
|
||||||
|
property int imageWidth: 22
|
||||||
|
property int imageHeight: 22
|
||||||
|
|
||||||
function openPopup(listParam) {
|
function openPopup(listParam) {
|
||||||
modelList = listParam
|
modelList = listParam
|
||||||
|
@ -62,6 +64,8 @@ Popup {
|
||||||
SVGImage {
|
SVGImage {
|
||||||
id: image
|
id: image
|
||||||
source: popup.getImageSource(modelData)
|
source: popup.getImageSource(modelData)
|
||||||
|
width: popup.imageWidth
|
||||||
|
height: popup.imageHeight
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: Style.current.smallPadding
|
anchors.leftMargin: Style.current.smallPadding
|
||||||
|
|
|
@ -297,9 +297,9 @@ function getDefaultWhiteList() {
|
||||||
sub: [],
|
sub: [],
|
||||||
sup: [],
|
sup: [],
|
||||||
strong: [],
|
strong: [],
|
||||||
table: ["width", "border", "align", "valign"],
|
table: ["width", "height", "border", "bgcolor", "cellspacing", "cellpadding", "class"],
|
||||||
tbody: ["align", "valign"],
|
tbody: ["align", "valign"],
|
||||||
td: ["width", "rowspan", "colspan", "align", "valign"],
|
td: ["width", "bgcolor", "rowspan", "colspan", "align", "valign", "class"],
|
||||||
tfoot: ["align", "valign"],
|
tfoot: ["align", "valign"],
|
||||||
th: ["width", "rowspan", "colspan", "align", "valign"],
|
th: ["width", "rowspan", "colspan", "align", "valign"],
|
||||||
thead: ["align", "valign"],
|
thead: ["align", "valign"],
|
||||||
|
|
Loading…
Reference in New Issue