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:
emizzle 2020-11-17 14:07:01 +11:00 committed by Iuri Matias
parent e2ec5fa84e
commit 417194e7b4
18 changed files with 211 additions and 65 deletions

View File

@ -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 & "&nbsp;"
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()

View File

@ -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

View File

@ -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)}`
} }
} }
} }

View File

@ -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
} }

View File

@ -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

View File

@ -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) {

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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('&nbsp;'), '26x26')); insertInTextInput(0, Emoji.parse(words.join('&nbsp;')));
} }
} }
@ -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, "&nbsp;"); .replace(/ /g, "&nbsp;");
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 {

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"],