feat: ponies.im emoji support (custom emojum)
This commit is contained in:
@@ -335,7 +335,7 @@ ToolBar {
|
|||||||
} else if (completionInfo.type === ChatDocumentHandler.Command) {
|
} else if (completionInfo.type === ChatDocumentHandler.Command) {
|
||||||
completionMenu.model = CommandModel.filterModel(completionInfo.keyword);
|
completionMenu.model = CommandModel.filterModel(completionInfo.keyword);
|
||||||
} else {
|
} else {
|
||||||
completionMenu.model = EmojiModel.filterModel(completionInfo.keyword);
|
completionMenu.model = Array.from(chatBar.customEmojiModel.filterModel(completionInfo.keyword)).concat(EmojiModel.filterModel(completionInfo.keyword))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (completionMenu.model.length === 0) {
|
if (completionMenu.model.length === 0) {
|
||||||
@@ -443,6 +443,10 @@ ToolBar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
property CustomEmojiModel customEmojiModel: CustomEmojiModel {
|
||||||
|
connection: Controller.activeConnection
|
||||||
|
}
|
||||||
|
|
||||||
function pasteImage() {
|
function pasteImage() {
|
||||||
let localPath = Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/screenshots/" + (new Date()).getTime() + ".png";
|
let localPath = Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/screenshots/" + (new Date()).getTime() + ".png";
|
||||||
if (!Clipboard.saveImage(localPath)) {
|
if (!Clipboard.saveImage(localPath)) {
|
||||||
@@ -457,7 +461,7 @@ ToolBar {
|
|||||||
if (ChatBoxHelper.hasAttachment) {
|
if (ChatBoxHelper.hasAttachment) {
|
||||||
// send attachment but don't reset the text
|
// send attachment but don't reset the text
|
||||||
actionsHandler.postMessage("", ChatBoxHelper.attachmentPath,
|
actionsHandler.postMessage("", ChatBoxHelper.attachmentPath,
|
||||||
ChatBoxHelper.replyEventId, ChatBoxHelper.editEventId, {});
|
ChatBoxHelper.replyEventId, ChatBoxHelper.editEventId, {}, this.customEmojiModel);
|
||||||
currentRoom.markAllMessagesAsRead();
|
currentRoom.markAllMessagesAsRead();
|
||||||
messageSent();
|
messageSent();
|
||||||
return;
|
return;
|
||||||
@@ -470,7 +474,7 @@ ToolBar {
|
|||||||
} else {
|
} else {
|
||||||
// send normal message
|
// send normal message
|
||||||
actionsHandler.postMessage(inputField.text.trim(), ChatBoxHelper.attachmentPath,
|
actionsHandler.postMessage(inputField.text.trim(), ChatBoxHelper.attachmentPath,
|
||||||
ChatBoxHelper.replyEventId, ChatBoxHelper.editEventId, userAutocompleted);
|
ChatBoxHelper.replyEventId, ChatBoxHelper.editEventId, userAutocompleted, this.customEmojiModel);
|
||||||
}
|
}
|
||||||
currentRoom.markAllMessagesAsRead();
|
currentRoom.markAllMessagesAsRead();
|
||||||
inputField.clear();
|
inputField.clear();
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import Qt.labs.qmlmodels 1.0
|
|||||||
import org.kde.kirigami 2.15 as Kirigami
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
import org.kde.neochat 1.0
|
||||||
|
import NeoChat.Component 1.0
|
||||||
|
|
||||||
Popup {
|
Popup {
|
||||||
id: control
|
id: control
|
||||||
@@ -94,23 +95,40 @@ Popup {
|
|||||||
Kirigami.BasicListItem {
|
Kirigami.BasicListItem {
|
||||||
id: emojiItem
|
id: emojiItem
|
||||||
width: ListView.view.width ?? implicitWidth
|
width: ListView.view.width ?? implicitWidth
|
||||||
property string displayName: modelData.unicode
|
property string displayName: modelData.isCustom ? modelData.shortname : modelData.unicode
|
||||||
text: modelData.unicode + " " + modelData.shortname
|
text: modelData.shortname
|
||||||
|
reserveSpaceForSubtitle: true
|
||||||
|
|
||||||
leading: Label {
|
leading: Image {
|
||||||
id: unicodeLabel
|
source: modelData.isCustom ? modelData.unicode : ""
|
||||||
Layout.preferredHeight: Kirigami.Units.gridUnit
|
|
||||||
Layout.preferredWidth: textMetrics.tightBoundingRect.width
|
width: height
|
||||||
font.pointSize: Kirigami.Units.gridUnit * 0.75
|
sourceSize.width: width
|
||||||
text: modelData.unicode
|
sourceSize.height: height
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
Rectangle {
|
||||||
}
|
anchors.fill: parent
|
||||||
TextMetrics {
|
visible: parent.status === Image.Loading
|
||||||
id: textMetrics
|
radius: height/2
|
||||||
text: modelData.unicode
|
gradient: ShimmerGradient { }
|
||||||
font: unicodeLabel.font
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
id: unicodeLabel
|
||||||
|
|
||||||
|
visible: !modelData.isCustom
|
||||||
|
|
||||||
|
font.family: 'emoji'
|
||||||
|
font.pixelSize: height - 2
|
||||||
|
|
||||||
|
text: modelData.unicode
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onClicked: completeTriggered();
|
onClicked: completeTriggered();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,10 +10,16 @@ import org.kde.neochat 1.0 as NeoChat
|
|||||||
import NeoChat.Component 1.0
|
import NeoChat.Component 1.0
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
|
id: _picker
|
||||||
|
|
||||||
property string emojiCategory: "history"
|
property string emojiCategory: "history"
|
||||||
property var textArea
|
property var textArea
|
||||||
readonly property var emojiModel: NeoChat.EmojiModel
|
readonly property var emojiModel: NeoChat.EmojiModel
|
||||||
|
|
||||||
|
property NeoChat.CustomEmojiModel customModel: NeoChat.CustomEmojiModel {
|
||||||
|
connection: NeoChat.Controller.activeConnection
|
||||||
|
}
|
||||||
|
|
||||||
signal chosen(string emoji)
|
signal chosen(string emoji)
|
||||||
|
|
||||||
spacing: 0
|
spacing: 0
|
||||||
@@ -29,6 +35,7 @@ ColumnLayout {
|
|||||||
orientation: ListView.Horizontal
|
orientation: ListView.Horizontal
|
||||||
|
|
||||||
model: ListModel {
|
model: ListModel {
|
||||||
|
ListElement { label: "custom"; category: "custom" }
|
||||||
ListElement { label: "⌛️"; category: "history" }
|
ListElement { label: "⌛️"; category: "history" }
|
||||||
ListElement { label: "😏"; category: "people" }
|
ListElement { label: "😏"; category: "people" }
|
||||||
ListElement { label: "🌲"; category: "nature" }
|
ListElement { label: "🌲"; category: "nature" }
|
||||||
@@ -41,16 +48,23 @@ ColumnLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
delegate: ItemDelegate {
|
delegate: ItemDelegate {
|
||||||
width: Kirigami.Units.gridUnit * 2
|
id: del
|
||||||
|
|
||||||
|
required property string label
|
||||||
|
required property string category
|
||||||
|
|
||||||
|
width: contentItem.Layout.preferredWidth
|
||||||
height: Kirigami.Units.gridUnit * 2
|
height: Kirigami.Units.gridUnit * 2
|
||||||
|
|
||||||
contentItem: Kirigami.Heading {
|
contentItem: Kirigami.Heading {
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
level: 1
|
level: del.label === "custom" ? 4 : 1
|
||||||
|
|
||||||
font.family: 'emoji'
|
Layout.preferredWidth: del.label === "custom" ? implicitWidth + Kirigami.Units.largeSpacing : Kirigami.Units.gridUnit * 2
|
||||||
text: label
|
|
||||||
|
font.family: del.label === "custom" ? undefined : 'emoji'
|
||||||
|
text: del.label === "custom" ? i18n("Custom") : del.label
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -87,6 +101,8 @@ ColumnLayout {
|
|||||||
|
|
||||||
model: {
|
model: {
|
||||||
switch (emojiCategory) {
|
switch (emojiCategory) {
|
||||||
|
case "custom":
|
||||||
|
return _picker.customModel
|
||||||
case "history":
|
case "history":
|
||||||
return emojiModel.history
|
return emojiModel.history
|
||||||
case "people":
|
case "people":
|
||||||
@@ -118,11 +134,32 @@ ColumnLayout {
|
|||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
font.family: 'emoji'
|
font.family: 'emoji'
|
||||||
text: modelData.unicode
|
text: modelData.isCustom ? "" : modelData.unicode
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
visible: modelData.isCustom
|
||||||
|
source: modelData.unicode
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 2
|
||||||
|
|
||||||
|
sourceSize.width: width
|
||||||
|
sourceSize.height: height
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
visible: parent.status === Image.Loading
|
||||||
|
radius: height/2
|
||||||
|
gradient: ShimmerGradient { }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
chosen(modelData.unicode)
|
if (modelData.isCustom) {
|
||||||
|
chosen(modelData.shortname)
|
||||||
|
} else {
|
||||||
|
chosen(modelData.unicode)
|
||||||
|
}
|
||||||
emojiModel.emojiUsed(modelData)
|
emojiModel.emojiUsed(modelData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
39
imports/NeoChat/Component/ShimmerGradient.qml
Normal file
39
imports/NeoChat/Component/ShimmerGradient.qml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2021 Carson Black <uhhadd@gmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||||
|
|
||||||
|
// Not to be confused with the Shimmer project.
|
||||||
|
// I like their gradiented GTK themes though.
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
Gradient {
|
||||||
|
id: gradient
|
||||||
|
|
||||||
|
orientation: Gradient.Horizontal
|
||||||
|
|
||||||
|
property color color: Kirigami.Theme.textColor
|
||||||
|
property color translucent: Qt.rgba(color.r, color.g, color.b, 0.2)
|
||||||
|
property color bright: Qt.rgba(color.r, color.g, color.b, 0.3)
|
||||||
|
property real pos: 0.5
|
||||||
|
property real offset: 0.6
|
||||||
|
|
||||||
|
property SequentialAnimation ani: SequentialAnimation {
|
||||||
|
running: true
|
||||||
|
loops: Animation.Infinite
|
||||||
|
NumberAnimation {
|
||||||
|
from: -2.0
|
||||||
|
to: 2.0
|
||||||
|
duration: 700
|
||||||
|
target: gradient
|
||||||
|
properties: "pos"
|
||||||
|
}
|
||||||
|
PauseAnimation {
|
||||||
|
duration: 300
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GradientStop { position: gradient.pos-gradient.offset; color: gradient.translucent }
|
||||||
|
GradientStop { position: gradient.pos; color: gradient.bright }
|
||||||
|
GradientStop { position: gradient.pos+gradient.offset; color: gradient.translucent }
|
||||||
|
}
|
||||||
@@ -4,3 +4,4 @@ ChatTextInput 1.0 ChatTextInput.qml
|
|||||||
FancyEffectsContainer 1.0 FancyEffectsContainer.qml
|
FancyEffectsContainer 1.0 FancyEffectsContainer.qml
|
||||||
TypingPane 1.0 TypingPane.qml
|
TypingPane 1.0 TypingPane.qml
|
||||||
QuickSwitcher 1.0 QuickSwitcher.qml
|
QuickSwitcher 1.0 QuickSwitcher.qml
|
||||||
|
ShimmerGradient 1.0 ShimmerGradient.qml
|
||||||
|
|||||||
@@ -53,6 +53,11 @@ Kirigami.ScrollablePage {
|
|||||||
icon.name: "preferences-system-users"
|
icon.name: "preferences-system-users"
|
||||||
onTriggered: pageSettingStack.push("qrc:/imports/NeoChat/Page/AccountsPage.qml")
|
onTriggered: pageSettingStack.push("qrc:/imports/NeoChat/Page/AccountsPage.qml")
|
||||||
},
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
text: i18n("Custom Emoji")
|
||||||
|
icon.name: "preferences-desktop-emoticons"
|
||||||
|
onTriggered: pageSettingStack.push("qrc:/imports/NeoChat/Settings/Emoticons.qml")
|
||||||
|
},
|
||||||
Kirigami.Action {
|
Kirigami.Action {
|
||||||
text: i18n("About NeoChat")
|
text: i18n("About NeoChat")
|
||||||
icon.name: "help-about"
|
icon.name: "help-about"
|
||||||
|
|||||||
104
imports/NeoChat/Settings/Emoticons.qml
Normal file
104
imports/NeoChat/Settings/Emoticons.qml
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2021 Carson Black <uhhadd@gmail.com>
|
||||||
|
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
import NeoChat.Settings 1.0
|
||||||
|
|
||||||
|
import NeoChat.Component 1.0 as Components
|
||||||
|
import NeoChat.Dialog 1.0
|
||||||
|
|
||||||
|
Kirigami.ScrollablePage {
|
||||||
|
Component {
|
||||||
|
id: openFileDialog
|
||||||
|
|
||||||
|
OpenFileDialog {
|
||||||
|
folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
header: QQC2.ToolBar {
|
||||||
|
width: parent.width
|
||||||
|
contentItem: RowLayout {
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: Qt.application.layoutDirection == Qt.LeftToRight
|
||||||
|
}
|
||||||
|
QQC2.TextField {
|
||||||
|
id: emojiField
|
||||||
|
placeholderText: i18n("new_emoji_name_here")
|
||||||
|
|
||||||
|
validator: RegularExpressionValidator {
|
||||||
|
regularExpression: /[a-zA-Z_0-9]*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QQC2.Button {
|
||||||
|
text: i18n("Add Emoji...")
|
||||||
|
|
||||||
|
enabled: emojiField.text != ""
|
||||||
|
property var fileDialog: null
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
if (this.fileDialog != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fileDialog = openFileDialog.createObject(QQC2.Overlay.overlay)
|
||||||
|
|
||||||
|
this.fileDialog.chosen.connect((url) => {
|
||||||
|
emojiModel.addEmoji(emojiField.text, url)
|
||||||
|
this.fileDialog = null
|
||||||
|
})
|
||||||
|
this.fileDialog.onRejected.connect(() => {
|
||||||
|
rej()
|
||||||
|
this.fileDialog = null
|
||||||
|
})
|
||||||
|
this.fileDialog.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: Qt.application.layoutDirection == Qt.RightToLeft
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
model: CustomEmojiModel {
|
||||||
|
id: emojiModel
|
||||||
|
|
||||||
|
connection: Controller.activeConnection
|
||||||
|
}
|
||||||
|
delegate: Kirigami.BasicListItem {
|
||||||
|
id: del
|
||||||
|
|
||||||
|
required property string name
|
||||||
|
required property url imageURL
|
||||||
|
|
||||||
|
text: name
|
||||||
|
reserveSpaceForSubtitle: true
|
||||||
|
|
||||||
|
leading: Image {
|
||||||
|
width: height
|
||||||
|
sourceSize.width: width
|
||||||
|
sourceSize.height: height
|
||||||
|
source: imageURL
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
visible: parent.status === Image.Loading
|
||||||
|
radius: height/2
|
||||||
|
gradient: Components.ShimmerGradient { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trailing: QQC2.ToolButton {
|
||||||
|
width: height
|
||||||
|
icon.name: "delete"
|
||||||
|
onClicked: emojiModel.removeEmoji(del.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
res.qrc
2
res.qrc
@@ -18,6 +18,7 @@
|
|||||||
<file>imports/NeoChat/Component/FullScreenImage.qml</file>
|
<file>imports/NeoChat/Component/FullScreenImage.qml</file>
|
||||||
<file>imports/NeoChat/Component/FancyEffectsContainer.qml</file>
|
<file>imports/NeoChat/Component/FancyEffectsContainer.qml</file>
|
||||||
<file>imports/NeoChat/Component/TypingPane.qml</file>
|
<file>imports/NeoChat/Component/TypingPane.qml</file>
|
||||||
|
<file>imports/NeoChat/Component/ShimmerGradient.qml</file>
|
||||||
<file>imports/NeoChat/Component/QuickSwitcher.qml</file>
|
<file>imports/NeoChat/Component/QuickSwitcher.qml</file>
|
||||||
<file>imports/NeoChat/Component/ChatBox</file>
|
<file>imports/NeoChat/Component/ChatBox</file>
|
||||||
<file>imports/NeoChat/Component/ChatBox/ChatBox.qml</file>
|
<file>imports/NeoChat/Component/ChatBox/ChatBox.qml</file>
|
||||||
@@ -72,6 +73,7 @@
|
|||||||
<file>imports/NeoChat/Settings/ThemeRadioButton.qml</file>
|
<file>imports/NeoChat/Settings/ThemeRadioButton.qml</file>
|
||||||
<file>imports/NeoChat/Settings/ColorScheme.qml</file>
|
<file>imports/NeoChat/Settings/ColorScheme.qml</file>
|
||||||
<file>imports/NeoChat/Settings/GeneralSettingsPage.qml</file>
|
<file>imports/NeoChat/Settings/GeneralSettingsPage.qml</file>
|
||||||
|
<file>imports/NeoChat/Settings/Emoticons.qml</file>
|
||||||
<file>imports/NeoChat/Settings/AppearanceSettingsPage.qml</file>
|
<file>imports/NeoChat/Settings/AppearanceSettingsPage.qml</file>
|
||||||
<file>imports/NeoChat/Settings/qmldir</file>
|
<file>imports/NeoChat/Settings/qmldir</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ add_executable(neochat
|
|||||||
controller.cpp
|
controller.cpp
|
||||||
actionshandler.cpp
|
actionshandler.cpp
|
||||||
emojimodel.cpp
|
emojimodel.cpp
|
||||||
|
customemojimodel.cpp
|
||||||
|
customemojimodel+network.cpp
|
||||||
clipboard.cpp
|
clipboard.cpp
|
||||||
matriximageprovider.cpp
|
matriximageprovider.cpp
|
||||||
messageeventmodel.cpp
|
messageeventmodel.cpp
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "roommanager.h"
|
#include "roommanager.h"
|
||||||
|
#include "customemojimodel.h"
|
||||||
|
|
||||||
ActionsHandler::ActionsHandler(QObject *parent)
|
ActionsHandler::ActionsHandler(QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
@@ -72,7 +73,7 @@ void ActionsHandler::postEdit(const QString &text)
|
|||||||
if (!match.hasMatch()) {
|
if (!match.hasMatch()) {
|
||||||
// should not happen but still make sure to send the message normally
|
// should not happen but still make sure to send the message normally
|
||||||
// just in case.
|
// just in case.
|
||||||
postMessage(text, QString(), QString(), QString(), QVariantMap());
|
postMessage(text, QString(), QString(), QString(), QVariantMap(), nullptr);
|
||||||
}
|
}
|
||||||
const QString regex = match.captured(1);
|
const QString regex = match.captured(1);
|
||||||
const QString replacement = match.captured(2);
|
const QString replacement = match.captured(2);
|
||||||
@@ -93,11 +94,19 @@ void ActionsHandler::postMessage(const QString &text,
|
|||||||
const QString &attachementPath,
|
const QString &attachementPath,
|
||||||
const QString &replyEventId,
|
const QString &replyEventId,
|
||||||
const QString &editEventId,
|
const QString &editEventId,
|
||||||
const QVariantMap &usernames)
|
const QVariantMap &usernames,
|
||||||
|
CustomEmojiModel* cem)
|
||||||
{
|
{
|
||||||
QString rawText = text;
|
QString rawText = text;
|
||||||
QString cleanedText = text;
|
QString cleanedText = text;
|
||||||
|
|
||||||
|
auto preprocess = [cem](const QString& it) -> QString {
|
||||||
|
if (cem == nullptr) {
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
return cem->preprocessText(it);
|
||||||
|
};
|
||||||
|
|
||||||
for (auto it = usernames.constBegin(); it != usernames.constEnd(); it++) {
|
for (auto it = usernames.constBegin(); it != usernames.constEnd(); it++) {
|
||||||
cleanedText = cleanedText.replace(it.key(), "[" + it.key() + "](https://matrix.to/#/" + it.value().toString() + ")");
|
cleanedText = cleanedText.replace(it.key(), "[" + it.key() + "](https://matrix.to/#/" + it.value().toString() + ")");
|
||||||
}
|
}
|
||||||
@@ -163,7 +172,7 @@ void ActionsHandler::postMessage(const QString &text,
|
|||||||
for (int i = 0; i < cleanedText.length(); i++) {
|
for (int i = 0; i < cleanedText.length(); i++) {
|
||||||
rainbowText = rainbowText % QStringLiteral("<font color='") % rainbowColors.at(i % rainbowColors.length()) % "'>" % cleanedText.at(i) % "</font>";
|
rainbowText = rainbowText % QStringLiteral("<font color='") % rainbowColors.at(i % rainbowColors.length()) % "'>" % cleanedText.at(i) % "</font>";
|
||||||
}
|
}
|
||||||
m_room->postHtmlMessage(cleanedText, rainbowText, RoomMessageEvent::MsgType::Notice, replyEventId, editEventId);
|
m_room->postHtmlMessage(cleanedText, preprocess(rainbowText), RoomMessageEvent::MsgType::Notice, replyEventId, editEventId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,7 +182,7 @@ void ActionsHandler::postMessage(const QString &text,
|
|||||||
for (int i = 0; i < cleanedText.length(); i++) {
|
for (int i = 0; i < cleanedText.length(); i++) {
|
||||||
rainbowText = rainbowText % QStringLiteral("<font color='") % rainbowColors.at(i % rainbowColors.length()) % "'>" % cleanedText.at(i) % "</font>";
|
rainbowText = rainbowText % QStringLiteral("<font color='") % rainbowColors.at(i % rainbowColors.length()) % "'>" % cleanedText.at(i) % "</font>";
|
||||||
}
|
}
|
||||||
m_room->postHtmlMessage(cleanedText, rainbowText, messageEventType, replyEventId, editEventId);
|
m_room->postHtmlMessage(cleanedText, preprocess(rainbowText), messageEventType, replyEventId, editEventId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,5 +289,5 @@ void ActionsHandler::postMessage(const QString &text,
|
|||||||
cleanedText = cleanedText.remove(0, noticePrefix.length());
|
cleanedText = cleanedText.remove(0, noticePrefix.length());
|
||||||
messageEventType = RoomMessageEvent::MsgType::Notice;
|
messageEventType = RoomMessageEvent::MsgType::Notice;
|
||||||
}
|
}
|
||||||
m_room->postMessage(rawText, cleanedText, messageEventType, replyEventId, editEventId);
|
m_room->postMessage(rawText, preprocess(m_room->preprocessText(cleanedText)), messageEventType, replyEventId, editEventId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
|
|
||||||
|
class CustomEmojiModel;
|
||||||
|
|
||||||
/// \brief Handles user interactions with NeoChat (joining room, creating room,
|
/// \brief Handles user interactions with NeoChat (joining room, creating room,
|
||||||
/// sending message). Account management is handled by Controller.
|
/// sending message). Account management is handled by Controller.
|
||||||
class ActionsHandler : public QObject
|
class ActionsHandler : public QObject
|
||||||
@@ -56,7 +58,7 @@ public Q_SLOTS:
|
|||||||
///
|
///
|
||||||
/// This also interprets commands if any.
|
/// This also interprets commands if any.
|
||||||
void
|
void
|
||||||
postMessage(const QString &text, const QString &attachementPath, const QString &replyEventId, const QString &editEventId, const QVariantMap &usernames);
|
postMessage(const QString &text, const QString &attachementPath, const QString &replyEventId, const QString &editEventId, const QVariantMap &usernames, CustomEmojiModel* cem);
|
||||||
|
|
||||||
/// \brief Send edit instructions (.e.g s/hallo/hello/)
|
/// \brief Send edit instructions (.e.g s/hallo/hello/)
|
||||||
///
|
///
|
||||||
|
|||||||
75
src/customemojimodel+network.cpp
Normal file
75
src/customemojimodel+network.cpp
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2021 Carson Black <uhhadd@gmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <csapi/account-data.h>
|
||||||
|
#include <csapi/profile.h>
|
||||||
|
#include <csapi/content-repo.h>
|
||||||
|
|
||||||
|
#include "customemojimodel_p.h"
|
||||||
|
|
||||||
|
#ifdef QUOTIENT_07
|
||||||
|
#define running isJobPending
|
||||||
|
#else
|
||||||
|
#define running isJobRunning
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void CustomEmojiModel::fetchEmojies()
|
||||||
|
{
|
||||||
|
if (d->conn == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& data = d->conn->accountData("im.ponies.user_emotes");
|
||||||
|
if (data == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto emojies = data->contentJson()["emoticons"].toObject();
|
||||||
|
|
||||||
|
beginResetModel();
|
||||||
|
d->emojies.clear();
|
||||||
|
|
||||||
|
for (const auto& emoji : emojies.keys()) {
|
||||||
|
const auto& data = emojies[emoji];
|
||||||
|
|
||||||
|
d->emojies << CustomEmoji {
|
||||||
|
.name = emoji,
|
||||||
|
.url = data["url"].toString(),
|
||||||
|
.regexp = QRegularExpression(QStringLiteral(R"((^|[^\\]))") + emoji)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CustomEmojiModel::addEmoji(const QString& name, const QUrl& location)
|
||||||
|
{
|
||||||
|
using namespace Quotient;
|
||||||
|
|
||||||
|
auto job = d->conn->uploadFile(location.toLocalFile());
|
||||||
|
|
||||||
|
if (running(job)) {
|
||||||
|
connect(job, &BaseJob::success, this, [this, name, job] {
|
||||||
|
const auto& data = d->conn->accountData("im.ponies.user_emotes");
|
||||||
|
auto json = data != nullptr ? data->contentJson() : QJsonObject();
|
||||||
|
auto emojiData = json["emoticons"].toObject();
|
||||||
|
emojiData[QStringLiteral(":%1:").arg(name)] = QJsonObject({
|
||||||
|
{QStringLiteral("url"), job->contentUri()}
|
||||||
|
});
|
||||||
|
json["emoticons"] = emojiData;
|
||||||
|
d->conn->setAccountData("im.ponies.user_emotes", json);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CustomEmojiModel::removeEmoji(const QString& name)
|
||||||
|
{
|
||||||
|
using namespace Quotient;
|
||||||
|
|
||||||
|
const auto& data = d->conn->accountData("im.ponies.user_emotes");
|
||||||
|
Q_ASSERT(data != nullptr); // something's screwed if we get here with a nullptr
|
||||||
|
auto json = data->contentJson();
|
||||||
|
auto emojiData = json["emoticons"].toObject();
|
||||||
|
emojiData.remove(name);
|
||||||
|
json["emoticons"] = emojiData;
|
||||||
|
d->conn->setAccountData("im.ponies.user_emotes", json);
|
||||||
|
}
|
||||||
113
src/customemojimodel.cpp
Normal file
113
src/customemojimodel.cpp
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2021 Carson Black <uhhadd@gmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "emojimodel.h"
|
||||||
|
#include "customemojimodel_p.h"
|
||||||
|
|
||||||
|
enum Roles {
|
||||||
|
Name,
|
||||||
|
ImageURL,
|
||||||
|
ModelData, // for emulating the regular emoji model's usage, otherwise the UI code would get too complicated
|
||||||
|
};
|
||||||
|
|
||||||
|
CustomEmojiModel::CustomEmojiModel(QObject* parent) : QAbstractListModel(parent), d(new Private)
|
||||||
|
{
|
||||||
|
connect(this, &CustomEmojiModel::connectionChanged, this, &CustomEmojiModel::fetchEmojies);
|
||||||
|
connect(this, &CustomEmojiModel::connectionChanged, this, [this]() {
|
||||||
|
if (!d->conn) return;
|
||||||
|
|
||||||
|
connect(d->conn, &Connection::accountDataChanged, this, [this](const QString& id) {
|
||||||
|
if (id != QStringLiteral("im.ponies.user_emotes")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fetchEmojies();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomEmojiModel::~CustomEmojiModel()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant CustomEmojiModel::data(const QModelIndex& idx, int role) const
|
||||||
|
{
|
||||||
|
const auto row = idx.row();
|
||||||
|
if (row >= d->emojies.length()) {
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
const auto& data = d->emojies[row];
|
||||||
|
|
||||||
|
switch (Roles(role)) {
|
||||||
|
case Roles::ModelData:
|
||||||
|
return QVariant::fromValue(Emoji(
|
||||||
|
QStringLiteral("image://mxc/") + data.url.mid(6),
|
||||||
|
data.name,
|
||||||
|
true
|
||||||
|
));
|
||||||
|
case Roles::Name:
|
||||||
|
return data.name;
|
||||||
|
case Roles::ImageURL:
|
||||||
|
return QUrl(QStringLiteral("image://mxc/") + data.url.mid(6));
|
||||||
|
}
|
||||||
|
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
int CustomEmojiModel::rowCount(const QModelIndex& parent) const
|
||||||
|
{
|
||||||
|
Q_UNUSED(parent)
|
||||||
|
|
||||||
|
return d->emojies.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash<int,QByteArray> CustomEmojiModel::roleNames() const
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
{ Name, "name" },
|
||||||
|
{ ImageURL, "imageURL" },
|
||||||
|
{ ModelData, "modelData" },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Connection* CustomEmojiModel::connection() const
|
||||||
|
{
|
||||||
|
return d->conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CustomEmojiModel::setConnection(Connection* it)
|
||||||
|
{
|
||||||
|
if (d->conn == it) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (d->conn != nullptr) {
|
||||||
|
disconnect(d->conn, nullptr, this, nullptr);
|
||||||
|
}
|
||||||
|
d->conn = it;
|
||||||
|
Q_EMIT connectionChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString CustomEmojiModel::preprocessText(const QString &it)
|
||||||
|
{
|
||||||
|
auto cp = it;
|
||||||
|
for (const auto& emoji : d->emojies) {
|
||||||
|
cp.replace(emoji.regexp, QStringLiteral(R"(<img data-mx-emoticon="" src="%1" alt="%2" title="%2" height="32" vertical-align="middle" />)").arg(emoji.url).arg(emoji.name));
|
||||||
|
}
|
||||||
|
return cp;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariantList CustomEmojiModel::filterModel(const QString &filter)
|
||||||
|
{
|
||||||
|
QVariantList results;
|
||||||
|
for (const auto& emoji : d->emojies) {
|
||||||
|
if (results.length() >= 10) break;
|
||||||
|
if (!emoji.name.contains(filter, Qt::CaseInsensitive)) continue;
|
||||||
|
|
||||||
|
results << QVariant::fromValue(Emoji(
|
||||||
|
QStringLiteral("image://mxc/") + emoji.url.mid(6),
|
||||||
|
emoji.name,
|
||||||
|
true
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
55
src/customemojimodel.h
Normal file
55
src/customemojimodel.h
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2021 Carson Black <uhhadd@gmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QAbstractListModel>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "connection.h"
|
||||||
|
|
||||||
|
using namespace Quotient;
|
||||||
|
|
||||||
|
class CustomEmojiModel : public QAbstractListModel
|
||||||
|
{
|
||||||
|
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
Q_PROPERTY(Connection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
// constructors
|
||||||
|
|
||||||
|
explicit CustomEmojiModel(QObject* parent = nullptr);
|
||||||
|
~CustomEmojiModel();
|
||||||
|
|
||||||
|
// model
|
||||||
|
|
||||||
|
QVariant data(const QModelIndex& idx, int role = Qt::DisplayRole) const override;
|
||||||
|
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||||
|
|
||||||
|
QHash<int,QByteArray> roleNames() const override;
|
||||||
|
|
||||||
|
// property setters
|
||||||
|
|
||||||
|
Connection* connection() const;
|
||||||
|
void setConnection(Connection* it);
|
||||||
|
Q_SIGNAL void connectionChanged();
|
||||||
|
|
||||||
|
// QML functions
|
||||||
|
|
||||||
|
Q_INVOKABLE QString preprocessText(const QString& it);
|
||||||
|
Q_INVOKABLE QVariantList filterModel(const QString &filter);
|
||||||
|
Q_INVOKABLE void addEmoji(const QString& name, const QUrl& location);
|
||||||
|
Q_INVOKABLE void removeEmoji(const QString& name);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
struct Private;
|
||||||
|
std::unique_ptr<Private> d;
|
||||||
|
|
||||||
|
void fetchEmojies();
|
||||||
|
|
||||||
|
};
|
||||||
19
src/customemojimodel_p.h
Normal file
19
src/customemojimodel_p.h
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2021 Carson Black <uhhadd@gmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "customemojimodel.h"
|
||||||
|
|
||||||
|
struct CustomEmoji
|
||||||
|
{
|
||||||
|
QString name; // with :semicolons:
|
||||||
|
QString url; // mxc://
|
||||||
|
QRegularExpression regexp;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CustomEmojiModel::Private
|
||||||
|
{
|
||||||
|
Connection* conn = nullptr;
|
||||||
|
QList<CustomEmoji> emojies;
|
||||||
|
};
|
||||||
@@ -10,9 +10,10 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
struct Emoji {
|
struct Emoji {
|
||||||
Emoji(QString u, QString s)
|
Emoji(QString u, QString s, bool isCustom = false)
|
||||||
: unicode(std::move(std::move(u)))
|
: unicode(std::move(std::move(u)))
|
||||||
, shortname(std::move(std::move(s)))
|
, shortname(std::move(std::move(s)))
|
||||||
|
, isCustom(isCustom)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
Emoji() = default;
|
Emoji() = default;
|
||||||
@@ -28,15 +29,18 @@ struct Emoji {
|
|||||||
{
|
{
|
||||||
arch >> object.unicode;
|
arch >> object.unicode;
|
||||||
arch >> object.shortname;
|
arch >> object.shortname;
|
||||||
|
object.isCustom = object.unicode.startsWith("image://");
|
||||||
return arch;
|
return arch;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString unicode;
|
QString unicode;
|
||||||
QString shortname;
|
QString shortname;
|
||||||
|
bool isCustom = false;
|
||||||
|
|
||||||
Q_GADGET
|
Q_GADGET
|
||||||
Q_PROPERTY(QString unicode MEMBER unicode)
|
Q_PROPERTY(QString unicode MEMBER unicode)
|
||||||
Q_PROPERTY(QString shortname MEMBER shortname)
|
Q_PROPERTY(QString shortname MEMBER shortname)
|
||||||
|
Q_PROPERTY(bool isCustom MEMBER isCustom)
|
||||||
};
|
};
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(Emoji)
|
Q_DECLARE_METATYPE(Emoji)
|
||||||
|
|||||||
@@ -58,6 +58,7 @@
|
|||||||
#include "userlistmodel.h"
|
#include "userlistmodel.h"
|
||||||
#include "webshortcutmodel.h"
|
#include "webshortcutmodel.h"
|
||||||
#include "spellcheckhighlighter.h"
|
#include "spellcheckhighlighter.h"
|
||||||
|
#include "customemojimodel.h"
|
||||||
#ifdef HAVE_COLORSCHEME
|
#ifdef HAVE_COLORSCHEME
|
||||||
#include "colorschemer.h"
|
#include "colorschemer.h"
|
||||||
#endif
|
#endif
|
||||||
@@ -163,6 +164,7 @@ int main(int argc, char *argv[])
|
|||||||
qmlRegisterType<RoomListModel>("org.kde.neochat", 1, 0, "RoomListModel");
|
qmlRegisterType<RoomListModel>("org.kde.neochat", 1, 0, "RoomListModel");
|
||||||
qmlRegisterType<KWebShortcutModel>("org.kde.neochat", 1, 0, "WebShortcutModel");
|
qmlRegisterType<KWebShortcutModel>("org.kde.neochat", 1, 0, "WebShortcutModel");
|
||||||
qmlRegisterType<UserListModel>("org.kde.neochat", 1, 0, "UserListModel");
|
qmlRegisterType<UserListModel>("org.kde.neochat", 1, 0, "UserListModel");
|
||||||
|
qmlRegisterType<CustomEmojiModel>("org.kde.neochat", 1, 0, "CustomEmojiModel");
|
||||||
qmlRegisterType<MessageEventModel>("org.kde.neochat", 1, 0, "MessageEventModel");
|
qmlRegisterType<MessageEventModel>("org.kde.neochat", 1, 0, "MessageEventModel");
|
||||||
qmlRegisterType<MessageFilterModel>("org.kde.neochat", 1, 0, "MessageFilterModel");
|
qmlRegisterType<MessageFilterModel>("org.kde.neochat", 1, 0, "MessageFilterModel");
|
||||||
qmlRegisterType<PublicRoomListModel>("org.kde.neochat", 1, 0, "PublicRoomListModel");
|
qmlRegisterType<PublicRoomListModel>("org.kde.neochat", 1, 0, "PublicRoomListModel");
|
||||||
|
|||||||
@@ -302,7 +302,7 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
|
|||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
return visit(
|
return visit(
|
||||||
evt,
|
evt,
|
||||||
[prettyPrint, removeReply](const RoomMessageEvent &e) {
|
[this, prettyPrint, removeReply](const RoomMessageEvent &e) {
|
||||||
using namespace MessageEventContent;
|
using namespace MessageEventContent;
|
||||||
|
|
||||||
// 1. prettyPrint/HTML
|
// 1. prettyPrint/HTML
|
||||||
@@ -314,6 +314,10 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
|
|||||||
htmlBody.replace(utils::userPillRegExp, R"(<b class="user-pill">\1</b>)");
|
htmlBody.replace(utils::userPillRegExp, R"(<b class="user-pill">\1</b>)");
|
||||||
htmlBody.replace(utils::strikethroughRegExp, "<s>\\1</s>");
|
htmlBody.replace(utils::strikethroughRegExp, "<s>\\1</s>");
|
||||||
|
|
||||||
|
auto url = connection()->homeserver();
|
||||||
|
auto base = url.scheme() + QStringLiteral("://") + url.host() + (url.port() != -1 ? ':'+QString::number(url.port()) : QString());
|
||||||
|
htmlBody.replace(utils::mxcImageRegExp, QStringLiteral(R"(<img \1 src="%1/_matrix/media/r0/download/\2/\3" \4 > )").arg(base));
|
||||||
|
|
||||||
return htmlBody;
|
return htmlBody;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -532,12 +536,14 @@ QString msgTypeToString(MessageEventType msgType)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString NeoChatRoom::preprocessText(const QString& text)
|
||||||
|
{
|
||||||
|
return markdownToHTML(text);
|
||||||
|
}
|
||||||
|
|
||||||
void NeoChatRoom::postMessage(const QString &rawText, const QString &text, MessageEventType type, const QString &replyEventId, const QString &relateToEventId)
|
void NeoChatRoom::postMessage(const QString &rawText, const QString &text, MessageEventType type, const QString &replyEventId, const QString &relateToEventId)
|
||||||
{
|
{
|
||||||
const auto html = markdownToHTML(text);
|
postHtmlMessage(rawText, text, type, replyEventId, relateToEventId);
|
||||||
QString cleanText(text);
|
|
||||||
cleanText.replace(QRegularExpression("\\[(.+)\\]\\(.+\\)"), "\\1");
|
|
||||||
postHtmlMessage(rawText, html, type, replyEventId, relateToEventId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NeoChatRoom::postHtmlMessage(const QString &text, const QString &html, MessageEventType type, const QString &replyEventId, const QString &relateToEventId)
|
void NeoChatRoom::postHtmlMessage(const QString &text, const QString &html, MessageEventType type, const QString &replyEventId, const QString &relateToEventId)
|
||||||
|
|||||||
@@ -138,6 +138,8 @@ public Q_SLOTS:
|
|||||||
void acceptInvitation();
|
void acceptInvitation();
|
||||||
void forget();
|
void forget();
|
||||||
void sendTypingNotification(bool isTyping);
|
void sendTypingNotification(bool isTyping);
|
||||||
|
QString preprocessText(const QString &text);
|
||||||
|
|
||||||
/// @param rawText The text as it was typed.
|
/// @param rawText The text as it was typed.
|
||||||
/// @param cleanedText The text with link to the users.
|
/// @param cleanedText The text with link to the users.
|
||||||
void postMessage(const QString &rawText,
|
void postMessage(const QString &rawText,
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
|
|||||||
std::unique_ptr<KNotificationReplyAction> replyAction(new KNotificationReplyAction(i18n("Reply")));
|
std::unique_ptr<KNotificationReplyAction> replyAction(new KNotificationReplyAction(i18n("Reply")));
|
||||||
replyAction->setPlaceholderText(i18n("Reply..."));
|
replyAction->setPlaceholderText(i18n("Reply..."));
|
||||||
QObject::connect(replyAction.get(), &KNotificationReplyAction::replied, [room, replyEventId](const QString &text) {
|
QObject::connect(replyAction.get(), &KNotificationReplyAction::replied, [room, replyEventId](const QString &text) {
|
||||||
room->postMessage(text, text, RoomMessageEvent::MsgType::Text, replyEventId, QString());
|
room->postMessage(text, room->preprocessText(text), RoomMessageEvent::MsgType::Text, replyEventId, QString());
|
||||||
});
|
});
|
||||||
notification->setReplyAction(std::move(replyAction));
|
notification->setReplyAction(std::move(replyAction));
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -22,4 +22,5 @@ static const QRegularExpression removeRichReplyRegex{"<mx-reply>.*?</mx-reply>",
|
|||||||
static const QRegularExpression codePillRegExp{"<pre><code[^>]*>(.*?)</code></pre>", QRegularExpression::DotMatchesEverythingOption};
|
static const QRegularExpression codePillRegExp{"<pre><code[^>]*>(.*?)</code></pre>", QRegularExpression::DotMatchesEverythingOption};
|
||||||
static const QRegularExpression userPillRegExp{"(<a href=\"https://matrix.to/#/@.*?:.*?\">.*?</a>)", QRegularExpression::DotMatchesEverythingOption};
|
static const QRegularExpression userPillRegExp{"(<a href=\"https://matrix.to/#/@.*?:.*?\">.*?</a>)", QRegularExpression::DotMatchesEverythingOption};
|
||||||
static const QRegularExpression strikethroughRegExp{"<del>(.*?)</del>", QRegularExpression::DotMatchesEverythingOption};
|
static const QRegularExpression strikethroughRegExp{"<del>(.*?)</del>", QRegularExpression::DotMatchesEverythingOption};
|
||||||
|
static const QRegularExpression mxcImageRegExp{R"AAA(<img(.*?)src="mxc:\/\/(.*?)\/(.*?)"(.*?)>)AAA"};
|
||||||
} // namespace utils
|
} // namespace utils
|
||||||
|
|||||||
Reference in New Issue
Block a user