Compare commits
73 Commits
work/locat
...
work/slawe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4732d41242 | ||
|
|
516b1cff88 | ||
|
|
6438977964 | ||
|
|
d750263d39 | ||
|
|
3ed952db9e | ||
|
|
f8040a1bf6 | ||
|
|
d83b31fd86 | ||
|
|
e0dbb657f6 | ||
|
|
a807cc6143 | ||
|
|
fe064c0ef8 | ||
|
|
6cc773426f | ||
|
|
db94408ba6 | ||
|
|
333bd3cdb9 | ||
|
|
9a0d82eb31 | ||
|
|
0990c0507c | ||
|
|
9f76ce22c1 | ||
|
|
6acd6075ff | ||
|
|
57b6f00d8e | ||
|
|
280c9327cb | ||
|
|
05bcbb695f | ||
|
|
552f4e8f13 | ||
|
|
1223c5348d | ||
|
|
3ccff4f337 | ||
|
|
a67f3334ea | ||
|
|
52dafbb6c8 | ||
|
|
4341cc437d | ||
|
|
7bb7dd7bbb | ||
|
|
b4090d9671 | ||
|
|
3d2bcce99a | ||
|
|
f3a04635cf | ||
|
|
419cb07557 | ||
|
|
57fccaa076 | ||
|
|
4bf65339f8 | ||
|
|
e5f2e209a2 | ||
|
|
be3b5cdb8a | ||
|
|
abd56baa51 | ||
|
|
ff27a1d0bb | ||
|
|
8af2d4d273 | ||
|
|
f64c8e28da | ||
|
|
4570d6350b | ||
|
|
6dd51a35c5 | ||
|
|
2470990d75 | ||
|
|
1a87e605d6 | ||
|
|
e995740790 | ||
|
|
0fb8b740a4 | ||
|
|
5087161e4b | ||
|
|
918e9e492a | ||
|
|
f10805dddb | ||
|
|
55e4d03dfe | ||
|
|
9f9086b4b0 | ||
|
|
e00ce79d26 | ||
|
|
a13b2e6bd2 | ||
|
|
cefe5acdaa | ||
|
|
1438aea965 | ||
|
|
1b60e24c64 | ||
|
|
4f1c8f6f35 | ||
|
|
882945260a | ||
|
|
3677088104 | ||
|
|
8f141cd88d | ||
|
|
0d1b35b610 | ||
|
|
87c20bf03c | ||
|
|
77479ca22d | ||
|
|
34ad743e98 | ||
|
|
493e27622f | ||
|
|
b1b6c7ceed | ||
|
|
d0b1610a9f | ||
|
|
dcf520a7a9 | ||
|
|
a0ae8b28b2 | ||
|
|
78a6179a11 | ||
|
|
9c91557d8f | ||
|
|
fb24ffd20d | ||
|
|
0c985a0af1 | ||
|
|
787dc5ab66 |
@@ -28,17 +28,6 @@
|
|||||||
"buildsystem": "cmake-ninja",
|
"buildsystem": "cmake-ninja",
|
||||||
"sources": [ { "type": "git", "url": "https://invent.kde.org/libraries/kirigami-addons.git" } ]
|
"sources": [ { "type": "git", "url": "https://invent.kde.org/libraries/kirigami-addons.git" } ]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "kquickcharts",
|
|
||||||
"buildsystem": "cmake-ninja",
|
|
||||||
"sources": [
|
|
||||||
{
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://invent.kde.org/frameworks/kquickcharts.git",
|
|
||||||
"branch": "kf5"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "kquickimageeditor",
|
"name": "kquickimageeditor",
|
||||||
"buildsystem": "cmake-ninja",
|
"buildsystem": "cmake-ninja",
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -10,3 +10,4 @@ kate.project.ctags.*
|
|||||||
*.user
|
*.user
|
||||||
.flatpak-builder/
|
.flatpak-builder/
|
||||||
.idea/
|
.idea/
|
||||||
|
cmake-build-*
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
include:
|
include:
|
||||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/reuse-lint.yml
|
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/reuse-lint.yml
|
||||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android.yml
|
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android.yml
|
||||||
# - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android-qt6.yml
|
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android-qt6.yml
|
||||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux.yml
|
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux.yml
|
||||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-qt6.yml
|
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-qt6.yml
|
||||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows.yml
|
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows.yml
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
#include "texthandler.h"
|
#include "texthandler.h"
|
||||||
|
|
||||||
#include <connection.h>
|
#include <qnamespace.h>
|
||||||
#include <quotient_common.h>
|
#include <quotient_common.h>
|
||||||
#include <syncdata.h>
|
#include <syncdata.h>
|
||||||
|
|
||||||
@@ -49,7 +49,8 @@ private Q_SLOTS:
|
|||||||
void receiveStripReply();
|
void receiveStripReply();
|
||||||
void receivePlainTextIn();
|
void receivePlainTextIn();
|
||||||
|
|
||||||
void recieveRichInPlainOut();
|
void receiveRichInPlainOut_data();
|
||||||
|
void receiveRichInPlainOut();
|
||||||
void receivePlainStripHtml();
|
void receivePlainStripHtml();
|
||||||
void receivePlainStripMarkup();
|
void receivePlainStripMarkup();
|
||||||
void receiveStripNewlines();
|
void receiveStripNewlines();
|
||||||
@@ -59,6 +60,9 @@ private Q_SLOTS:
|
|||||||
void receiveRichtextIn();
|
void receiveRichtextIn();
|
||||||
void receiveRichMxcUrl();
|
void receiveRichMxcUrl();
|
||||||
void receiveRichPlainUrl();
|
void receiveRichPlainUrl();
|
||||||
|
void receiveRichEmote();
|
||||||
|
void receiveRichEdited_data();
|
||||||
|
void receiveRichEdited();
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef QUOTIENT_07
|
#ifdef QUOTIENT_07
|
||||||
@@ -146,7 +150,44 @@ void TextHandlerTest::initTestCase()
|
|||||||
"sender": "@example:example.org",
|
"sender": "@example:example.org",
|
||||||
"type": "m.room.message",
|
"type": "m.room.message",
|
||||||
"unsigned": {
|
"unsigned": {
|
||||||
"age": 1235
|
"age": 1232
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"body": "/me This is an emote.",
|
||||||
|
"format": "org.matrix.custom.html",
|
||||||
|
"formatted_body": "This is an emote.",
|
||||||
|
"msgtype": "m.emote"
|
||||||
|
},
|
||||||
|
"event_id": "$153273582443PhrSn:example.org",
|
||||||
|
"origin_server_ts": 1532735824654,
|
||||||
|
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||||
|
"sender": "@example:example.org",
|
||||||
|
"type": "m.room.message",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 1231
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"body": "tested",
|
||||||
|
"msgtype": "m.text"
|
||||||
|
},
|
||||||
|
"event_id": "$zrCiBxBnqqTn0Z5FY78qSZAszno_w8nJJXzfBULG-3E",
|
||||||
|
"origin_server_ts": 1680948575928,
|
||||||
|
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||||
|
"sender": "@example:example.org",
|
||||||
|
"type": "m.room.message",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 1747776,
|
||||||
|
"m.relations": {
|
||||||
|
"m.replace": {
|
||||||
|
"event_id": "$UX0PlpyI7vYO32iHMuuYEP7ECMh4sX3XLGiB2SwM4mQ",
|
||||||
|
"origin_server_ts": 1680948580992,
|
||||||
|
"sender": "@example:example.org"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -309,15 +350,24 @@ void TextHandlerTest::receiveStripReply()
|
|||||||
QCOMPARE(testTextHandler.handleRecievePlainText(), testOutputString);
|
QCOMPARE(testTextHandler.handleRecievePlainText(), testOutputString);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextHandlerTest::recieveRichInPlainOut()
|
void TextHandlerTest::receiveRichInPlainOut_data()
|
||||||
{
|
{
|
||||||
const QString testInputString = QStringLiteral("a & b");
|
QTest::addColumn<QString>("testInputString");
|
||||||
const QString testOutputString = QStringLiteral("a & b");
|
QTest::addColumn<QString>("testOutputString");
|
||||||
|
|
||||||
|
QTest::newRow("ampersand") << QStringLiteral("a & b") << QStringLiteral("a & b");
|
||||||
|
QTest::newRow("quote") << QStringLiteral(""a and b"") << QStringLiteral("\"a and b\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextHandlerTest::receiveRichInPlainOut()
|
||||||
|
{
|
||||||
|
QFETCH(QString, testInputString);
|
||||||
|
QFETCH(QString, testOutputString);
|
||||||
|
|
||||||
TextHandler testTextHandler;
|
TextHandler testTextHandler;
|
||||||
testTextHandler.setData(testInputString);
|
testTextHandler.setData(testInputString);
|
||||||
|
|
||||||
QCOMPARE(testTextHandler.handleRecievePlainText(), testOutputString);
|
QCOMPARE(testTextHandler.handleRecievePlainText(Qt::RichText), testOutputString);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextHandlerTest::receivePlainTextIn()
|
void TextHandlerTest::receivePlainTextIn()
|
||||||
@@ -347,6 +397,9 @@ void TextHandlerTest::receiveStripNewlines()
|
|||||||
const QString testInputStringRich = QStringLiteral("Test<br>many<br />new<br>lines.");
|
const QString testInputStringRich = QStringLiteral("Test<br>many<br />new<br>lines.");
|
||||||
const QString testOutputString = QStringLiteral("Test many new lines.");
|
const QString testOutputString = QStringLiteral("Test many new lines.");
|
||||||
|
|
||||||
|
const QString testInputStringPlain2 = QStringLiteral("* List\n* Items");
|
||||||
|
const QString testOutputString2 = QStringLiteral("List Items");
|
||||||
|
|
||||||
TextHandler testTextHandler;
|
TextHandler testTextHandler;
|
||||||
testTextHandler.setData(testInputStringPlain);
|
testTextHandler.setData(testInputStringPlain);
|
||||||
|
|
||||||
@@ -354,9 +407,11 @@ void TextHandlerTest::receiveStripNewlines()
|
|||||||
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::PlainText, nullptr, nullptr, true), testOutputString);
|
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::PlainText, nullptr, nullptr, true), testOutputString);
|
||||||
|
|
||||||
testTextHandler.setData(testInputStringRich);
|
testTextHandler.setData(testInputStringRich);
|
||||||
|
|
||||||
QCOMPARE(testTextHandler.handleRecievePlainText(Qt::RichText, true), testOutputString);
|
QCOMPARE(testTextHandler.handleRecievePlainText(Qt::RichText, true), testOutputString);
|
||||||
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText, nullptr, nullptr, true), testOutputString);
|
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText, nullptr, nullptr, true), testOutputString);
|
||||||
|
|
||||||
|
testTextHandler.setData(testInputStringPlain2);
|
||||||
|
QCOMPARE(testTextHandler.handleRecievePlainText(Qt::RichText, true), testOutputString2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -434,7 +489,7 @@ void TextHandlerTest::receiveRichMxcUrl()
|
|||||||
TextHandler testTextHandler;
|
TextHandler testTextHandler;
|
||||||
testTextHandler.setData(testInputString);
|
testTextHandler.setData(testInputString);
|
||||||
|
|
||||||
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText, room, room->messageEvents().back().get()), testOutputString);
|
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText, room, room->messageEvents().at(0).get()), testOutputString);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -493,5 +548,44 @@ void TextHandlerTest::receiveRichPlainUrl()
|
|||||||
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringMxId);
|
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringMxId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test that user pill is add to an emote message.
|
||||||
|
// N.B. The second message in the test timeline is marked as an emote.
|
||||||
|
void TextHandlerTest::receiveRichEmote()
|
||||||
|
{
|
||||||
|
auto event = room->messageEvents().at(1).get();
|
||||||
|
auto author = static_cast<NeoChatUser *>(room->user(event->senderId()));
|
||||||
|
const QString testInputString = QStringLiteral("This is an emote.");
|
||||||
|
const QString testOutputString = QStringLiteral("* <a href=\"https://matrix.to/#/@example:example.org\" style=\"color:") + author->color().name()
|
||||||
|
+ QStringLiteral("\">@example:example.org</a> This is an emote.");
|
||||||
|
|
||||||
|
TextHandler testTextHandler;
|
||||||
|
testTextHandler.setData(testInputString);
|
||||||
|
|
||||||
|
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText, room, event), testOutputString);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextHandlerTest::receiveRichEdited_data()
|
||||||
|
{
|
||||||
|
QTest::addColumn<QString>("testInputString");
|
||||||
|
QTest::addColumn<QString>("testOutputString");
|
||||||
|
|
||||||
|
QTest::newRow("basic") << QStringLiteral("Edited") << QStringLiteral("Edited <span style=\"color:#000000\">(edited)</span>");
|
||||||
|
QTest::newRow("multiple paragraphs") << QStringLiteral("<p>Edited</p>\n<p>Edited</p>")
|
||||||
|
<< QStringLiteral("<p>Edited</p>\n<p>Edited <span style=\"color:#000000\">(edited)</span></p>");
|
||||||
|
QTest::newRow("blockquote") << QStringLiteral("<blockquote>Edited</blockquote>")
|
||||||
|
<< QStringLiteral("<blockquote>Edited</blockquote><p> <span style=\"color:#000000\">(edited)</span></p>");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextHandlerTest::receiveRichEdited()
|
||||||
|
{
|
||||||
|
QFETCH(QString, testInputString);
|
||||||
|
QFETCH(QString, testOutputString);
|
||||||
|
|
||||||
|
TextHandler testTextHandler;
|
||||||
|
testTextHandler.setData(testInputString);
|
||||||
|
|
||||||
|
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText, room, room->messageEvents().at(2).get()), testOutputString);
|
||||||
|
}
|
||||||
|
|
||||||
QTEST_MAIN(TextHandlerTest)
|
QTEST_MAIN(TextHandlerTest)
|
||||||
#include "texthandlertest.moc"
|
#include "texthandlertest.moc"
|
||||||
|
|||||||
@@ -231,6 +231,7 @@
|
|||||||
<content_attribute id="social-chat">intense</content_attribute>
|
<content_attribute id="social-chat">intense</content_attribute>
|
||||||
</content_rating>
|
</content_rating>
|
||||||
<releases>
|
<releases>
|
||||||
|
<release version="23.04.0" date="2023-04-20"/>
|
||||||
<release version="23.01" date="2023-01-30">
|
<release version="23.01" date="2023-01-30">
|
||||||
<url>https://plasma-mobile.org/2023/01/30/january-blog-post/</url>
|
<url>https://plasma-mobile.org/2023/01/30/january-blog-post/</url>
|
||||||
<description>
|
<description>
|
||||||
|
|||||||
1090
po/ar/neochat.po
1090
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
1173
po/az/neochat.po
1173
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
1080
po/ca/neochat.po
1080
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1075
po/cs/neochat.po
1075
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
1076
po/da/neochat.po
1076
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
1324
po/de/neochat.po
1324
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
1053
po/el/neochat.po
1053
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
1097
po/en_GB/neochat.po
1097
po/en_GB/neochat.po
File diff suppressed because it is too large
Load Diff
1080
po/es/neochat.po
1080
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
927
po/eu/neochat.po
927
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
1151
po/fi/neochat.po
1151
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
1080
po/fr/neochat.po
1080
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
1175
po/hu/neochat.po
1175
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
1294
po/ia/neochat.po
1294
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
1053
po/id/neochat.po
1053
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
1097
po/ie/neochat.po
1097
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
1146
po/it/neochat.po
1146
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
1051
po/ja/neochat.po
1051
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
1078
po/ka/neochat.po
1078
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
1159
po/ko/neochat.po
1159
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
1052
po/lt/neochat.po
1052
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
1078
po/nl/neochat.po
1078
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
136
po/nn/neochat.po
136
po/nn/neochat.po
@@ -6,7 +6,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: neochat\n"
|
"Project-Id-Version: neochat\n"
|
||||||
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
|
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
|
||||||
"POT-Creation-Date: 2023-03-20 00:50+0000\n"
|
"POT-Creation-Date: 2023-03-31 00:47+0000\n"
|
||||||
"PO-Revision-Date: 2021-12-15 11:17+0100\n"
|
"PO-Revision-Date: 2021-12-15 11:17+0100\n"
|
||||||
"Last-Translator: Øystein Steffensen-Alværvik <oysteins.omsetting@protonmail."
|
"Last-Translator: Øystein Steffensen-Alværvik <oysteins.omsetting@protonmail."
|
||||||
"com>\n"
|
"com>\n"
|
||||||
@@ -631,50 +631,50 @@ msgctxt "'Custom' is a category of emoji"
|
|||||||
msgid "Custom"
|
msgid "Custom"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/models/messageeventmodel.cpp:359 src/models/searchmodel.cpp:156
|
#: src/models/messageeventmodel.cpp:362 src/models/searchmodel.cpp:156
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "Today"
|
msgid "Today"
|
||||||
msgstr "I dag"
|
msgstr "I dag"
|
||||||
|
|
||||||
#: src/models/messageeventmodel.cpp:362 src/models/searchmodel.cpp:159
|
#: src/models/messageeventmodel.cpp:365 src/models/searchmodel.cpp:159
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "Yesterday"
|
msgid "Yesterday"
|
||||||
msgstr "I går"
|
msgstr "I går"
|
||||||
|
|
||||||
#: src/models/messageeventmodel.cpp:365 src/models/searchmodel.cpp:162
|
#: src/models/messageeventmodel.cpp:368 src/models/searchmodel.cpp:162
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "The day before yesterday"
|
msgid "The day before yesterday"
|
||||||
msgstr "I forgårs"
|
msgstr "I forgårs"
|
||||||
|
|
||||||
#: src/models/messageeventmodel.cpp:467 src/models/messageeventmodel.cpp:476
|
#: src/models/messageeventmodel.cpp:470 src/models/messageeventmodel.cpp:479
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "<i>[This message was deleted]</i>"
|
msgid "<i>[This message was deleted]</i>"
|
||||||
msgstr "<i>[Denne meldinga er sletta]</i>"
|
msgstr "<i>[Denne meldinga er sletta]</i>"
|
||||||
|
|
||||||
#: src/models/messageeventmodel.cpp:468
|
#: src/models/messageeventmodel.cpp:471
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "<i>[This message was deleted: %1]</i>"
|
msgid "<i>[This message was deleted: %1]</i>"
|
||||||
msgstr "<i>[Denne meldinga er sletta: %1]</i>"
|
msgstr "<i>[Denne meldinga er sletta: %1]</i>"
|
||||||
|
|
||||||
# Eller «SENSURERT»?
|
# Eller «SENSURERT»?
|
||||||
#: src/models/messageeventmodel.cpp:563
|
#: src/models/messageeventmodel.cpp:568
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "[REDACTED]"
|
msgid "[REDACTED]"
|
||||||
msgstr "[TREKT TILBAKE]"
|
msgstr "[TREKT TILBAKE]"
|
||||||
|
|
||||||
#: src/models/messageeventmodel.cpp:563
|
#: src/models/messageeventmodel.cpp:568
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "[REDACTED: %1]"
|
msgid "[REDACTED: %1]"
|
||||||
msgstr "[TREKT TILBAKE: %1]"
|
msgstr "[TREKT TILBAKE: %1]"
|
||||||
|
|
||||||
#: src/models/messageeventmodel.cpp:848
|
#: src/models/messageeventmodel.cpp:882
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "1 user: "
|
msgid "1 user: "
|
||||||
msgid_plural "%1 users: "
|
msgid_plural "%1 users: "
|
||||||
msgstr[0] ""
|
msgstr[0] ""
|
||||||
msgstr[1] ""
|
msgstr[1] ""
|
||||||
|
|
||||||
#: src/models/messageeventmodel.cpp:855
|
#: src/models/messageeventmodel.cpp:889
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgctxt "list separator"
|
msgctxt "list separator"
|
||||||
msgid ", "
|
msgid ", "
|
||||||
@@ -726,7 +726,7 @@ msgctxt "Optional reason for an invitation"
|
|||||||
msgid ": %1"
|
msgid ": %1"
|
||||||
msgstr ": %1"
|
msgstr ": %1"
|
||||||
|
|
||||||
#: src/neochatroom.cpp:533 src/neochatroom.cpp:674
|
#: src/neochatroom.cpp:533 src/neochatroom.cpp:678
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "joined the room (repeated)"
|
msgid "joined the room (repeated)"
|
||||||
msgstr "kom inn i rommet (på nytt)"
|
msgstr "kom inn i rommet (på nytt)"
|
||||||
@@ -736,7 +736,7 @@ msgstr "kom inn i rommet (på nytt)"
|
|||||||
msgid "invited %1 to the room"
|
msgid "invited %1 to the room"
|
||||||
msgstr "inviterte %1 til rommet"
|
msgstr "inviterte %1 til rommet"
|
||||||
|
|
||||||
#: src/neochatroom.cpp:535 src/neochatroom.cpp:676
|
#: src/neochatroom.cpp:535 src/neochatroom.cpp:680
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "joined the room"
|
msgid "joined the room"
|
||||||
msgstr "kom inn i rommet"
|
msgstr "kom inn i rommet"
|
||||||
@@ -746,7 +746,7 @@ msgstr "kom inn i rommet"
|
|||||||
msgid ": %1"
|
msgid ": %1"
|
||||||
msgstr ": %1"
|
msgstr ": %1"
|
||||||
|
|
||||||
#: src/neochatroom.cpp:546 src/neochatroom.cpp:684
|
#: src/neochatroom.cpp:546 src/neochatroom.cpp:688
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgctxt "their refers to a singular user"
|
msgctxt "their refers to a singular user"
|
||||||
msgid "cleared their display name"
|
msgid "cleared their display name"
|
||||||
@@ -758,29 +758,29 @@ msgctxt "their refers to a singular user"
|
|||||||
msgid "changed their display name to %1"
|
msgid "changed their display name to %1"
|
||||||
msgstr "endra visingsnamn til %1"
|
msgstr "endra visingsnamn til %1"
|
||||||
|
|
||||||
#: src/neochatroom.cpp:553 src/neochatroom.cpp:691
|
#: src/neochatroom.cpp:553 src/neochatroom.cpp:695
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid " and "
|
msgid " and "
|
||||||
msgstr " og "
|
msgstr " og "
|
||||||
|
|
||||||
#: src/neochatroom.cpp:556 src/neochatroom.cpp:694
|
#: src/neochatroom.cpp:556 src/neochatroom.cpp:698
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgctxt "their refers to a singular user"
|
msgctxt "their refers to a singular user"
|
||||||
msgid "cleared their avatar"
|
msgid "cleared their avatar"
|
||||||
msgstr "fjerna avataren"
|
msgstr "fjerna avataren"
|
||||||
|
|
||||||
#: src/neochatroom.cpp:562 src/neochatroom.cpp:700
|
#: src/neochatroom.cpp:562 src/neochatroom.cpp:704
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "set an avatar"
|
msgid "set an avatar"
|
||||||
msgstr "valde ein avatar"
|
msgstr "valde ein avatar"
|
||||||
|
|
||||||
#: src/neochatroom.cpp:564 src/neochatroom.cpp:702
|
#: src/neochatroom.cpp:564 src/neochatroom.cpp:706
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgctxt "their refers to a singular user"
|
msgctxt "their refers to a singular user"
|
||||||
msgid "updated their avatar"
|
msgid "updated their avatar"
|
||||||
msgstr "bytte avataren sin"
|
msgstr "bytte avataren sin"
|
||||||
|
|
||||||
#: src/neochatroom.cpp:568 src/neochatroom.cpp:706
|
#: src/neochatroom.cpp:568 src/neochatroom.cpp:710
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgctxt "<user> changed nothing"
|
msgctxt "<user> changed nothing"
|
||||||
msgid "changed nothing"
|
msgid "changed nothing"
|
||||||
@@ -791,7 +791,7 @@ msgstr ""
|
|||||||
msgid "withdrew %1's invitation"
|
msgid "withdrew %1's invitation"
|
||||||
msgstr "trekte tilbake invitasjonen til %1"
|
msgstr "trekte tilbake invitasjonen til %1"
|
||||||
|
|
||||||
#: src/neochatroom.cpp:574 src/neochatroom.cpp:712
|
#: src/neochatroom.cpp:574 src/neochatroom.cpp:716
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "rejected the invitation"
|
msgid "rejected the invitation"
|
||||||
msgstr "avviste invitasjonen"
|
msgstr "avviste invitasjonen"
|
||||||
@@ -801,7 +801,7 @@ msgstr "avviste invitasjonen"
|
|||||||
msgid "unbanned %1"
|
msgid "unbanned %1"
|
||||||
msgstr "oppheva utestenging av %1"
|
msgstr "oppheva utestenging av %1"
|
||||||
|
|
||||||
#: src/neochatroom.cpp:578 src/neochatroom.cpp:716
|
#: src/neochatroom.cpp:578 src/neochatroom.cpp:720
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "self-unbanned"
|
msgid "self-unbanned"
|
||||||
msgstr "utestengde seg sjølv"
|
msgstr "utestengde seg sjølv"
|
||||||
@@ -811,7 +811,7 @@ msgstr "utestengde seg sjølv"
|
|||||||
msgid "has put %1 out of the room: %2"
|
msgid "has put %1 out of the room: %2"
|
||||||
msgstr "fjerna %1 frå rommet: %2"
|
msgstr "fjerna %1 frå rommet: %2"
|
||||||
|
|
||||||
#: src/neochatroom.cpp:582 src/neochatroom.cpp:718
|
#: src/neochatroom.cpp:582 src/neochatroom.cpp:722
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "left the room"
|
msgid "left the room"
|
||||||
msgstr "forlét rommet"
|
msgstr "forlét rommet"
|
||||||
@@ -826,12 +826,12 @@ msgstr ""
|
|||||||
msgid "banned %1 from the room: %2"
|
msgid "banned %1 from the room: %2"
|
||||||
msgstr "utestengde %1 frå rommet: %2"
|
msgstr "utestengde %1 frå rommet: %2"
|
||||||
|
|
||||||
#: src/neochatroom.cpp:591 src/neochatroom.cpp:723
|
#: src/neochatroom.cpp:591 src/neochatroom.cpp:727
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "self-banned from the room"
|
msgid "self-banned from the room"
|
||||||
msgstr "utestengde seg sjølv frå rommet"
|
msgstr "utestengde seg sjølv frå rommet"
|
||||||
|
|
||||||
#: src/neochatroom.cpp:595 src/neochatroom.cpp:726
|
#: src/neochatroom.cpp:595 src/neochatroom.cpp:730
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "requested an invite"
|
msgid "requested an invite"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -841,12 +841,12 @@ msgstr ""
|
|||||||
msgid "requested an invite with reason: %1"
|
msgid "requested an invite with reason: %1"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/neochatroom.cpp:599 src/neochatroom.cpp:730
|
#: src/neochatroom.cpp:599 src/neochatroom.cpp:734
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "made something unknown"
|
msgid "made something unknown"
|
||||||
msgstr "gjorde noko ukjend"
|
msgstr "gjorde noko ukjend"
|
||||||
|
|
||||||
#: src/neochatroom.cpp:602 src/neochatroom.cpp:733
|
#: src/neochatroom.cpp:602 src/neochatroom.cpp:737
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "cleared the room main alias"
|
msgid "cleared the room main alias"
|
||||||
msgstr "fjerna hovudaliaset til rommet"
|
msgstr "fjerna hovudaliaset til rommet"
|
||||||
@@ -856,7 +856,7 @@ msgstr "fjerna hovudaliaset til rommet"
|
|||||||
msgid "set the room main alias to: %1"
|
msgid "set the room main alias to: %1"
|
||||||
msgstr "bytte hovudalias for rommet til: %1"
|
msgstr "bytte hovudalias for rommet til: %1"
|
||||||
|
|
||||||
#: src/neochatroom.cpp:605 src/neochatroom.cpp:736
|
#: src/neochatroom.cpp:605 src/neochatroom.cpp:740
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "cleared the room name"
|
msgid "cleared the room name"
|
||||||
msgstr "fjerna romnamnet"
|
msgstr "fjerna romnamnet"
|
||||||
@@ -866,177 +866,177 @@ msgstr "fjerna romnamnet"
|
|||||||
msgid "set the room name to: %1"
|
msgid "set the room name to: %1"
|
||||||
msgstr "bytte romnamnet til: %1"
|
msgstr "bytte romnamnet til: %1"
|
||||||
|
|
||||||
#: src/neochatroom.cpp:608 src/neochatroom.cpp:739
|
#: src/neochatroom.cpp:608 src/neochatroom.cpp:743
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "cleared the topic"
|
msgid "cleared the topic"
|
||||||
msgstr "tømte emnefeltet"
|
msgstr "tømte emnefeltet"
|
||||||
|
|
||||||
#: src/neochatroom.cpp:608
|
#: src/neochatroom.cpp:609
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "set the topic to: %1"
|
msgid "set the topic to: %1"
|
||||||
msgstr "bytte emnet til: %1"
|
msgstr "bytte emnet til: %1"
|
||||||
|
|
||||||
#: src/neochatroom.cpp:611 src/neochatroom.cpp:742
|
#: src/neochatroom.cpp:615 src/neochatroom.cpp:746
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "changed the room avatar"
|
msgid "changed the room avatar"
|
||||||
msgstr "bytte ut romavataren"
|
msgstr "bytte ut romavataren"
|
||||||
|
|
||||||
#: src/neochatroom.cpp:614 src/neochatroom.cpp:745
|
#: src/neochatroom.cpp:618 src/neochatroom.cpp:749
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "activated End-to-End Encryption"
|
msgid "activated End-to-End Encryption"
|
||||||
msgstr "slå på ende-til-ende-kryptering"
|
msgstr "slå på ende-til-ende-kryptering"
|
||||||
|
|
||||||
#: src/neochatroom.cpp:617
|
#: src/neochatroom.cpp:621
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "upgraded the room to version %1"
|
msgid "upgraded the room to version %1"
|
||||||
msgstr "oppgraderte rommet til versjon %1"
|
msgstr "oppgraderte rommet til versjon %1"
|
||||||
|
|
||||||
#: src/neochatroom.cpp:618
|
#: src/neochatroom.cpp:622
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "created the room, version %1"
|
msgid "created the room, version %1"
|
||||||
msgstr "oppretta rommet, versjon %1"
|
msgstr "oppretta rommet, versjon %1"
|
||||||
|
|
||||||
#: src/neochatroom.cpp:621 src/neochatroom.cpp:751
|
#: src/neochatroom.cpp:625 src/neochatroom.cpp:755
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgctxt "'power level' means permission level"
|
msgctxt "'power level' means permission level"
|
||||||
msgid "changed the power levels for this room"
|
msgid "changed the power levels for this room"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/neochatroom.cpp:625 src/neochatroom.cpp:755
|
#: src/neochatroom.cpp:629 src/neochatroom.cpp:759
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "changed the server access control lists for this room"
|
msgid "changed the server access control lists for this room"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/neochatroom.cpp:629
|
#: src/neochatroom.cpp:633
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgctxt "[User] added <name> widget"
|
msgctxt "[User] added <name> widget"
|
||||||
msgid "added %1 widget"
|
msgid "added %1 widget"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/neochatroom.cpp:632
|
#: src/neochatroom.cpp:636
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgctxt "[User] removed <name> widget"
|
msgctxt "[User] removed <name> widget"
|
||||||
msgid "removed %1 widget"
|
msgid "removed %1 widget"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/neochatroom.cpp:634
|
#: src/neochatroom.cpp:638
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgctxt "[User] configured <name> widget"
|
msgctxt "[User] configured <name> widget"
|
||||||
msgid "configured %1 widget"
|
msgid "configured %1 widget"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/neochatroom.cpp:636
|
#: src/neochatroom.cpp:640
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "updated %1 state"
|
msgid "updated %1 state"
|
||||||
msgstr "oppdaterte %1-tilstand"
|
msgstr "oppdaterte %1-tilstand"
|
||||||
|
|
||||||
#: src/neochatroom.cpp:637
|
#: src/neochatroom.cpp:641
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "updated %1 state for %2"
|
msgid "updated %1 state for %2"
|
||||||
msgstr "oppdaterte %1-tilstand for %2"
|
msgstr "oppdaterte %1-tilstand for %2"
|
||||||
|
|
||||||
#: src/neochatroom.cpp:644 src/neochatroom.cpp:773
|
#: src/neochatroom.cpp:648 src/neochatroom.cpp:777
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "Unknown event"
|
msgid "Unknown event"
|
||||||
msgstr "Ukjend hending"
|
msgstr "Ukjend hending"
|
||||||
|
|
||||||
#: src/neochatroom.cpp:657
|
#: src/neochatroom.cpp:661
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "sent a message"
|
msgid "sent a message"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/neochatroom.cpp:661
|
#: src/neochatroom.cpp:665
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "sent a sticker"
|
msgid "sent a sticker"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/neochatroom.cpp:667
|
#: src/neochatroom.cpp:671
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "reinvited someone to the room"
|
msgid "reinvited someone to the room"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/neochatroom.cpp:676
|
#: src/neochatroom.cpp:680
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "invited someone to the room"
|
msgid "invited someone to the room"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/neochatroom.cpp:686
|
#: src/neochatroom.cpp:690
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgctxt "their refers to a singular user"
|
msgctxt "their refers to a singular user"
|
||||||
msgid "changed their display name"
|
msgid "changed their display name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/neochatroom.cpp:712
|
#: src/neochatroom.cpp:716
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "withdrew a user's invitation"
|
msgid "withdrew a user's invitation"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/neochatroom.cpp:716
|
#: src/neochatroom.cpp:720
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "unbanned a user"
|
msgid "unbanned a user"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/neochatroom.cpp:718
|
#: src/neochatroom.cpp:722
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "put a user out of the room"
|
msgid "put a user out of the room"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/neochatroom.cpp:721
|
#: src/neochatroom.cpp:725
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "banned a user from the room"
|
msgid "banned a user from the room"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/neochatroom.cpp:733
|
#: src/neochatroom.cpp:737
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "set the room main alias"
|
msgid "set the room main alias"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/neochatroom.cpp:736
|
#: src/neochatroom.cpp:740
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "set the room name"
|
msgid "set the room name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/neochatroom.cpp:739
|
#: src/neochatroom.cpp:743
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "set the topic"
|
msgid "set the topic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/neochatroom.cpp:748
|
#: src/neochatroom.cpp:752
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "upgraded the room version"
|
msgid "upgraded the room version"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/neochatroom.cpp:748
|
#: src/neochatroom.cpp:752
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "created the room"
|
msgid "created the room"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/neochatroom.cpp:759
|
#: src/neochatroom.cpp:763
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "added a widget"
|
msgid "added a widget"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/neochatroom.cpp:762
|
#: src/neochatroom.cpp:766
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "removed a widget"
|
msgid "removed a widget"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/neochatroom.cpp:764
|
#: src/neochatroom.cpp:768
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "configured a widget"
|
msgid "configured a widget"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/neochatroom.cpp:766
|
#: src/neochatroom.cpp:770
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "updated the state"
|
msgid "updated the state"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/neochatroom.cpp:770
|
#: src/neochatroom.cpp:774
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "started a poll"
|
msgid "started a poll"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/neochatroom.cpp:1689 src/neochatroom.cpp:1690
|
#: src/neochatroom.cpp:1693 src/neochatroom.cpp:1694
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "Report sent successfully."
|
msgid "Report sent successfully."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1189,7 +1189,7 @@ msgstr ""
|
|||||||
msgid "No emojis"
|
msgid "No emojis"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/qml/Component/ExploreComponent.qml:19 src/qml/Page/RoomListPage.qml:224
|
#: src/qml/Component/ExploreComponent.qml:19 src/qml/Page/RoomListPage.qml:221
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "Explore rooms"
|
msgid "Explore rooms"
|
||||||
msgstr "Utforsk rom"
|
msgstr "Utforsk rom"
|
||||||
@@ -2504,38 +2504,38 @@ msgstr ""
|
|||||||
msgid "Joined"
|
msgid "Joined"
|
||||||
msgstr "Vart med"
|
msgstr "Vart med"
|
||||||
|
|
||||||
#: src/qml/Page/RoomListPage.qml:58
|
#: src/qml/Page/RoomListPage.qml:55
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
msgid "Show All Rooms"
|
msgid "Show All Rooms"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/qml/Page/RoomListPage.qml:221
|
#: src/qml/Page/RoomListPage.qml:218
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "No rooms found"
|
msgid "No rooms found"
|
||||||
msgstr "Fann ingen rom"
|
msgstr "Fann ingen rom"
|
||||||
|
|
||||||
#: src/qml/Page/RoomListPage.qml:221
|
#: src/qml/Page/RoomListPage.qml:218
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "Join some rooms to get started"
|
msgid "Join some rooms to get started"
|
||||||
msgstr "Start ved å verta med i nokre rom"
|
msgstr "Start ved å verta med i nokre rom"
|
||||||
|
|
||||||
#: src/qml/Page/RoomListPage.qml:224
|
#: src/qml/Page/RoomListPage.qml:221
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "Search in room directory"
|
msgid "Search in room directory"
|
||||||
msgstr "Søk i romkatalogen"
|
msgstr "Søk i romkatalogen"
|
||||||
|
|
||||||
#: src/qml/Page/RoomListPage.qml:322 src/qml/Page/RoomListPage.qml:374
|
#: src/qml/Page/RoomListPage.qml:319 src/qml/Page/RoomListPage.qml:371
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "No Name"
|
msgid "No Name"
|
||||||
msgstr "Namnlaus"
|
msgstr "Namnlaus"
|
||||||
|
|
||||||
#: src/qml/Page/RoomListPage.qml:389
|
#: src/qml/Page/RoomListPage.qml:386
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "Muted room"
|
msgid "Muted room"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/qml/Page/RoomListPage.qml:416
|
#: src/qml/Page/RoomListPage.qml:413
|
||||||
#, kde-format
|
#, kde-format
|
||||||
msgid "Configure room"
|
msgid "Configure room"
|
||||||
msgstr "Set opp rommet"
|
msgstr "Set opp rommet"
|
||||||
|
|||||||
1159
po/pa/neochat.po
1159
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
1135
po/pl/neochat.po
1135
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
1078
po/pt/neochat.po
1078
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
1173
po/pt_BR/neochat.po
1173
po/pt_BR/neochat.po
File diff suppressed because it is too large
Load Diff
1055
po/ru/neochat.po
1055
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
1175
po/sk/neochat.po
1175
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
1078
po/sl/neochat.po
1078
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
1161
po/sv/neochat.po
1161
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
1081
po/ta/neochat.po
1081
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
1084
po/tok/neochat.po
1084
po/tok/neochat.po
File diff suppressed because it is too large
Load Diff
1084
po/tr/neochat.po
1084
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
1078
po/uk/neochat.po
1078
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
1078
po/zh_CN/neochat.po
1078
po/zh_CN/neochat.po
File diff suppressed because it is too large
Load Diff
1051
po/zh_TW/neochat.po
1051
po/zh_TW/neochat.po
File diff suppressed because it is too large
Load Diff
@@ -178,6 +178,7 @@ if(ANDROID)
|
|||||||
"home"
|
"home"
|
||||||
"preferences-desktop-notification"
|
"preferences-desktop-notification"
|
||||||
"computer-symbolic"
|
"computer-symbolic"
|
||||||
|
"gps"
|
||||||
)
|
)
|
||||||
else()
|
else()
|
||||||
target_link_libraries(neochat PUBLIC Qt::Widgets KF${QT_MAJOR_VERSION}::KIOWidgets)
|
target_link_libraries(neochat PUBLIC Qt::Widgets KF${QT_MAJOR_VERSION}::KIOWidgets)
|
||||||
|
|||||||
@@ -138,12 +138,13 @@ int ChatDocumentHandler::completionStartIndex() const
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
|
#if !defined(Q_OS_ANDROID) && QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
|
||||||
const long long cursor = cursorPosition();
|
const long long cursor = cursorPosition();
|
||||||
#else
|
#else
|
||||||
const auto cursor = cursorPosition();
|
const auto cursor = cursorPosition();
|
||||||
#endif
|
#endif
|
||||||
const auto &text = getText();
|
const auto &text = getText();
|
||||||
|
|
||||||
auto start = std::min(cursor, text.size()) - 1;
|
auto start = std::min(cursor, text.size()) - 1;
|
||||||
while (start > -1) {
|
while (start > -1) {
|
||||||
if (text.at(start) == QLatin1Char(' ')) {
|
if (text.at(start) == QLatin1Char(' ')) {
|
||||||
@@ -324,3 +325,35 @@ void ChatDocumentHandler::pushMention(const Mention mention) const
|
|||||||
m_room->mentions()->push_back(mention);
|
m_room->mentions()->push_back(mention);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QColor ChatDocumentHandler::mentionColor() const
|
||||||
|
{
|
||||||
|
return m_mentionColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatDocumentHandler::setMentionColor(const QColor &color)
|
||||||
|
{
|
||||||
|
if (m_mentionColor == color) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_mentionColor = color;
|
||||||
|
m_highlighter->mentionFormat.setForeground(m_mentionColor);
|
||||||
|
m_highlighter->rehighlight();
|
||||||
|
Q_EMIT mentionColorChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
QColor ChatDocumentHandler::errorColor() const
|
||||||
|
{
|
||||||
|
return m_errorColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatDocumentHandler::setErrorColor(const QColor &color)
|
||||||
|
{
|
||||||
|
if (m_errorColor == color) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_errorColor = color;
|
||||||
|
m_highlighter->errorFormat.setForeground(m_errorColor);
|
||||||
|
m_highlighter->rehighlight();
|
||||||
|
Q_EMIT errorColorChanged();
|
||||||
|
}
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ class ChatDocumentHandler : public QObject
|
|||||||
|
|
||||||
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
||||||
|
|
||||||
|
Q_PROPERTY(QColor mentionColor READ mentionColor WRITE setMentionColor NOTIFY mentionColorChanged);
|
||||||
|
Q_PROPERTY(QColor errorColor READ errorColor WRITE setErrorColor NOTIFY errorColorChanged);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ChatDocumentHandler(QObject *parent = nullptr);
|
explicit ChatDocumentHandler(QObject *parent = nullptr);
|
||||||
|
|
||||||
@@ -60,6 +63,13 @@ public:
|
|||||||
|
|
||||||
void updateCompletions();
|
void updateCompletions();
|
||||||
CompletionModel *completionModel() const;
|
CompletionModel *completionModel() const;
|
||||||
|
|
||||||
|
[[nodiscard]] QColor mentionColor() const;
|
||||||
|
void setMentionColor(const QColor &color);
|
||||||
|
|
||||||
|
[[nodiscard]] QColor errorColor() const;
|
||||||
|
void setErrorColor(const QColor &color);
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void isEditChanged();
|
void isEditChanged();
|
||||||
void documentChanged();
|
void documentChanged();
|
||||||
@@ -68,6 +78,8 @@ Q_SIGNALS:
|
|||||||
void completionModelChanged();
|
void completionModelChanged();
|
||||||
void selectionStartChanged();
|
void selectionStartChanged();
|
||||||
void selectionEndChanged();
|
void selectionEndChanged();
|
||||||
|
void errorColorChanged();
|
||||||
|
void mentionColorChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int completionStartIndex() const;
|
int completionStartIndex() const;
|
||||||
@@ -79,6 +91,9 @@ private:
|
|||||||
NeoChatRoom *m_room = nullptr;
|
NeoChatRoom *m_room = nullptr;
|
||||||
bool completionVisible = false;
|
bool completionVisible = false;
|
||||||
|
|
||||||
|
QColor m_mentionColor;
|
||||||
|
QColor m_errorColor;
|
||||||
|
|
||||||
int m_cursorPosition;
|
int m_cursorPosition;
|
||||||
int m_selectionStart;
|
int m_selectionStart;
|
||||||
int m_selectionEnd;
|
int m_selectionEnd;
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
#include "neochatuser.h"
|
|
||||||
#include "roommanager.h"
|
#include "roommanager.h"
|
||||||
#include <events/roommemberevent.h>
|
#include <events/roommemberevent.h>
|
||||||
#include <events/roompowerlevelsevent.h>
|
#include <events/roompowerlevelsevent.h>
|
||||||
@@ -19,6 +18,41 @@ QStringList rainbowColors{"#ff2b00", "#ff5500", "#ff8000", "#ffaa00", "#ffd500",
|
|||||||
"#00ff2b", "#00ff55", "#00ff80", "#00ffaa", "#00ffd5", "#00ffff", "#00d4ff", "#00aaff", "#007fff", "#0055ff", "#002bff", "#0000ff",
|
"#00ff2b", "#00ff55", "#00ff80", "#00ffaa", "#00ffd5", "#00ffff", "#00d4ff", "#00aaff", "#007fff", "#0055ff", "#002bff", "#0000ff",
|
||||||
"#2a00ff", "#5500ff", "#7f00ff", "#aa00ff", "#d400ff", "#ff00ff", "#ff00d4", "#ff00aa", "#ff0080", "#ff0055", "#ff002b", "#ff0000"};
|
"#2a00ff", "#5500ff", "#7f00ff", "#aa00ff", "#d400ff", "#ff00ff", "#ff00d4", "#ff00aa", "#ff0080", "#ff0055", "#ff002b", "#ff0000"};
|
||||||
|
|
||||||
|
auto leaveRoomLambda = [](const QString &text, NeoChatRoom *room) {
|
||||||
|
if (text.isEmpty()) {
|
||||||
|
Q_EMIT room->showMessage(NeoChatRoom::Info, i18n("Leaving this room."));
|
||||||
|
room->connection()->leaveRoom(room);
|
||||||
|
} else {
|
||||||
|
QRegularExpression roomRegex(QStringLiteral(R"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"));
|
||||||
|
auto regexMatch = roomRegex.match(text);
|
||||||
|
if (!regexMatch.hasMatch()) {
|
||||||
|
Q_EMIT room->showMessage(NeoChatRoom::Error,
|
||||||
|
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
auto leaving = room->connection()->room(text);
|
||||||
|
if (!leaving) {
|
||||||
|
leaving = room->connection()->roomByAlias(text);
|
||||||
|
}
|
||||||
|
if (leaving) {
|
||||||
|
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Leaving room <roomname>.", "Leaving room %1.", text));
|
||||||
|
room->connection()->leaveRoom(leaving);
|
||||||
|
} else {
|
||||||
|
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Room <roomname> not found", "Room %1 not found.", text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QString();
|
||||||
|
};
|
||||||
|
|
||||||
|
auto roomNickLambda = [](const QString &text, NeoChatRoom *room) {
|
||||||
|
if (text.isEmpty()) {
|
||||||
|
Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("No new nickname provided, no changes will happen."));
|
||||||
|
} else {
|
||||||
|
room->connection()->user()->rename(text, room);
|
||||||
|
}
|
||||||
|
return QString();
|
||||||
|
};
|
||||||
|
|
||||||
QVector<ActionsModel::Action> actions{
|
QVector<ActionsModel::Action> actions{
|
||||||
Action{
|
Action{
|
||||||
QStringLiteral("shrug"),
|
QStringLiteral("shrug"),
|
||||||
@@ -268,31 +302,7 @@ QVector<ActionsModel::Action> actions{
|
|||||||
},
|
},
|
||||||
Action{
|
Action{
|
||||||
QStringLiteral("part"),
|
QStringLiteral("part"),
|
||||||
[](const QString &text, NeoChatRoom *room) {
|
leaveRoomLambda,
|
||||||
if (text.isEmpty()) {
|
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18n("Leaving this room."));
|
|
||||||
room->connection()->leaveRoom(room);
|
|
||||||
} else {
|
|
||||||
QRegularExpression roomRegex(QStringLiteral(R"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"));
|
|
||||||
auto regexMatch = roomRegex.match(text);
|
|
||||||
if (!regexMatch.hasMatch()) {
|
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Error,
|
|
||||||
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
auto leaving = room->connection()->room(text);
|
|
||||||
if (!leaving) {
|
|
||||||
leaving = room->connection()->roomByAlias(text);
|
|
||||||
}
|
|
||||||
if (leaving) {
|
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Leaving room <roomname>.", "Leaving room %1.", text));
|
|
||||||
room->connection()->leaveRoom(leaving);
|
|
||||||
} else {
|
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Room <roomname> not found", "Room %1 not found.", text));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return QString();
|
|
||||||
},
|
|
||||||
false,
|
false,
|
||||||
std::nullopt,
|
std::nullopt,
|
||||||
kli18n("[<room alias or id>]"),
|
kli18n("[<room alias or id>]"),
|
||||||
@@ -300,31 +310,7 @@ QVector<ActionsModel::Action> actions{
|
|||||||
},
|
},
|
||||||
Action{
|
Action{
|
||||||
QStringLiteral("leave"),
|
QStringLiteral("leave"),
|
||||||
[](const QString &text, NeoChatRoom *room) {
|
leaveRoomLambda,
|
||||||
if (text.isEmpty()) {
|
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18n("Leaving this room."));
|
|
||||||
room->connection()->leaveRoom(room);
|
|
||||||
} else {
|
|
||||||
QRegularExpression roomRegex(QStringLiteral(R"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"));
|
|
||||||
auto regexMatch = roomRegex.match(text);
|
|
||||||
if (!regexMatch.hasMatch()) {
|
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Error,
|
|
||||||
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
auto leaving = room->connection()->room(text);
|
|
||||||
if (!leaving) {
|
|
||||||
leaving = room->connection()->roomByAlias(text);
|
|
||||||
}
|
|
||||||
if (leaving) {
|
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Leaving room <roomname>.", "Leaving room %1.", text));
|
|
||||||
room->connection()->leaveRoom(leaving);
|
|
||||||
} else {
|
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Room <roomname> not found", "Room %1 not found.", text));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return QString();
|
|
||||||
},
|
|
||||||
false,
|
false,
|
||||||
std::nullopt,
|
std::nullopt,
|
||||||
kli18n("[<room alias or id>]"),
|
kli18n("[<room alias or id>]"),
|
||||||
@@ -347,14 +333,15 @@ QVector<ActionsModel::Action> actions{
|
|||||||
},
|
},
|
||||||
Action{
|
Action{
|
||||||
QStringLiteral("roomnick"),
|
QStringLiteral("roomnick"),
|
||||||
[](const QString &text, NeoChatRoom *room) {
|
roomNickLambda,
|
||||||
if (text.isEmpty()) {
|
false,
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("No new nickname provided, no changes will happen."));
|
std::nullopt,
|
||||||
} else {
|
kli18n("<display name>"),
|
||||||
room->connection()->user()->rename(text, room);
|
kli18n("Changes your display name in this room"),
|
||||||
}
|
},
|
||||||
return QString();
|
Action{
|
||||||
},
|
QStringLiteral("myroomnick"),
|
||||||
|
roomNickLambda,
|
||||||
false,
|
false,
|
||||||
std::nullopt,
|
std::nullopt,
|
||||||
kli18n("<display name>"),
|
kli18n("<display name>"),
|
||||||
|
|||||||
@@ -9,20 +9,42 @@
|
|||||||
|
|
||||||
class NeoChatRoom;
|
class NeoChatRoom;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class ActionsModel
|
||||||
|
*
|
||||||
|
* This class defines a model for chat actions.
|
||||||
|
*
|
||||||
|
* @note A chat action is a message starting with /, resulting in something other
|
||||||
|
* than a normal message being sent (e.g. /me, /join).
|
||||||
|
*/
|
||||||
class ActionsModel : public QAbstractListModel
|
class ActionsModel : public QAbstractListModel
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Definition of an action.
|
||||||
|
*/
|
||||||
struct Action {
|
struct Action {
|
||||||
// The prefix, without '/' and space after the word
|
QString prefix; /**< The prefix, without '/' and space after the word. */
|
||||||
QString prefix;
|
/**
|
||||||
|
* @brief The function to execute when the action is triggered.
|
||||||
|
*/
|
||||||
std::function<QString(const QString &, NeoChatRoom *)> handle;
|
std::function<QString(const QString &, NeoChatRoom *)> handle;
|
||||||
// If this is true, this action transforms a message to a different message and it will be sent.
|
/**
|
||||||
// If this is false, this message does some action on the client and should not be sent as a message.
|
* @brief Whether the action is a message type action.
|
||||||
|
*
|
||||||
|
* If this is true, a message action will be sent. If this is false, this
|
||||||
|
* message does some action on the client and should not be sent as a message.
|
||||||
|
*/
|
||||||
bool messageAction;
|
bool messageAction;
|
||||||
// If this action changes the message type, this is the new message type. Otherwise it's nullopt
|
/**
|
||||||
|
* @brief The new message type of a message being sent.
|
||||||
|
*
|
||||||
|
* For a non-message action or a message action that outputs the same type
|
||||||
|
* as its input, it's nullopt.
|
||||||
|
*/
|
||||||
std::optional<Quotient::RoomMessageEvent::MsgType> messageType = std::nullopt;
|
std::optional<Quotient::RoomMessageEvent::MsgType> messageType = std::nullopt;
|
||||||
KLazyLocalizedString parameters;
|
KLazyLocalizedString parameters; /**< The input parameters expected by the action. */
|
||||||
KLazyLocalizedString description;
|
KLazyLocalizedString description; /**< The description of the action. */
|
||||||
};
|
};
|
||||||
static ActionsModel &instance()
|
static ActionsModel &instance()
|
||||||
{
|
{
|
||||||
@@ -30,18 +52,41 @@ public:
|
|||||||
return _instance;
|
return _instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Defines the model roles.
|
||||||
|
*/
|
||||||
enum Roles {
|
enum Roles {
|
||||||
Prefix = Qt::DisplayRole,
|
Prefix = Qt::DisplayRole, /**< The prefix, without '/' and space after the word. */
|
||||||
Description,
|
Description, /**< The description of the action. */
|
||||||
CompletionType,
|
CompletionType, /**< The completion type (always "action" for this model). */
|
||||||
Parameters,
|
Parameters, /**< The input parameters expected by the action. */
|
||||||
};
|
};
|
||||||
Q_ENUM(Roles);
|
Q_ENUM(Roles);
|
||||||
|
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
/**
|
||||||
|
* @brief Get the given role value at the given index.
|
||||||
|
*
|
||||||
|
* @sa QAbstractItemModel::data
|
||||||
|
*/
|
||||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Number of rows in the model.
|
||||||
|
*
|
||||||
|
* @sa QAbstractItemModel::rowCount
|
||||||
|
*/
|
||||||
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a mapping from Role enum values to role names.
|
||||||
|
*
|
||||||
|
* @sa EventRoles, QAbstractItemModel::roleNames()
|
||||||
|
*/
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return a vector with all supported actions.
|
||||||
|
*/
|
||||||
QVector<Action> &allActions() const;
|
QVector<Action> &allActions() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -9,9 +9,9 @@
|
|||||||
bool CollapseStateProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
|
bool CollapseStateProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
|
||||||
{
|
{
|
||||||
Q_UNUSED(source_parent);
|
Q_UNUSED(source_parent);
|
||||||
return sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::EventTypeRole)
|
return sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::DelegateTypeRole)
|
||||||
!= MessageEventModel::DelegateType::State // If this is not a state, show it
|
!= MessageEventModel::DelegateType::State // If this is not a state, show it
|
||||||
|| sourceModel()->data(sourceModel()->index(source_row + 1, 0), MessageEventModel::EventTypeRole)
|
|| sourceModel()->data(sourceModel()->index(source_row + 1, 0), MessageEventModel::DelegateTypeRole)
|
||||||
!= MessageEventModel::DelegateType::State // If this is the first state in a block, show it. TODO hidden events?
|
!= MessageEventModel::DelegateType::State // If this is the first state in a block, show it. TODO hidden events?
|
||||||
|| sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::ShowSectionRole).toBool(); // If it's a new day, show it
|
|| sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::ShowSectionRole).toBool(); // If it's a new day, show it
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,7 @@ QString CollapseStateProxyModel::aggregateEventToString(int sourceRow) const
|
|||||||
if (!uniqueAuthors.contains(nextAuthor)) {
|
if (!uniqueAuthors.contains(nextAuthor)) {
|
||||||
uniqueAuthors.append(nextAuthor);
|
uniqueAuthors.append(nextAuthor);
|
||||||
}
|
}
|
||||||
if (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::EventTypeRole)
|
if (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole)
|
||||||
!= MessageEventModel::DelegateType::State // If it's not a state event
|
!= MessageEventModel::DelegateType::State // If it's not a state event
|
||||||
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||||
) {
|
) {
|
||||||
@@ -105,7 +105,7 @@ QVariantList CollapseStateProxyModel::stateEventsList(int sourceRow) const
|
|||||||
{"text", sourceModel()->data(sourceModel()->index(i, 0), Qt::DisplayRole).toString()},
|
{"text", sourceModel()->data(sourceModel()->index(i, 0), Qt::DisplayRole).toString()},
|
||||||
};
|
};
|
||||||
stateEvents.append(nextState);
|
stateEvents.append(nextState);
|
||||||
if (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::EventTypeRole)
|
if (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole)
|
||||||
!= MessageEventModel::DelegateType::State // If it's not a state event
|
!= MessageEventModel::DelegateType::State // If it's not a state event
|
||||||
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||||
) {
|
) {
|
||||||
@@ -123,7 +123,7 @@ QVariantList CollapseStateProxyModel::authorList(int sourceRow) const
|
|||||||
if (!uniqueAuthors.contains(nextAvatar)) {
|
if (!uniqueAuthors.contains(nextAvatar)) {
|
||||||
uniqueAuthors.append(nextAvatar);
|
uniqueAuthors.append(nextAvatar);
|
||||||
}
|
}
|
||||||
if (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::EventTypeRole)
|
if (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole)
|
||||||
!= MessageEventModel::DelegateType::State // If it's not a state event
|
!= MessageEventModel::DelegateType::State // If it's not a state event
|
||||||
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -6,26 +6,58 @@
|
|||||||
#include "messageeventmodel.h"
|
#include "messageeventmodel.h"
|
||||||
#include <QSortFilterProxyModel>
|
#include <QSortFilterProxyModel>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class CollapseStateProxyModel
|
||||||
|
*
|
||||||
|
* This model aggregates multiple sequential state events into a single entry.
|
||||||
|
*
|
||||||
|
* Events are only aggregated if they happened on the same day.
|
||||||
|
*
|
||||||
|
* @sa MessageEventModel
|
||||||
|
*/
|
||||||
class CollapseStateProxyModel : public QSortFilterProxyModel
|
class CollapseStateProxyModel : public QSortFilterProxyModel
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Defines the model roles.
|
||||||
|
*/
|
||||||
enum Roles {
|
enum Roles {
|
||||||
AggregateDisplayRole = MessageEventModel::LastRole + 1,
|
AggregateDisplayRole = MessageEventModel::LastRole + 1, /**< Single line aggregation of all the state events. */
|
||||||
StateEventsRole,
|
StateEventsRole, /**< List of state events in the aggregated state. */
|
||||||
AuthorListRole,
|
AuthorListRole, /**< List of unique authors of the aggregated state event. */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether a row should be shown out or not.
|
||||||
|
*
|
||||||
|
* @sa QSortFilterProxyModel::filterAcceptsRow
|
||||||
|
*/
|
||||||
[[nodiscard]] bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
|
[[nodiscard]] bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
|
||||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
|
||||||
|
/**
|
||||||
|
* @brief Get the given role value at the given index.
|
||||||
|
*
|
||||||
|
* @sa QSortFilterProxyModel::data
|
||||||
|
*/
|
||||||
[[nodiscard]] QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
|
[[nodiscard]] QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief QString aggregating the text of consecutive state events starting at row.
|
* @brief Returns a mapping from Role enum values to role names.
|
||||||
|
*
|
||||||
|
* @sa Roles, QAbstractProxyModel::roleNames()
|
||||||
|
*/
|
||||||
|
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* @brief Aggregation of the text of consecutive state events starting at row.
|
||||||
*
|
*
|
||||||
* If state events happen on different days they will be split into two aggregate
|
* If state events happen on different days they will be split into two aggregate
|
||||||
* events.
|
* events.
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] QString aggregateEventToString(int row) const;
|
[[nodiscard]] QString aggregateEventToString(int row) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Return a list of consecutive state events starting at row.
|
* @brief Return a list of consecutive state events starting at row.
|
||||||
*
|
*
|
||||||
@@ -33,6 +65,7 @@ public:
|
|||||||
* events.
|
* events.
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] QVariantList stateEventsList(int row) const;
|
[[nodiscard]] QVariantList stateEventsList(int row) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief List of unique authors for the aggregate state events starting at row.
|
* @brief List of unique authors for the aggregate state events starting at row.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
|
||||||
#include "actionsmodel.h"
|
#include "actionsmodel.h"
|
||||||
#include "chatdocumenthandler.h"
|
|
||||||
#include "completionproxymodel.h"
|
#include "completionproxymodel.h"
|
||||||
#include "customemojimodel.h"
|
#include "customemojimodel.h"
|
||||||
#include "emojimodel.h"
|
#include "emojimodel.h"
|
||||||
|
|||||||
@@ -13,41 +13,89 @@ class UserListModel;
|
|||||||
class NeoChatRoom;
|
class NeoChatRoom;
|
||||||
class RoomListModel;
|
class RoomListModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class CompletionModel
|
||||||
|
*
|
||||||
|
* This class defines the model for suggesting completions in chat text.
|
||||||
|
*
|
||||||
|
* This model is able to select the appropriate completion type for the input text
|
||||||
|
* and present a list of options that can be presented to the user.
|
||||||
|
*/
|
||||||
class CompletionModel : public QAbstractListModel
|
class CompletionModel : public QAbstractListModel
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The current text to search for completions.
|
||||||
|
*/
|
||||||
Q_PROPERTY(QString text READ text NOTIFY textChanged)
|
Q_PROPERTY(QString text READ text NOTIFY textChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The current room that the model is getting completions for.
|
||||||
|
*/
|
||||||
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The current type of completion being done on the entered text.
|
||||||
|
*
|
||||||
|
* @sa AutoCompletionType
|
||||||
|
*/
|
||||||
Q_PROPERTY(AutoCompletionType autoCompletionType READ autoCompletionType NOTIFY autoCompletionTypeChanged);
|
Q_PROPERTY(AutoCompletionType autoCompletionType READ autoCompletionType NOTIFY autoCompletionTypeChanged);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The RoomListModel to be used for room completions.
|
||||||
|
*/
|
||||||
Q_PROPERTY(RoomListModel *roomListModel READ roomListModel WRITE setRoomListModel NOTIFY roomListModelChanged);
|
Q_PROPERTY(RoomListModel *roomListModel READ roomListModel WRITE setRoomListModel NOTIFY roomListModelChanged);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Defines the different types of completion available.
|
||||||
|
*/
|
||||||
enum AutoCompletionType {
|
enum AutoCompletionType {
|
||||||
User,
|
User, /**< A user in the current room. */
|
||||||
Room,
|
Room, /**< A matrix room. */
|
||||||
Emoji,
|
Emoji, /**< An emoji. */
|
||||||
Command,
|
Command, /**< A / command. */
|
||||||
None,
|
None, /**< No available completion for the current text. */
|
||||||
};
|
};
|
||||||
Q_ENUM(AutoCompletionType)
|
Q_ENUM(AutoCompletionType)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Defines the model roles.
|
||||||
|
*/
|
||||||
enum Roles {
|
enum Roles {
|
||||||
Text = Qt::DisplayRole,
|
Text = Qt::DisplayRole, /**< The main text to show. */
|
||||||
Subtitle,
|
Subtitle, /**< The subtitle text to show. */
|
||||||
Icon,
|
Icon, /**< The icon to show. */
|
||||||
ReplacedText,
|
ReplacedText, /**< The text to replace the input text with for the completion. */
|
||||||
};
|
};
|
||||||
Q_ENUM(Roles);
|
Q_ENUM(Roles);
|
||||||
|
|
||||||
CompletionModel(QObject *parent = nullptr);
|
CompletionModel(QObject *parent = nullptr);
|
||||||
|
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
/**
|
||||||
|
* @brief Get the given role value at the given index.
|
||||||
|
*
|
||||||
|
* @sa QAbstractItemModel::data
|
||||||
|
*/
|
||||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Number of rows in the model.
|
||||||
|
*
|
||||||
|
* @sa QAbstractItemModel::rowCount
|
||||||
|
*/
|
||||||
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a mapping from Role enum values to role names.
|
||||||
|
*
|
||||||
|
* @sa EventRoles, QAbstractItemModel::roleNames()
|
||||||
|
*/
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
QString text() const;
|
QString text() const;
|
||||||
void setText(const QString &text, const QString &fullText);
|
void setText(const QString &text, const QString &fullText);
|
||||||
void updateCompletion();
|
|
||||||
|
|
||||||
NeoChatRoom *room() const;
|
NeoChatRoom *room() const;
|
||||||
void setRoom(NeoChatRoom *room);
|
void setRoom(NeoChatRoom *room);
|
||||||
@@ -56,6 +104,7 @@ public:
|
|||||||
void setRoomListModel(RoomListModel *roomListModel);
|
void setRoomListModel(RoomListModel *roomListModel);
|
||||||
|
|
||||||
AutoCompletionType autoCompletionType() const;
|
AutoCompletionType autoCompletionType() const;
|
||||||
|
void setAutoCompletionType(AutoCompletionType autoCompletionType);
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void textChanged();
|
void textChanged();
|
||||||
@@ -70,7 +119,7 @@ private:
|
|||||||
NeoChatRoom *m_room = nullptr;
|
NeoChatRoom *m_room = nullptr;
|
||||||
AutoCompletionType m_autoCompletionType = None;
|
AutoCompletionType m_autoCompletionType = None;
|
||||||
|
|
||||||
void setAutoCompletionType(AutoCompletionType autoCompletionType);
|
void updateCompletion();
|
||||||
|
|
||||||
UserListModel *m_userListModel;
|
UserListModel *m_userListModel;
|
||||||
RoomListModel *m_roomListModel;
|
RoomListModel *m_roomListModel;
|
||||||
|
|||||||
@@ -2,9 +2,8 @@
|
|||||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
|
||||||
#include "completionproxymodel.h"
|
#include "completionproxymodel.h"
|
||||||
#include <QDebug>
|
|
||||||
|
|
||||||
#include "neochatroom.h"
|
#include <QDebug>
|
||||||
|
|
||||||
bool CompletionProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
|
bool CompletionProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
|
||||||
{
|
{
|
||||||
@@ -44,7 +43,6 @@ int CompletionProxyModel::secondaryFilterRole() const
|
|||||||
void CompletionProxyModel::setSecondaryFilterRole(int role)
|
void CompletionProxyModel::setSecondaryFilterRole(int role)
|
||||||
{
|
{
|
||||||
m_secondaryFilterRole = role;
|
m_secondaryFilterRole = role;
|
||||||
Q_EMIT secondaryFilterRoleChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString CompletionProxyModel::filterText() const
|
QString CompletionProxyModel::filterText() const
|
||||||
@@ -55,7 +53,6 @@ QString CompletionProxyModel::filterText() const
|
|||||||
void CompletionProxyModel::setFilterText(const QString &filterText)
|
void CompletionProxyModel::setFilterText(const QString &filterText)
|
||||||
{
|
{
|
||||||
m_filterText = filterText;
|
m_filterText = filterText;
|
||||||
Q_EMIT filterTextChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CompletionProxyModel::setFullText(const QString &fullText)
|
void CompletionProxyModel::setFullText(const QString &fullText)
|
||||||
|
|||||||
@@ -5,28 +5,71 @@
|
|||||||
|
|
||||||
#include <QSortFilterProxyModel>
|
#include <QSortFilterProxyModel>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class CompletionProxyModel
|
||||||
|
*
|
||||||
|
* A filter model to sort and filter completion results.
|
||||||
|
*
|
||||||
|
* This model is designed to work with multiple source models depending upon the
|
||||||
|
* completion type.
|
||||||
|
*
|
||||||
|
* A model value will be shown if its primary or secondary role values start with
|
||||||
|
* the filter text. The exception is if the full text perfectly matches
|
||||||
|
* the primary filter role value in which case the completion ends (i.e. the filter
|
||||||
|
* will return no results).
|
||||||
|
*
|
||||||
|
* @note The filter is primarily design to work with strings, therefore make sure
|
||||||
|
* that the source model roles that are to be filtered are strings.
|
||||||
|
*/
|
||||||
class CompletionProxyModel : public QSortFilterProxyModel
|
class CompletionProxyModel : public QSortFilterProxyModel
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(int secondaryFilterRole READ secondaryFilterRole WRITE setSecondaryFilterRole NOTIFY secondaryFilterRoleChanged)
|
|
||||||
Q_PROPERTY(QString filterText READ filterText WRITE setFilterText NOTIFY filterTextChanged)
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Wether a row should be shown or not.
|
||||||
|
*
|
||||||
|
* @sa QSortFilterProxyModel::filterAcceptsRow
|
||||||
|
*/
|
||||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns true if the value of source_left is less than source_right.
|
||||||
|
*
|
||||||
|
* @sa QSortFilterProxyModel::lessThan
|
||||||
|
*/
|
||||||
bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override;
|
bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the current secondary filter role.
|
||||||
|
*/
|
||||||
int secondaryFilterRole() const;
|
int secondaryFilterRole() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the secondary filter role.
|
||||||
|
*
|
||||||
|
* Refer to the source model for what value corresponds to what role.
|
||||||
|
*/
|
||||||
void setSecondaryFilterRole(int role);
|
void setSecondaryFilterRole(int role);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the current text being used to filter the source model.
|
||||||
|
*/
|
||||||
QString filterText() const;
|
QString filterText() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the text to be used to filter the source model.
|
||||||
|
*/
|
||||||
void setFilterText(const QString &filterText);
|
void setFilterText(const QString &filterText);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the full text in the chatbar after the completion start.
|
||||||
|
*
|
||||||
|
* This is used to automatically end the completion if the user replicated the
|
||||||
|
* primary filter role value perfectly.
|
||||||
|
*/
|
||||||
void setFullText(const QString &fullText);
|
void setFullText(const QString &fullText);
|
||||||
|
|
||||||
Q_SIGNALS:
|
|
||||||
void secondaryFilterRoleChanged();
|
|
||||||
void filterTextChanged();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int m_secondaryFilterRole = -1;
|
int m_secondaryFilterRole = -1;
|
||||||
QString m_filterText;
|
QString m_filterText;
|
||||||
|
|||||||
@@ -17,19 +17,29 @@ struct CustomEmoji {
|
|||||||
Q_PROPERTY(QString name MEMBER name)
|
Q_PROPERTY(QString name MEMBER name)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class CustomEmojiModel
|
||||||
|
*
|
||||||
|
* This class defines the model for custom user emojis.
|
||||||
|
*
|
||||||
|
* This is based upon the im.ponies.user_emotes spec (MSC2545).
|
||||||
|
*/
|
||||||
class CustomEmojiModel : public QAbstractListModel
|
class CustomEmojiModel : public QAbstractListModel
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Defines the model roles.
|
||||||
|
*/
|
||||||
enum Roles {
|
enum Roles {
|
||||||
Name = Qt::DisplayRole,
|
Name = Qt::DisplayRole, /**< The name of the emoji. */
|
||||||
ImageURL,
|
ImageURL, /**< The URL for the custom emoji. */
|
||||||
ModelData, // for emulating the regular emoji model's usage, otherwise the UI code would get too complicated
|
ModelData, /**< for emulating the regular emoji model's usage, otherwise the UI code would get too complicated. */
|
||||||
MxcUrl = 50,
|
MxcUrl = 50, /**< The mxc source URL for the custom emoji. */
|
||||||
DisplayRole = 51,
|
DisplayRole = 51, /**< The name of the emoji. For compatibility with EmojiModel. */
|
||||||
ReplacedTextRole = 52,
|
ReplacedTextRole = 52, /**< The name of the emoji. For compatibility with EmojiModel. */
|
||||||
DescriptionRole = 53, // also invalid, reserved
|
DescriptionRole = 53, /**< Invalid, reserved. For compatibility with EmojiModel. */
|
||||||
};
|
};
|
||||||
Q_ENUM(Roles);
|
Q_ENUM(Roles);
|
||||||
|
|
||||||
@@ -39,14 +49,45 @@ public:
|
|||||||
return _instance;
|
return _instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the given role value at the given index.
|
||||||
|
*
|
||||||
|
* @sa QAbstractItemModel::data
|
||||||
|
*/
|
||||||
QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
|
QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Number of rows in the model.
|
||||||
|
*
|
||||||
|
* @sa QAbstractItemModel::rowCount
|
||||||
|
*/
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a mapping from Role enum values to role names.
|
||||||
|
*
|
||||||
|
* @sa Roles, QAbstractItemModel::roleNames()
|
||||||
|
*/
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Substitute any custom emojis for an image in the input text.
|
||||||
|
*/
|
||||||
Q_INVOKABLE QString preprocessText(const QString &it);
|
Q_INVOKABLE QString preprocessText(const QString &it);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return a list of custom emojis where the name contains the filter text.
|
||||||
|
*/
|
||||||
Q_INVOKABLE QVariantList filterModel(const QString &filter);
|
Q_INVOKABLE QVariantList filterModel(const QString &filter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Add a new emoji to the model.
|
||||||
|
*/
|
||||||
Q_INVOKABLE void addEmoji(const QString &name, const QUrl &location);
|
Q_INVOKABLE void addEmoji(const QString &name, const QUrl &location);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Remove an emoji from the model.
|
||||||
|
*/
|
||||||
Q_INVOKABLE void removeEmoji(const QString &name);
|
Q_INVOKABLE void removeEmoji(const QString &name);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -12,28 +12,67 @@ namespace Quotient
|
|||||||
class Connection;
|
class Connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class DevicesModel
|
||||||
|
*
|
||||||
|
* This class defines the model for managing the devices of the local user.
|
||||||
|
*
|
||||||
|
* A device is any session where the local user is logged into a client. This means
|
||||||
|
* the same physical device can have multiple sessions for example if the user uses
|
||||||
|
* multiple clients on the same machine.
|
||||||
|
*/
|
||||||
class DevicesModel : public QAbstractListModel
|
class DevicesModel : public QAbstractListModel
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The current connection that the model is getting its devices from.
|
||||||
|
*/
|
||||||
Q_PROPERTY(Quotient::Connection *connection READ connection NOTIFY connectionChanged)
|
Q_PROPERTY(Quotient::Connection *connection READ connection NOTIFY connectionChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Defines the model roles.
|
||||||
|
*/
|
||||||
enum Roles {
|
enum Roles {
|
||||||
Id,
|
Id, /**< The device ID. */
|
||||||
DisplayName,
|
DisplayName, /**< Display name set by the user for this device. */
|
||||||
LastIp,
|
LastIp, /**< The IP address where this device was last seen. */
|
||||||
LastTimestamp,
|
LastTimestamp, /**< The timestamp when this devices was last seen. */
|
||||||
};
|
};
|
||||||
Q_ENUM(Roles);
|
Q_ENUM(Roles);
|
||||||
|
|
||||||
DevicesModel(QObject *parent = nullptr);
|
DevicesModel(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the given role value at the given index.
|
||||||
|
*
|
||||||
|
* @sa QAbstractItemModel::data
|
||||||
|
*/
|
||||||
QVariant data(const QModelIndex &index, int role) const override;
|
QVariant data(const QModelIndex &index, int role) const override;
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
|
||||||
|
/**
|
||||||
|
* @brief Number of rows in the model.
|
||||||
|
*
|
||||||
|
* @sa QAbstractItemModel::rowCount
|
||||||
|
*/
|
||||||
int rowCount(const QModelIndex &parent) const override;
|
int rowCount(const QModelIndex &parent) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a mapping from Role enum values to role names.
|
||||||
|
*
|
||||||
|
* @sa Roles, QAbstractItemModel::roleNames()
|
||||||
|
*/
|
||||||
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Logout the device at the given index.
|
||||||
|
*/
|
||||||
Q_INVOKABLE void logout(int index, const QString &password);
|
Q_INVOKABLE void logout(int index, const QString &password);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the display name of the device at the given index.
|
||||||
|
*/
|
||||||
Q_INVOKABLE void setName(int index, const QString &name);
|
Q_INVOKABLE void setName(int index, const QString &name);
|
||||||
|
|
||||||
Quotient::Connection *connection() const;
|
Quotient::Connection *connection() const;
|
||||||
|
|||||||
@@ -51,12 +51,30 @@ struct Emoji {
|
|||||||
|
|
||||||
Q_DECLARE_METATYPE(Emoji)
|
Q_DECLARE_METATYPE(Emoji)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class EmojiModel
|
||||||
|
*
|
||||||
|
* This class defines the model for visualising a list of emojis.
|
||||||
|
*/
|
||||||
class EmojiModel : public QAbstractListModel
|
class EmojiModel : public QAbstractListModel
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return a list of recently used emojis.
|
||||||
|
*/
|
||||||
Q_PROPERTY(QVariantList history READ history NOTIFY historyChanged)
|
Q_PROPERTY(QVariantList history READ history NOTIFY historyChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return a list of emoji categories.
|
||||||
|
*
|
||||||
|
* @note No custom emoji categories will be included.
|
||||||
|
*/
|
||||||
Q_PROPERTY(QVariantList categories READ categories CONSTANT)
|
Q_PROPERTY(QVariantList categories READ categories CONSTANT)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return a list of emoji categories with custom emojis.
|
||||||
|
*/
|
||||||
Q_PROPERTY(QVariantList categoriesWithCustom READ categoriesWithCustom CONSTANT)
|
Q_PROPERTY(QVariantList categoriesWithCustom READ categoriesWithCustom CONSTANT)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -66,47 +84,92 @@ public:
|
|||||||
return _instance;
|
return _instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Defines the model roles.
|
||||||
|
*/
|
||||||
enum RoleNames {
|
enum RoleNames {
|
||||||
ShortNameRole = Qt::DisplayRole,
|
ShortNameRole = Qt::DisplayRole, /**< The name of the emoji. */
|
||||||
UnicodeRole,
|
UnicodeRole, /**< The unicode character of the emoji. */
|
||||||
InvalidRole = 50,
|
InvalidRole = 50, /**< Invalid, reserved. */
|
||||||
DisplayRole = 51,
|
DisplayRole = 51, /**< The display text for an emoji. */
|
||||||
ReplacedTextRole = 52,
|
ReplacedTextRole = 52, /**< The text to replace the short name with (i.e. the unicode character). */
|
||||||
DescriptionRole = 53,
|
DescriptionRole = 53, /**< The long description of an emoji. */
|
||||||
};
|
};
|
||||||
Q_ENUM(RoleNames);
|
Q_ENUM(RoleNames);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Defines the potential categories an emoji can be placed in.
|
||||||
|
*/
|
||||||
enum Category {
|
enum Category {
|
||||||
Custom,
|
Custom, /**< A custom user emoji. */
|
||||||
Search,
|
Search, /**< The results of a filter. */
|
||||||
SearchNoCustom,
|
SearchNoCustom, /**< The results of a filter with no custom emojis. */
|
||||||
History,
|
History, /**< Recently used emojis. */
|
||||||
HistoryNoCustom,
|
HistoryNoCustom, /**< Recently used emojis with no custom emojis. */
|
||||||
Smileys,
|
Smileys, /**< Smileys & emotion emojis. */
|
||||||
People,
|
People, /**< People & Body emojis. */
|
||||||
Nature,
|
Nature, /**< Animals & Nature emojis. */
|
||||||
Food,
|
Food, /**< Food & Drink emojis. */
|
||||||
Activities,
|
Activities, /**< Activities emojis. */
|
||||||
Travel,
|
Travel, /**< Travel & Places emojis. */
|
||||||
Objects,
|
Objects, /**< Objects emojis. */
|
||||||
Symbols,
|
Symbols, /**< Symbols emojis. */
|
||||||
Flags,
|
Flags, /**< Flags emojis. */
|
||||||
Component,
|
Component, /**< ??? */
|
||||||
};
|
};
|
||||||
Q_ENUM(Category)
|
Q_ENUM(Category)
|
||||||
|
|
||||||
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
/**
|
||||||
|
* @brief Get the given role value at the given index.
|
||||||
|
*
|
||||||
|
* @sa QAbstractItemModel::data
|
||||||
|
*/
|
||||||
[[nodiscard]] QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
|
[[nodiscard]] QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Number of rows in the model.
|
||||||
|
*
|
||||||
|
* @sa QAbstractItemModel::rowCount
|
||||||
|
*/
|
||||||
|
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a mapping from Role enum values to role names.
|
||||||
|
*
|
||||||
|
* @sa RoleNames, QAbstractItemModel::roleNames()
|
||||||
|
*/
|
||||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
Q_INVOKABLE QVariantList history() const;
|
/**
|
||||||
|
* @brief Return a filtered list of emojis.
|
||||||
|
*
|
||||||
|
* @note This includes custom emojis, use filterModelNoCustom to return a result
|
||||||
|
* without custom emojis.
|
||||||
|
*
|
||||||
|
* @sa filterModelNoCustom
|
||||||
|
*/
|
||||||
Q_INVOKABLE static QVariantList filterModel(const QString &filter, bool limit = true);
|
Q_INVOKABLE static QVariantList filterModel(const QString &filter, bool limit = true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return a filtered list of emojis without custom emojis.
|
||||||
|
*
|
||||||
|
* @note Use filterModel to return a result with custom emojis.
|
||||||
|
*
|
||||||
|
* @sa filterModel
|
||||||
|
*/
|
||||||
Q_INVOKABLE static QVariantList filterModelNoCustom(const QString &filter, bool limit = true);
|
Q_INVOKABLE static QVariantList filterModelNoCustom(const QString &filter, bool limit = true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return a list of emojis for the given category.
|
||||||
|
*/
|
||||||
Q_INVOKABLE QVariantList emojis(Category category) const;
|
Q_INVOKABLE QVariantList emojis(Category category) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return a list of emoji tones for the given base emoji.
|
||||||
|
*/
|
||||||
Q_INVOKABLE QVariantList tones(const QString &baseEmoji) const;
|
Q_INVOKABLE QVariantList tones(const QString &baseEmoji) const;
|
||||||
|
|
||||||
|
Q_INVOKABLE QVariantList history() const;
|
||||||
QVariantList categories() const;
|
QVariantList categories() const;
|
||||||
QVariantList categoriesWithCustom() const;
|
QVariantList categoriesWithCustom() const;
|
||||||
|
|
||||||
|
|||||||
@@ -7,23 +7,54 @@
|
|||||||
|
|
||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class KeywordNotificationRuleModel
|
||||||
|
*
|
||||||
|
* This class defines the model for managing notification push rule keywords.
|
||||||
|
*/
|
||||||
class KeywordNotificationRuleModel : public QAbstractListModel
|
class KeywordNotificationRuleModel : public QAbstractListModel
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Defines the model roles.
|
||||||
|
*/
|
||||||
enum EventRoles {
|
enum EventRoles {
|
||||||
NameRole = Qt::DisplayRole,
|
NameRole = Qt::DisplayRole, /**< The push rule keyword. */
|
||||||
};
|
};
|
||||||
|
|
||||||
KeywordNotificationRuleModel(QObject *parent = nullptr);
|
KeywordNotificationRuleModel(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the given role value at the given index.
|
||||||
|
*
|
||||||
|
* @sa QAbstractItemModel::data
|
||||||
|
*/
|
||||||
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Number of rows in the model.
|
||||||
|
*
|
||||||
|
* @sa QAbstractItemModel::rowCount
|
||||||
|
*/
|
||||||
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a mapping from Role enum values to role names.
|
||||||
|
*
|
||||||
|
* @sa EventRoles, QAbstractItemModel::roleNames()
|
||||||
|
*/
|
||||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Add a new keyword to the model.
|
||||||
|
*/
|
||||||
Q_INVOKABLE void addKeyword(const QString &keyword);
|
Q_INVOKABLE void addKeyword(const QString &keyword);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Remove a keyword from the model.
|
||||||
|
*/
|
||||||
Q_INVOKABLE void removeKeywordAtIndex(int index);
|
Q_INVOKABLE void removeKeywordAtIndex(int index);
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ using namespace Quotient;
|
|||||||
QHash<int, QByteArray> MessageEventModel::roleNames() const
|
QHash<int, QByteArray> MessageEventModel::roleNames() const
|
||||||
{
|
{
|
||||||
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
|
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
|
||||||
roles[EventTypeRole] = "eventType";
|
roles[DelegateTypeRole] = "delegateType";
|
||||||
roles[MessageRole] = "message";
|
roles[MessageRole] = "message";
|
||||||
roles[EventIdRole] = "eventId";
|
roles[EventIdRole] = "eventId";
|
||||||
roles[TimeRole] = "time";
|
roles[TimeRole] = "time";
|
||||||
@@ -45,19 +45,16 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const
|
|||||||
roles[SpecialMarksRole] = "marks";
|
roles[SpecialMarksRole] = "marks";
|
||||||
roles[LongOperationRole] = "progressInfo";
|
roles[LongOperationRole] = "progressInfo";
|
||||||
roles[FileMimetypeIcon] = "fileMimetypeIcon";
|
roles[FileMimetypeIcon] = "fileMimetypeIcon";
|
||||||
roles[AnnotationRole] = "annotation";
|
|
||||||
roles[EventResolvedTypeRole] = "eventResolvedType";
|
roles[EventResolvedTypeRole] = "eventResolvedType";
|
||||||
roles[IsReplyRole] = "isReply";
|
roles[IsReplyRole] = "isReply";
|
||||||
roles[ReplyRole] = "reply";
|
roles[ReplyRole] = "reply";
|
||||||
roles[ReplyIdRole] = "replyId";
|
roles[ReplyIdRole] = "replyId";
|
||||||
roles[UserMarkerRole] = "userMarker";
|
|
||||||
roles[ShowAuthorRole] = "showAuthor";
|
roles[ShowAuthorRole] = "showAuthor";
|
||||||
roles[ShowSectionRole] = "showSection";
|
roles[ShowSectionRole] = "showSection";
|
||||||
roles[ReadMarkersRole] = "readMarkers";
|
roles[ReadMarkersRole] = "readMarkers";
|
||||||
roles[ReadMarkersStringRole] = "readMarkersString";
|
roles[ReadMarkersStringRole] = "readMarkersString";
|
||||||
roles[ShowReadMarkersRole] = "showReadMarkers";
|
roles[ShowReadMarkersRole] = "showReadMarkers";
|
||||||
roles[ReactionRole] = "reaction";
|
roles[ReactionRole] = "reaction";
|
||||||
roles[IsEditedRole] = "isEdited";
|
|
||||||
roles[SourceRole] = "source";
|
roles[SourceRole] = "source";
|
||||||
roles[MimeTypeRole] = "mimeType";
|
roles[MimeTypeRole] = "mimeType";
|
||||||
roles[FormattedBodyRole] = "formattedBody";
|
roles[FormattedBodyRole] = "formattedBody";
|
||||||
@@ -69,12 +66,14 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const
|
|||||||
roles[IsRedactedRole] = "isRedacted";
|
roles[IsRedactedRole] = "isRedacted";
|
||||||
roles[GenericDisplayRole] = "genericDisplay";
|
roles[GenericDisplayRole] = "genericDisplay";
|
||||||
roles[IsPendingRole] = "isPending";
|
roles[IsPendingRole] = "isPending";
|
||||||
|
roles[LatitudeRole] = "latitude";
|
||||||
|
roles[LongitudeRole] = "longitude";
|
||||||
|
roles[AssetRole] = "asset";
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageEventModel::MessageEventModel(QObject *parent)
|
MessageEventModel::MessageEventModel(QObject *parent)
|
||||||
: QAbstractListModel(parent)
|
: QAbstractListModel(parent)
|
||||||
, m_currentRoom(nullptr)
|
|
||||||
{
|
{
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
qmlRegisterAnonymousType<FileTransferInfo>("org.kde.neochat", 1);
|
qmlRegisterAnonymousType<FileTransferInfo>("org.kde.neochat", 1);
|
||||||
@@ -87,6 +86,11 @@ MessageEventModel::MessageEventModel(QObject *parent)
|
|||||||
|
|
||||||
MessageEventModel::~MessageEventModel() = default;
|
MessageEventModel::~MessageEventModel() = default;
|
||||||
|
|
||||||
|
NeoChatRoom *MessageEventModel::room() const
|
||||||
|
{
|
||||||
|
return m_currentRoom;
|
||||||
|
}
|
||||||
|
|
||||||
void MessageEventModel::setRoom(NeoChatRoom *room)
|
void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||||
{
|
{
|
||||||
if (room == m_currentRoom) {
|
if (room == m_currentRoom) {
|
||||||
@@ -314,7 +318,7 @@ int MessageEventModel::refreshEventRoles(const QString &id, const QVector<int> &
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
row = int(timelineIt - m_currentRoom->messageEvents().rbegin()) + timelineBaseIndex();
|
row = int(timelineIt - m_currentRoom->messageEvents().rbegin()) + timelineBaseIndex();
|
||||||
if (data(index(row, 0), EventTypeRole).toInt() == ReadMarker || data(index(row, 0), EventTypeRole).toInt() == Other) {
|
if (data(index(row, 0), DelegateTypeRole).toInt() == ReadMarker || data(index(row, 0), DelegateTypeRole).toInt() == Other) {
|
||||||
row++;
|
row++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -445,7 +449,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
|
|
||||||
if (m_lastReadEventIndex.row() == row) {
|
if (m_lastReadEventIndex.row() == row) {
|
||||||
switch (role) {
|
switch (role) {
|
||||||
case EventTypeRole:
|
case DelegateTypeRole:
|
||||||
return DelegateType::ReadMarker;
|
return DelegateType::ReadMarker;
|
||||||
case TimeRole: {
|
case TimeRole: {
|
||||||
const QDateTime eventDate = data(index(m_lastReadEventIndex.row() + 1, 0), TimeRole).toDateTime().toLocalTime();
|
const QDateTime eventDate = data(index(m_lastReadEventIndex.row() + 1, 0), TimeRole).toDateTime().toLocalTime();
|
||||||
@@ -497,7 +501,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
return evt.originalJson();
|
return evt.originalJson();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role == EventTypeRole) {
|
if (role == DelegateTypeRole) {
|
||||||
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
||||||
switch (e->msgtype()) {
|
switch (e->msgtype()) {
|
||||||
case MessageEventType::Emote:
|
case MessageEventType::Emote:
|
||||||
@@ -510,6 +514,8 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
return DelegateType::Audio;
|
return DelegateType::Audio;
|
||||||
case MessageEventType::Video:
|
case MessageEventType::Video:
|
||||||
return DelegateType::Video;
|
return DelegateType::Video;
|
||||||
|
case MessageEventType::Location:
|
||||||
|
return DelegateType::Location;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -564,6 +570,9 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
||||||
|
if (e->msgtype() == Quotient::MessageEventType::Location) {
|
||||||
|
return e->contentJson();
|
||||||
|
}
|
||||||
// Cannot use e.contentJson() here because some
|
// Cannot use e.contentJson() here because some
|
||||||
// EventContent classes inject values into the copy of the
|
// EventContent classes inject values into the copy of the
|
||||||
// content JSON stored in EventContent::Base
|
// content JSON stored in EventContent::Base
|
||||||
@@ -654,14 +663,6 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
return EventStatus::Normal;
|
return EventStatus::Normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role == IsEditedRole) {
|
|
||||||
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
|
||||||
return !e->unsignedJson().isEmpty() && e->unsignedJson().contains("m.relations")
|
|
||||||
&& e->unsignedJson()["m.relations"].toObject().contains("m.replace");
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (role == EventIdRole) {
|
if (role == EventIdRole) {
|
||||||
return !evt.id().isEmpty() ? evt.id() : evt.transactionId();
|
return !evt.id().isEmpty() ? evt.id() : evt.transactionId();
|
||||||
}
|
}
|
||||||
@@ -677,29 +678,11 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role == AnnotationRole) {
|
|
||||||
if (isPending) {
|
|
||||||
return pendingIt->annotation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (role == TimeRole || role == SectionRole) {
|
if (role == TimeRole || role == SectionRole) {
|
||||||
auto ts = isPending ? pendingIt->lastUpdated() : makeMessageTimestamp(timelineIt);
|
auto ts = isPending ? pendingIt->lastUpdated() : makeMessageTimestamp(timelineIt);
|
||||||
return role == TimeRole ? QVariant(ts) : renderDate(ts);
|
return role == TimeRole ? QVariant(ts) : renderDate(ts);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role == UserMarkerRole) {
|
|
||||||
QVariantList variantList;
|
|
||||||
const auto users = m_currentRoom->usersAtEventId(evt.id());
|
|
||||||
for (User *user : users) {
|
|
||||||
if (user == m_currentRoom->localUser()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
variantList.append(QVariant::fromValue(user));
|
|
||||||
}
|
|
||||||
return variantList;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (role == IsReplyRole) {
|
if (role == IsReplyRole) {
|
||||||
return !evt.contentJson()["m.relates_to"].toObject()["m.in_reply_to"].toObject()["event_id"].toString().isEmpty();
|
return !evt.contentJson()["m.relates_to"].toObject()["m.in_reply_to"].toObject()["event_id"].toString().isEmpty();
|
||||||
}
|
}
|
||||||
@@ -785,7 +768,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
// While the row is removed the subsequent row indexes are not changed so we need to skip over the removed index.
|
// While the row is removed the subsequent row indexes are not changed so we need to skip over the removed index.
|
||||||
// See - https://doc.qt.io/qt-5/qabstractitemmodel.html#beginRemoveRows
|
// See - https://doc.qt.io/qt-5/qabstractitemmodel.html#beginRemoveRows
|
||||||
if (data(i, SpecialMarksRole) != EventStatus::Hidden && !itemData(i).empty()) {
|
if (data(i, SpecialMarksRole) != EventStatus::Hidden && !itemData(i).empty()) {
|
||||||
return data(i, AuthorRole) != data(idx, AuthorRole) || data(i, EventTypeRole) == MessageEventModel::State
|
return data(i, AuthorRole) != data(idx, AuthorRole) || data(i, DelegateTypeRole) == MessageEventModel::State
|
||||||
|| data(i, TimeRole).toDateTime().msecsTo(data(idx, TimeRole).toDateTime()) > 600000
|
|| data(i, TimeRole).toDateTime().msecsTo(data(idx, TimeRole).toDateTime()) > 600000
|
||||||
|| data(i, TimeRole).toDateTime().toLocalTime().date().day() != data(idx, TimeRole).toDateTime().toLocalTime().date().day();
|
|| data(i, TimeRole).toDateTime().toLocalTime().date().day() != data(idx, TimeRole).toDateTime().toLocalTime().date().day();
|
||||||
}
|
}
|
||||||
@@ -810,6 +793,32 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (role == LatitudeRole) {
|
||||||
|
const auto geoUri = evt.contentJson()["geo_uri"_ls].toString();
|
||||||
|
if (geoUri.isEmpty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto latitude = geoUri.split(u';')[0].split(u':')[1].split(u',')[0];
|
||||||
|
return latitude.toFloat();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == LongitudeRole) {
|
||||||
|
const auto geoUri = evt.contentJson()["geo_uri"_ls].toString();
|
||||||
|
if (geoUri.isEmpty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto latitude = geoUri.split(u';')[0].split(u':')[1].split(u',')[1];
|
||||||
|
return latitude.toFloat();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == AssetRole) {
|
||||||
|
const auto assetType = evt.contentJson()["org.matrix.msc3488.asset"].toObject()["type"].toString();
|
||||||
|
if (assetType.isEmpty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return assetType;
|
||||||
|
}
|
||||||
|
|
||||||
if (role == ReadMarkersRole) {
|
if (role == ReadMarkersRole) {
|
||||||
#ifdef QUOTIENT_07
|
#ifdef QUOTIENT_07
|
||||||
auto userIds = room()->userIdsAtEvent(evt.id());
|
auto userIds = room()->userIdsAtEvent(evt.id());
|
||||||
@@ -980,7 +989,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
int MessageEventModel::eventIDToIndex(const QString &eventID) const
|
int MessageEventModel::eventIdToRow(const QString &eventID) const
|
||||||
{
|
{
|
||||||
const auto it = m_currentRoom->findInTimeline(eventID);
|
const auto it = m_currentRoom->findInTimeline(eventID);
|
||||||
if (it == m_currentRoom->historyEdge()) {
|
if (it == m_currentRoom->historyEdge()) {
|
||||||
@@ -1022,7 +1031,7 @@ QVariant MessageEventModel::getLastLocalUserMessageEventId()
|
|||||||
targetMessage.insert("event_id", eventId);
|
targetMessage.insert("event_id", eventId);
|
||||||
targetMessage.insert("formattedBody", content["formatted_body"].toString());
|
targetMessage.insert("formattedBody", content["formatted_body"].toString());
|
||||||
// Need to get the message from the original eventId or body will have * on the front
|
// Need to get the message from the original eventId or body will have * on the front
|
||||||
QModelIndex idx = index(eventIDToIndex(eventId), 0);
|
QModelIndex idx = index(eventIdToRow(eventId), 0);
|
||||||
targetMessage.insert("message", idx.data(Qt::UserRole + 2));
|
targetMessage.insert("message", idx.data(Qt::UserRole + 2));
|
||||||
|
|
||||||
return targetMessage;
|
return targetMessage;
|
||||||
@@ -1032,14 +1041,14 @@ QVariant MessageEventModel::getLastLocalUserMessageEventId()
|
|||||||
return targetMessage;
|
return targetMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant MessageEventModel::getLatestMessageFromIndex(const int baseline)
|
QVariant MessageEventModel::getLatestMessageFromRow(const int startRow)
|
||||||
{
|
{
|
||||||
QVariantMap replyResponse;
|
QVariantMap replyResponse;
|
||||||
const auto &timelineBottom = m_currentRoom->messageEvents().rbegin() + baseline;
|
const auto &timelineBottom = m_currentRoom->messageEvents().rbegin() + startRow;
|
||||||
|
|
||||||
// set a cap limit of baseline + 35 messages, to prevent loading a lot of messages
|
// set a cap limit of startRow + 35 messages, to prevent loading a lot of messages
|
||||||
// in rooms where the user has not sent many messages
|
// in rooms where the user has not sent many messages
|
||||||
const auto limit = timelineBottom + std::min(baseline + 35, m_currentRoom->timelineSize());
|
const auto limit = timelineBottom + std::min(startRow + 35, m_currentRoom->timelineSize());
|
||||||
|
|
||||||
for (auto it = timelineBottom; it != limit; ++it) {
|
for (auto it = timelineBottom; it != limit; ++it) {
|
||||||
auto evt = it->event();
|
auto evt = it->event();
|
||||||
@@ -1061,7 +1070,7 @@ QVariant MessageEventModel::getLatestMessageFromIndex(const int baseline)
|
|||||||
}
|
}
|
||||||
replyResponse.insert("event_id", eventId);
|
replyResponse.insert("event_id", eventId);
|
||||||
// Need to get the message from the original eventId or body will have * on the front
|
// Need to get the message from the original eventId or body will have * on the front
|
||||||
QModelIndex idx = index(eventIDToIndex(eventId), 0);
|
QModelIndex idx = index(eventIdToRow(eventId), 0);
|
||||||
replyResponse.insert("message", idx.data(Qt::UserRole + 2));
|
replyResponse.insert("message", idx.data(Qt::UserRole + 2));
|
||||||
replyResponse.insert("sender_id", QVariant::fromValue(m_currentRoom->getUser((*it)->senderId())));
|
replyResponse.insert("sender_id", QVariant::fromValue(m_currentRoom->getUser((*it)->senderId())));
|
||||||
replyResponse.insert("at", -it->index());
|
replyResponse.insert("at", -it->index());
|
||||||
|
|||||||
@@ -7,74 +7,99 @@
|
|||||||
|
|
||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class MessageEventModel
|
||||||
|
*
|
||||||
|
* This class defines the model for visualising the room timeline.
|
||||||
|
*
|
||||||
|
* This model covers all event types in the timeline with many of the roles being
|
||||||
|
* specific to a subset of events. This means the user needs to understand which
|
||||||
|
* roles will return useful information for a given event type.
|
||||||
|
*
|
||||||
|
* @sa NeoChatRoom
|
||||||
|
*/
|
||||||
class MessageEventModel : public QAbstractListModel
|
class MessageEventModel : public QAbstractListModel
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The current room that the model is getting its messages from.
|
||||||
|
*/
|
||||||
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief The type of delegate that is needed for the event.
|
||||||
|
*
|
||||||
|
* @note While similar this is not the matrix event or message type. This is
|
||||||
|
* to tell a QML ListView what delegate to show for each event. So while
|
||||||
|
* similar to the spec it is not the same.
|
||||||
|
*/
|
||||||
enum DelegateType {
|
enum DelegateType {
|
||||||
Emote,
|
Emote, /**< A message that begins with /me. */
|
||||||
Notice,
|
Notice, /**< A notice event. */
|
||||||
Image,
|
Image, /**< A message that is an image. */
|
||||||
Audio,
|
Audio, /**< A message that is an audio recording. */
|
||||||
Video,
|
Video, /**< A message that is a video. */
|
||||||
File,
|
File, /**< A message that is a file. */
|
||||||
Message,
|
Message, /**< A text message. */
|
||||||
Sticker,
|
Sticker, /**< A message that is a sticker. */
|
||||||
State,
|
State, /**< A state event in the room. */
|
||||||
Encrypted,
|
Encrypted, /**< An encrypted message that cannot be decrypted. */
|
||||||
ReadMarker,
|
ReadMarker, /**< The local user read marker. */
|
||||||
Poll,
|
Poll, /**< The initial event for a poll. */
|
||||||
Other,
|
Location, /**< A location event. */
|
||||||
|
Other, /**< Anything that cannot be classified as another type. */
|
||||||
};
|
};
|
||||||
Q_ENUM(DelegateType);
|
Q_ENUM(DelegateType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Defines the model roles.
|
||||||
|
*/
|
||||||
enum EventRoles {
|
enum EventRoles {
|
||||||
EventTypeRole = Qt::UserRole + 1,
|
DelegateTypeRole = Qt::UserRole + 1, /**< The delegate type of the message. */
|
||||||
MessageRole,
|
MessageRole, /**< Plain text representation of the message. */
|
||||||
EventIdRole,
|
EventIdRole, /**< The matrix event ID of the event. */
|
||||||
TimeRole,
|
TimeRole, /**< The timestamp for when the event was sent. */
|
||||||
SectionRole,
|
SectionRole, /**< The date of the event as a string. */
|
||||||
AuthorRole,
|
AuthorRole, /**< The author of the event. */
|
||||||
ContentRole,
|
ContentRole, /**< The full message content. */
|
||||||
ContentTypeRole,
|
ContentTypeRole, /**< The content mime type. */
|
||||||
HighlightRole,
|
HighlightRole, /**< Whether the event should be highlighted. */
|
||||||
SpecialMarksRole,
|
SpecialMarksRole, /**< Whether the event is hidden or not. */
|
||||||
LongOperationRole,
|
LongOperationRole, /**< Progress info when downloading files. */
|
||||||
AnnotationRole,
|
FormattedBodyRole, /**< The formatted body of a rich message. */
|
||||||
UserMarkerRole,
|
GenericDisplayRole, /**< A generic string based upon the message type. */
|
||||||
FormattedBodyRole,
|
|
||||||
GenericDisplayRole,
|
|
||||||
|
|
||||||
MimeTypeRole,
|
MimeTypeRole, /**< The mime type of the message's file or media. */
|
||||||
FileMimetypeIcon,
|
FileMimetypeIcon, /**< The icon name for the mime type of a file. */
|
||||||
|
|
||||||
IsReplyRole,
|
IsReplyRole, /**< Is the message a reply to another event. */
|
||||||
ReplyRole,
|
ReplyRole, /**< The content data of the message that was replied to. */
|
||||||
ReplyIdRole,
|
ReplyIdRole, /**< The matrix ID of the message that was replied to. */
|
||||||
|
|
||||||
ShowAuthorRole,
|
ShowAuthorRole, /**< Whether the author's name should be shown. */
|
||||||
ShowSectionRole,
|
ShowSectionRole, /**< Whether the section header should be shown. */
|
||||||
|
|
||||||
ReadMarkersRole, /**< QVariantList of users at the event for read marker tracking. */
|
ReadMarkersRole, /**< Other users at the event for read marker tracking. */
|
||||||
ReadMarkersStringRole, /**< QString with the display name and mxID of the users at the event. */
|
ReadMarkersStringRole, /**< String with the display name and mxID of the users at the event. */
|
||||||
ShowReadMarkersRole, /**< bool with whether there are any other user read markers to be shown. */
|
ShowReadMarkersRole, /**< Whether there are any other user read markers to be shown. */
|
||||||
ReactionRole,
|
ReactionRole, /**< List of reactions to this event. */
|
||||||
|
SourceRole, /**< The full message source JSON. */
|
||||||
|
MediaUrlRole, /**< The source URL for any media in the message. */
|
||||||
|
|
||||||
IsEditedRole,
|
|
||||||
SourceRole,
|
|
||||||
MediaUrlRole,
|
|
||||||
// For debugging
|
// For debugging
|
||||||
EventResolvedTypeRole,
|
EventResolvedTypeRole, /**< The event type the message. */
|
||||||
AuthorIdRole,
|
AuthorIdRole, /**< Matrix ID of the message author. */
|
||||||
VerifiedRole,
|
|
||||||
// Sender's displayname, always without the matrix id
|
VerifiedRole, /**< Whether an encrypted message is sent in a verified session. */
|
||||||
DisplayNameForInitialsRole,
|
DisplayNameForInitialsRole, /**< Sender's displayname, always without the matrix id. */
|
||||||
// The displayname for the event's sender; for name change events, the old displayname
|
AuthorDisplayNameRole, /**< The displayname for the event's sender; for name change events, the old displayname. */
|
||||||
AuthorDisplayNameRole,
|
IsRedactedRole, /**< Whether an event has been deleted. */
|
||||||
IsRedactedRole,
|
IsPendingRole, /**< Whether an event is waiting to be accepted by the server. */
|
||||||
IsPendingRole,
|
LatitudeRole, /**< Latitude for a location event. */
|
||||||
|
LongitudeRole, /**< Longitude for a location event. */
|
||||||
|
AssetRole, /**< Type of location event, e.g. self pin of the user location. */
|
||||||
LastRole, // Keep this last
|
LastRole, // Keep this last
|
||||||
};
|
};
|
||||||
Q_ENUM(EventRoles)
|
Q_ENUM(EventRoles)
|
||||||
@@ -82,20 +107,67 @@ public:
|
|||||||
explicit MessageEventModel(QObject *parent = nullptr);
|
explicit MessageEventModel(QObject *parent = nullptr);
|
||||||
~MessageEventModel() override;
|
~MessageEventModel() override;
|
||||||
|
|
||||||
[[nodiscard]] NeoChatRoom *room() const
|
[[nodiscard]] NeoChatRoom *room() const;
|
||||||
{
|
|
||||||
return m_currentRoom;
|
|
||||||
}
|
|
||||||
void setRoom(NeoChatRoom *room);
|
void setRoom(NeoChatRoom *room);
|
||||||
|
|
||||||
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
/**
|
||||||
|
* @brief Get the given role value at the given index.
|
||||||
|
*
|
||||||
|
* @sa QAbstractItemModel::data
|
||||||
|
*/
|
||||||
[[nodiscard]] QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
|
[[nodiscard]] QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Number of rows in the model.
|
||||||
|
*
|
||||||
|
* @sa QAbstractItemModel::rowCount
|
||||||
|
*/
|
||||||
|
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a mapping from Role enum values to role names.
|
||||||
|
*
|
||||||
|
* @sa EventRoles, QAbstractItemModel::roleNames()
|
||||||
|
*/
|
||||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
Q_INVOKABLE [[nodiscard]] int eventIDToIndex(const QString &eventID) const;
|
/**
|
||||||
|
* @brief Get the row number of the given event ID in the model.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE [[nodiscard]] int eventIdToRow(const QString &eventID) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the last message sent by the local user.
|
||||||
|
*
|
||||||
|
* @note This checks a maximum of the previous 35 message for performance reasons.
|
||||||
|
*
|
||||||
|
* @return a QVariantMap for the event with the following parameters:
|
||||||
|
* - eventId - The event ID.
|
||||||
|
* - formattedBody - The message text formatted as Qt::RichText.
|
||||||
|
* - message - The message text formatted as Qt::PlainText.
|
||||||
|
*/
|
||||||
Q_INVOKABLE [[nodiscard]] QVariant getLastLocalUserMessageEventId();
|
Q_INVOKABLE [[nodiscard]] QVariant getLastLocalUserMessageEventId();
|
||||||
Q_INVOKABLE [[nodiscard]] QVariant getLatestMessageFromIndex(const int baseline);
|
|
||||||
Q_INVOKABLE void loadReply(const QModelIndex &row);
|
/**
|
||||||
|
* @brief Get the last message sent earlier than the given row.
|
||||||
|
*
|
||||||
|
* @note This checks a maximum of the previous 35 message for performance reasons.
|
||||||
|
*
|
||||||
|
* @return a QVariantMap for the event with the following parameters:
|
||||||
|
* - eventId - The event ID.
|
||||||
|
* - message - The message text formatted as Qt::PlainText.
|
||||||
|
* - sender_id - The matrix ID of the sender.
|
||||||
|
* - at - The QModelIndex of the message.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE [[nodiscard]] QVariant getLatestMessageFromRow(const int startRow);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Load the event that the item at the given index replied to.
|
||||||
|
*
|
||||||
|
* This is used to ensure that the reply data is available when the message that
|
||||||
|
* was replied to is outside the currently loaded timeline.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE void loadReply(const QModelIndex &index);
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
int refreshEvent(const QString &eventId);
|
int refreshEvent(const QString &eventId);
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ bool MessageFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sour
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto eventType = index.data(MessageEventModel::EventTypeRole).toInt();
|
const auto eventType = index.data(MessageEventModel::DelegateTypeRole).toInt();
|
||||||
|
|
||||||
if (eventType == MessageEventModel::Other) {
|
if (eventType == MessageEventModel::Other) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -5,10 +5,21 @@
|
|||||||
|
|
||||||
#include <QSortFilterProxyModel>
|
#include <QSortFilterProxyModel>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class MessageFilterModel
|
||||||
|
*
|
||||||
|
* This model filters out any messages that should be hidden.
|
||||||
|
*
|
||||||
|
* Deleted messages are only hidden if the user hasn't set them to be shown.
|
||||||
|
*/
|
||||||
class MessageFilterModel : public QSortFilterProxyModel
|
class MessageFilterModel : public QSortFilterProxyModel
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
MessageFilterModel(QObject *parent = nullptr);
|
MessageFilterModel(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Custom filter function to remove hidden messages.
|
||||||
|
*/
|
||||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ PublicRoomListModel::PublicRoomListModel(QObject *parent)
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Quotient::Connection *PublicRoomListModel::connection() const
|
||||||
|
{
|
||||||
|
return m_connection;
|
||||||
|
}
|
||||||
|
|
||||||
void PublicRoomListModel::setConnection(Connection *conn)
|
void PublicRoomListModel::setConnection(Connection *conn)
|
||||||
{
|
{
|
||||||
if (m_connection == conn) {
|
if (m_connection == conn) {
|
||||||
@@ -47,6 +52,11 @@ void PublicRoomListModel::setConnection(Connection *conn)
|
|||||||
Q_EMIT hasMoreChanged();
|
Q_EMIT hasMoreChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString PublicRoomListModel::server() const
|
||||||
|
{
|
||||||
|
return m_server;
|
||||||
|
}
|
||||||
|
|
||||||
void PublicRoomListModel::setServer(const QString &value)
|
void PublicRoomListModel::setServer(const QString &value)
|
||||||
{
|
{
|
||||||
if (m_server == value) {
|
if (m_server == value) {
|
||||||
@@ -76,6 +86,11 @@ void PublicRoomListModel::setServer(const QString &value)
|
|||||||
Q_EMIT hasMoreChanged();
|
Q_EMIT hasMoreChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString PublicRoomListModel::keyword() const
|
||||||
|
{
|
||||||
|
return m_keyword;
|
||||||
|
}
|
||||||
|
|
||||||
void PublicRoomListModel::setKeyword(const QString &value)
|
void PublicRoomListModel::setKeyword(const QString &value)
|
||||||
{
|
{
|
||||||
if (m_keyword == value) {
|
if (m_keyword == value) {
|
||||||
|
|||||||
@@ -13,54 +13,96 @@ namespace Quotient
|
|||||||
class Connection;
|
class Connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class PublicRoomListModel
|
||||||
|
*
|
||||||
|
* This class defines the model for visualising a list of public rooms.
|
||||||
|
*
|
||||||
|
* The model finds the public rooms visible to the given server (which doesn't have
|
||||||
|
* to be the user's home server) and can also apply a filter if desired.
|
||||||
|
*
|
||||||
|
* Due to the fact that the public room list could be huge the model is lazily loaded
|
||||||
|
* and requires that the next batch of rooms be manually called.
|
||||||
|
*/
|
||||||
class PublicRoomListModel : public QAbstractListModel
|
class PublicRoomListModel : public QAbstractListModel
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The current connection that the model is getting its rooms from.
|
||||||
|
*/
|
||||||
Q_PROPERTY(Quotient::Connection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
|
Q_PROPERTY(Quotient::Connection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The server to get the public room list from.
|
||||||
|
*/
|
||||||
Q_PROPERTY(QString server READ server WRITE setServer NOTIFY serverChanged)
|
Q_PROPERTY(QString server READ server WRITE setServer NOTIFY serverChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The filter keyword for the list of public rooms.
|
||||||
|
*/
|
||||||
Q_PROPERTY(QString keyword READ keyword WRITE setKeyword NOTIFY keywordChanged)
|
Q_PROPERTY(QString keyword READ keyword WRITE setKeyword NOTIFY keywordChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether the model has more items to load.
|
||||||
|
*/
|
||||||
Q_PROPERTY(bool hasMore READ hasMore NOTIFY hasMoreChanged)
|
Q_PROPERTY(bool hasMore READ hasMore NOTIFY hasMoreChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Defines the model roles.
|
||||||
|
*/
|
||||||
enum EventRoles {
|
enum EventRoles {
|
||||||
NameRole = Qt::DisplayRole + 1,
|
NameRole = Qt::DisplayRole + 1, /**< The name of the room. */
|
||||||
AvatarRole,
|
AvatarRole, /**< The source URL for the room's avatar. */
|
||||||
TopicRole,
|
TopicRole, /**< The room topic. */
|
||||||
RoomIDRole,
|
RoomIDRole, /**< The room matrix ID. */
|
||||||
AliasRole,
|
AliasRole, /**< The room canonical alias. */
|
||||||
MemberCountRole,
|
MemberCountRole, /**< The number of members in the room. */
|
||||||
AllowGuestsRole,
|
AllowGuestsRole, /**< Whether the room allows guest users. */
|
||||||
WorldReadableRole,
|
WorldReadableRole, /**< Whether the room events can be seen by non-members. */
|
||||||
IsJoinedRole,
|
IsJoinedRole, /**< Whether the local user has joined the room. */
|
||||||
};
|
};
|
||||||
|
|
||||||
PublicRoomListModel(QObject *parent = nullptr);
|
PublicRoomListModel(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the given role value at the given index.
|
||||||
|
*
|
||||||
|
* @sa QAbstractItemModel::data
|
||||||
|
*/
|
||||||
[[nodiscard]] QVariant data(const QModelIndex &index, int role = NameRole) const override;
|
[[nodiscard]] QVariant data(const QModelIndex &index, int role = NameRole) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Number of rows in the model.
|
||||||
|
*
|
||||||
|
* @sa QAbstractItemModel::rowCount
|
||||||
|
*/
|
||||||
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a mapping from Role enum values to role names.
|
||||||
|
*
|
||||||
|
* @sa EventRoles, QAbstractItemModel::roleNames()
|
||||||
|
*/
|
||||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
[[nodiscard]] Quotient::Connection *connection() const
|
[[nodiscard]] Quotient::Connection *connection() const;
|
||||||
{
|
|
||||||
return m_connection;
|
|
||||||
}
|
|
||||||
void setConnection(Quotient::Connection *conn);
|
void setConnection(Quotient::Connection *conn);
|
||||||
|
|
||||||
[[nodiscard]] QString server() const
|
[[nodiscard]] QString server() const;
|
||||||
{
|
|
||||||
return m_server;
|
|
||||||
}
|
|
||||||
void setServer(const QString &value);
|
void setServer(const QString &value);
|
||||||
|
|
||||||
[[nodiscard]] QString keyword() const
|
[[nodiscard]] QString keyword() const;
|
||||||
{
|
|
||||||
return m_keyword;
|
|
||||||
}
|
|
||||||
void setKeyword(const QString &value);
|
void setKeyword(const QString &value);
|
||||||
|
|
||||||
[[nodiscard]] bool hasMore() const;
|
[[nodiscard]] bool hasMore() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Load the next set of rooms.
|
||||||
|
*
|
||||||
|
* @param count the maximum number of rooms to load.
|
||||||
|
*/
|
||||||
Q_INVOKABLE void next(int count = 50);
|
Q_INVOKABLE void next(int count = 50);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -10,11 +10,13 @@
|
|||||||
#include "user.h"
|
#include "user.h"
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
|
||||||
#ifndef Q_OS_ANDROID
|
#ifndef Q_OS_ANDROID
|
||||||
#include <QDBusConnection>
|
#include <QDBusConnection>
|
||||||
#include <QDBusInterface>
|
#include <QDBusInterface>
|
||||||
#include <QDBusMessage>
|
#include <QDBusMessage>
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <KLocalizedString>
|
#include <KLocalizedString>
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
@@ -29,15 +31,6 @@ using namespace Quotient;
|
|||||||
|
|
||||||
Q_DECLARE_METATYPE(Quotient::JoinState)
|
Q_DECLARE_METATYPE(Quotient::JoinState)
|
||||||
|
|
||||||
#ifndef Q_OS_ANDROID
|
|
||||||
bool useUnityCounter()
|
|
||||||
{
|
|
||||||
static const auto Result = QDBusInterface("com.canonical.Unity", "/").isValid();
|
|
||||||
|
|
||||||
return Result;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
RoomListModel::RoomListModel(QObject *parent)
|
RoomListModel::RoomListModel(QObject *parent)
|
||||||
: QAbstractListModel(parent)
|
: QAbstractListModel(parent)
|
||||||
{
|
{
|
||||||
@@ -46,30 +39,32 @@ RoomListModel::RoomListModel(QObject *parent)
|
|||||||
m_categoryVisibility[collapsedSection] = false;
|
m_categoryVisibility[collapsedSection] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef Q_OS_ANDROID
|
|
||||||
connect(this, &RoomListModel::notificationCountChanged, this, [this]() {
|
connect(this, &RoomListModel::notificationCountChanged, this, [this]() {
|
||||||
if (useUnityCounter()) {
|
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
|
||||||
// copied from Telegram desktop
|
#ifndef Q_OS_ANDROID
|
||||||
const auto launcherUrl = "application://org.kde.neochat.desktop";
|
// copied from Telegram desktop
|
||||||
// Gnome requires that count is a 64bit integer
|
const auto launcherUrl = "application://org.kde.neochat.desktop";
|
||||||
const qint64 counterSlice = std::min(m_notificationCount, 9999);
|
// Gnome requires that count is a 64bit integer
|
||||||
QVariantMap dbusUnityProperties;
|
const qint64 counterSlice = std::min(m_notificationCount, 9999);
|
||||||
|
QVariantMap dbusUnityProperties;
|
||||||
|
|
||||||
if (counterSlice > 0) {
|
if (counterSlice > 0) {
|
||||||
dbusUnityProperties["count"] = counterSlice;
|
dbusUnityProperties["count"] = counterSlice;
|
||||||
dbusUnityProperties["count-visible"] = true;
|
dbusUnityProperties["count-visible"] = true;
|
||||||
} else {
|
} else {
|
||||||
dbusUnityProperties["count-visible"] = false;
|
dbusUnityProperties["count-visible"] = false;
|
||||||
}
|
|
||||||
|
|
||||||
auto signal = QDBusMessage::createSignal("/com/canonical/unity/launcherentry/neochat", "com.canonical.Unity.LauncherEntry", "Update");
|
|
||||||
|
|
||||||
signal.setArguments({launcherUrl, dbusUnityProperties});
|
|
||||||
|
|
||||||
QDBusConnection::sessionBus().send(signal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto signal = QDBusMessage::createSignal("/com/canonical/unity/launcherentry/neochat", "com.canonical.Unity.LauncherEntry", "Update");
|
||||||
|
|
||||||
|
signal.setArguments({launcherUrl, dbusUnityProperties});
|
||||||
|
|
||||||
|
QDBusConnection::sessionBus().send(signal);
|
||||||
|
#endif // Q_OS_ANDROID
|
||||||
|
#else
|
||||||
|
qGuiApp->setBadgeNumber(m_notificationCount);
|
||||||
|
#endif // QT_VERSION_CHECK(6, 6, 0)
|
||||||
});
|
});
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RoomListModel::~RoomListModel() = default;
|
RoomListModel::~RoomListModel() = default;
|
||||||
@@ -154,10 +149,10 @@ void RoomListModel::doAddRoom(Room *r)
|
|||||||
void RoomListModel::connectRoomSignals(NeoChatRoom *room)
|
void RoomListModel::connectRoomSignals(NeoChatRoom *room)
|
||||||
{
|
{
|
||||||
connect(room, &Room::displaynameChanged, this, [this, room] {
|
connect(room, &Room::displaynameChanged, this, [this, room] {
|
||||||
refresh(room);
|
refresh(room, {DisplayNameRole, NameRole});
|
||||||
});
|
});
|
||||||
connect(room, &Room::unreadMessagesChanged, this, [this, room] {
|
connect(room, &Room::unreadMessagesChanged, this, [this, room] {
|
||||||
refresh(room);
|
refresh(room, {UnreadCountRole, NotificationCountRole, HighlightCountRole});
|
||||||
});
|
});
|
||||||
connect(room, &Room::notificationCountChanged, this, [this, room] {
|
connect(room, &Room::notificationCountChanged, this, [this, room] {
|
||||||
refresh(room);
|
refresh(room);
|
||||||
@@ -172,7 +167,7 @@ void RoomListModel::connectRoomSignals(NeoChatRoom *room)
|
|||||||
refresh(room);
|
refresh(room);
|
||||||
});
|
});
|
||||||
connect(room, &Room::addedMessages, this, [this, room] {
|
connect(room, &Room::addedMessages, this, [this, room] {
|
||||||
refresh(room, {LastEventRole, SubtitleTextRole});
|
refresh(room, {LastEventRole, SubtitleTextRole, LastActiveTimeRole});
|
||||||
});
|
});
|
||||||
connect(room, &Room::pendingEventMerged, this, [this, room] {
|
connect(room, &Room::pendingEventMerged, this, [this, room] {
|
||||||
refresh(room, {LastEventRole, SubtitleTextRole});
|
refresh(room, {LastEventRole, SubtitleTextRole});
|
||||||
|
|||||||
@@ -217,6 +217,9 @@ int UserListModel::findUserPos(Quotient::User *user) const
|
|||||||
|
|
||||||
int UserListModel::findUserPos(const QString &username) const
|
int UserListModel::findUserPos(const QString &username) const
|
||||||
{
|
{
|
||||||
|
if (!m_currentRoom) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
return m_currentRoom->memberSorter().lowerBoundIndex(m_users, username);
|
return m_currentRoom->memberSorter().lowerBoundIndex(m_users, username);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
#include <QMimeDatabase>
|
#include <QMimeDatabase>
|
||||||
#include <QTemporaryFile>
|
#include <QTemporaryFile>
|
||||||
#include <QTextDocument>
|
#include <QTextDocument>
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
#include <QMediaMetaData>
|
#include <QMediaMetaData>
|
||||||
#include <QMediaPlayer>
|
#include <QMediaPlayer>
|
||||||
@@ -110,6 +109,34 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool NeoChatRoom::hasFileUploading() const
|
||||||
|
{
|
||||||
|
return m_hasFileUploading;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::setHasFileUploading(bool value)
|
||||||
|
{
|
||||||
|
if (value == m_hasFileUploading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_hasFileUploading = value;
|
||||||
|
Q_EMIT hasFileUploadingChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
int NeoChatRoom::fileUploadingProgress() const
|
||||||
|
{
|
||||||
|
return m_fileUploadingProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::setFileUploadingProgress(int value)
|
||||||
|
{
|
||||||
|
if (m_fileUploadingProgress == value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_fileUploadingProgress = value;
|
||||||
|
Q_EMIT fileUploadingProgressChanged();
|
||||||
|
}
|
||||||
|
|
||||||
void NeoChatRoom::uploadFile(const QUrl &url, const QString &body)
|
void NeoChatRoom::uploadFile(const QUrl &url, const QString &body)
|
||||||
{
|
{
|
||||||
doUploadFile(url, body);
|
doUploadFile(url, body);
|
||||||
@@ -361,30 +388,6 @@ QDateTime NeoChatRoom::lastActiveTime()
|
|||||||
return messageEvents().rbegin()->get()->originTimestamp();
|
return messageEvents().rbegin()->get()->originTimestamp();
|
||||||
}
|
}
|
||||||
|
|
||||||
int NeoChatRoom::savedTopVisibleIndex() const
|
|
||||||
{
|
|
||||||
return firstDisplayedMarker() == historyEdge() ? 0 : int(firstDisplayedMarker() - messageEvents().rbegin());
|
|
||||||
}
|
|
||||||
|
|
||||||
int NeoChatRoom::savedBottomVisibleIndex() const
|
|
||||||
{
|
|
||||||
return lastDisplayedMarker() == historyEdge() ? 0 : int(lastDisplayedMarker() - messageEvents().rbegin());
|
|
||||||
}
|
|
||||||
|
|
||||||
void NeoChatRoom::saveViewport(int topIndex, int bottomIndex)
|
|
||||||
{
|
|
||||||
if (topIndex == -1 || bottomIndex == -1 || (bottomIndex == savedBottomVisibleIndex() && (bottomIndex == 0 || topIndex == savedTopVisibleIndex()))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (bottomIndex == 0) {
|
|
||||||
setFirstDisplayedEventId({});
|
|
||||||
setLastDisplayedEventId({});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setFirstDisplayedEvent(maxTimelineIndex() - topIndex);
|
|
||||||
setLastDisplayedEvent(maxTimelineIndex() - bottomIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariantList NeoChatRoom::getUsers(const QString &keyword, int limit) const
|
QVariantList NeoChatRoom::getUsers(const QString &keyword, int limit) const
|
||||||
{
|
{
|
||||||
const auto userList = users();
|
const auto userList = users();
|
||||||
@@ -418,15 +421,6 @@ QVariantMap NeoChatRoom::getUser(const QString &userID) const
|
|||||||
{QStringLiteral("color"), user.color()}};
|
{QStringLiteral("color"), user.color()}};
|
||||||
}
|
}
|
||||||
|
|
||||||
QUrl NeoChatRoom::urlToMxcUrl(const QUrl &mxcUrl)
|
|
||||||
{
|
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
return connection()->makeMediaUrl(mxcUrl);
|
|
||||||
#else
|
|
||||||
return DownloadFileJob::makeRequestUrl(connection()->homeserver(), mxcUrl);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
QString NeoChatRoom::avatarMediaId() const
|
QString NeoChatRoom::avatarMediaId() const
|
||||||
{
|
{
|
||||||
if (const auto avatar = Room::avatarMediaId(); !avatar.isEmpty()) {
|
if (const auto avatar = Room::avatarMediaId(); !avatar.isEmpty()) {
|
||||||
@@ -468,7 +462,7 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
|
|||||||
fileCaption = e.plainBody() + " | " + fileCaption;
|
fileCaption = e.plainBody() + " | " + fileCaption;
|
||||||
}
|
}
|
||||||
textHandler.setData(fileCaption);
|
textHandler.setData(fileCaption);
|
||||||
return !fileCaption.isEmpty() ? textHandler.handleRecievePlainText() : i18n("a file");
|
return !fileCaption.isEmpty() ? textHandler.handleRecievePlainText(Qt::PlainText, stripNewlines) : i18n("a file");
|
||||||
}
|
}
|
||||||
|
|
||||||
QString body;
|
QString body;
|
||||||
@@ -604,8 +598,12 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
|
|||||||
[](const RoomNameEvent &e) {
|
[](const RoomNameEvent &e) {
|
||||||
return (e.name().isEmpty()) ? i18n("cleared the room name") : i18n("set the room name to: %1", e.name().toHtmlEscaped());
|
return (e.name().isEmpty()) ? i18n("cleared the room name") : i18n("set the room name to: %1", e.name().toHtmlEscaped());
|
||||||
},
|
},
|
||||||
[prettyPrint](const RoomTopicEvent &e) {
|
[prettyPrint, stripNewlines](const RoomTopicEvent &e) {
|
||||||
return (e.topic().isEmpty()) ? i18n("cleared the topic") : i18n("set the topic to: %1", prettyPrint ? Quotient::prettyPrint(e.topic()) : e.topic());
|
return (e.topic().isEmpty()) ? i18n("cleared the topic")
|
||||||
|
: i18n("set the topic to: %1",
|
||||||
|
prettyPrint ? Quotient::prettyPrint(e.topic())
|
||||||
|
: stripNewlines ? e.topic().replace(u'\n', u' ')
|
||||||
|
: e.topic());
|
||||||
},
|
},
|
||||||
[](const RoomAvatarEvent &) {
|
[](const RoomAvatarEvent &) {
|
||||||
return i18n("changed the room avatar");
|
return i18n("changed the room avatar");
|
||||||
@@ -791,30 +789,6 @@ void NeoChatRoom::changeAvatar(const QUrl &localFile)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NeoChatRoom::addLocalAlias(const QString &alias)
|
|
||||||
{
|
|
||||||
auto a = aliases();
|
|
||||||
if (a.contains(alias)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
a += alias;
|
|
||||||
|
|
||||||
setLocalAliases(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NeoChatRoom::removeLocalAlias(const QString &alias)
|
|
||||||
{
|
|
||||||
auto a = aliases();
|
|
||||||
if (!a.contains(alias)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.removeAll(alias);
|
|
||||||
|
|
||||||
setLocalAliases(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString msgTypeToString(MessageEventType msgType)
|
QString msgTypeToString(MessageEventType msgType)
|
||||||
{
|
{
|
||||||
switch (msgType) {
|
switch (msgType) {
|
||||||
@@ -1001,11 +975,6 @@ bool NeoChatRoom::isUserBanned(const QString &user) const
|
|||||||
return getCurrentState<RoomMemberEvent>(user)->membership() == MembershipType::Ban;
|
return getCurrentState<RoomMemberEvent>(user)->membership() == MembershipType::Ban;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString NeoChatRoom::htmlSafeName() const
|
|
||||||
{
|
|
||||||
return name().toHtmlEscaped();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString NeoChatRoom::htmlSafeDisplayName() const
|
QString NeoChatRoom::htmlSafeDisplayName() const
|
||||||
{
|
{
|
||||||
return displayName().toHtmlEscaped();
|
return displayName().toHtmlEscaped();
|
||||||
@@ -1488,6 +1457,11 @@ bool NeoChatRoom::isSpace()
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PushNotificationState::State NeoChatRoom::pushNotificationState() const
|
||||||
|
{
|
||||||
|
return m_currentPushNotificationState;
|
||||||
|
}
|
||||||
|
|
||||||
void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
|
void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
|
||||||
{
|
{
|
||||||
// The caller should never try to set the state to unknown.
|
// The caller should never try to set the state to unknown.
|
||||||
|
|||||||
@@ -3,14 +3,13 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <qobjectdefs.h>
|
|
||||||
#include <room.h>
|
#include <room.h>
|
||||||
|
|
||||||
#include <QCache>
|
#include <QCache>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QTextCursor>
|
#include <QTextCursor>
|
||||||
|
|
||||||
#include <qcoro/task.h>
|
#include <QCoroTask>
|
||||||
|
|
||||||
#include "neochatuser.h"
|
#include "neochatuser.h"
|
||||||
#include "pollhandler.h"
|
#include "pollhandler.h"
|
||||||
@@ -20,36 +19,150 @@ class PushNotificationState : public QObject
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Describes the push notification state for the room.
|
||||||
|
*/
|
||||||
enum State {
|
enum State {
|
||||||
Unknown,
|
Unknown, /**< The state has not yet been obtained from the server. */
|
||||||
Default,
|
Default, /**< The room follows the globally configured rules for the local user. */
|
||||||
Mute,
|
Mute, /**< No notifications for messages in the room. */
|
||||||
MentionKeyword,
|
MentionKeyword, /**< Notifications only for local user mentions and keywords. */
|
||||||
All,
|
All, /**< Notifications for all messages. */
|
||||||
};
|
};
|
||||||
Q_ENUM(State);
|
Q_ENUM(State);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Defines a user mention in the current chat or edit text.
|
||||||
|
*/
|
||||||
struct Mention {
|
struct Mention {
|
||||||
QTextCursor cursor;
|
QTextCursor cursor; /**< Contains the mention's text and position in the text. */
|
||||||
QString text;
|
QString text; /**< The inserted text of the mention. */
|
||||||
int start = 0;
|
int start = 0; /**< Start position of the mention. */
|
||||||
int position = 0;
|
int position = 0; /**< End position of the mention. */
|
||||||
QString id;
|
QString id; /**< The id the mention (used to create link when sending the message). */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class NeoChatRoom
|
||||||
|
*
|
||||||
|
* This class is designed to act as a wrapper over Quotient::Room to provide API and
|
||||||
|
* functionality not available in Quotient::Room.
|
||||||
|
*
|
||||||
|
* The functions fall into two main categories:
|
||||||
|
* - Helper functions to make functionality easily accessible in QML.
|
||||||
|
* - Implement functions not yet available in Quotient::Room.
|
||||||
|
*
|
||||||
|
* @sa Quotient::Room
|
||||||
|
*/
|
||||||
class NeoChatRoom : public Quotient::Room
|
class NeoChatRoom : public Quotient::Room
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A list of users currently typing in the room.
|
||||||
|
*
|
||||||
|
* The list does not include the local user.
|
||||||
|
*
|
||||||
|
* This is different to getting a list of NeoChatUser objects or Quotient::User objects
|
||||||
|
* as neither of those can provide details like the displayName or avatarMediaId
|
||||||
|
* without the room context as these can vary from room to room. This function
|
||||||
|
* provides the room context and puts the result as a list of QVariantMap objects.
|
||||||
|
*
|
||||||
|
* @return a QVariantMap for the user with the following
|
||||||
|
* parameters:
|
||||||
|
* - id - User ID.
|
||||||
|
* - avatarMediaId - Avatar id in the context of this room.
|
||||||
|
* - displayName - Display name in the context of this room.
|
||||||
|
* - display - Name in the context of this room.
|
||||||
|
*
|
||||||
|
* @sa Quotient::User, NeoChatUser
|
||||||
|
*/
|
||||||
Q_PROPERTY(QVariantList usersTyping READ getUsersTyping NOTIFY typingChanged)
|
Q_PROPERTY(QVariantList usersTyping READ getUsersTyping NOTIFY typingChanged)
|
||||||
Q_PROPERTY(bool hasFileUploading READ hasFileUploading WRITE setHasFileUploading NOTIFY hasFileUploadingChanged)
|
|
||||||
Q_PROPERTY(int fileUploadingProgress READ fileUploadingProgress NOTIFY fileUploadingProgressChanged)
|
/**
|
||||||
Q_PROPERTY(QString avatarMediaId READ avatarMediaId NOTIFY avatarChanged STORED false)
|
* @brief Convenience function to get the QDateTime of the last event.
|
||||||
Q_PROPERTY(bool readMarkerLoaded READ readMarkerLoaded NOTIFY readMarkerLoadedChanged)
|
*
|
||||||
|
* @sa lastEvent()
|
||||||
|
*/
|
||||||
Q_PROPERTY(QDateTime lastActiveTime READ lastActiveTime NOTIFY lastActiveTimeChanged)
|
Q_PROPERTY(QDateTime lastActiveTime READ lastActiveTime NOTIFY lastActiveTimeChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether a file is being uploaded to the server.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(bool hasFileUploading READ hasFileUploading WRITE setHasFileUploading NOTIFY hasFileUploadingChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Progress of a file upload as a percentage 0 - 100%.
|
||||||
|
*
|
||||||
|
* The value will be 0 if no file is uploading.
|
||||||
|
*
|
||||||
|
* @sa hasFileUploading
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(int fileUploadingProgress READ fileUploadingProgress NOTIFY fileUploadingProgressChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether the read marker should be shown.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(bool readMarkerLoaded READ readMarkerLoaded NOTIFY readMarkerLoadedChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Display name with any html special characters escaped.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(QString htmlSafeDisplayName READ htmlSafeDisplayName NOTIFY displayNameChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The avatar image to be used for the room.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(QString avatarMediaId READ avatarMediaId NOTIFY avatarChanged STORED false)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get a user object for the other person in a direct chat.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(NeoChatUser *directChatRemoteUser READ directChatRemoteUser CONSTANT)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief If the room is a space.
|
||||||
|
*/
|
||||||
Q_PROPERTY(bool isSpace READ isSpace CONSTANT)
|
Q_PROPERTY(bool isSpace READ isSpace CONSTANT)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether the local user has an invite to the room.
|
||||||
|
*
|
||||||
|
* False for any other state including if the local user is a member.
|
||||||
|
*/
|
||||||
Q_PROPERTY(bool isInvite READ isInvite NOTIFY isInviteChanged)
|
Q_PROPERTY(bool isInvite READ isInvite NOTIFY isInviteChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The current join rule for the room as a QString.
|
||||||
|
*
|
||||||
|
* Possible values are [public, knock, invite, private, restricted].
|
||||||
|
*
|
||||||
|
* @sa https://spec.matrix.org/v1.5/client-server-api/#mroomjoin_rules
|
||||||
|
*/
|
||||||
Q_PROPERTY(QString joinRule READ joinRule WRITE setJoinRule NOTIFY joinRuleChanged)
|
Q_PROPERTY(QString joinRule READ joinRule WRITE setJoinRule NOTIFY joinRuleChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the maximum room version that the server supports.
|
||||||
|
*
|
||||||
|
* Only returns main integer room versions (i.e. no msc room versions).
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(int maxRoomVersion READ maxRoomVersion NOTIFY maxRoomVersionChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The rule for which messages should generate notifications for the room.
|
||||||
|
*
|
||||||
|
* @sa PushNotificationState::State
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(PushNotificationState::State pushNotificationState READ pushNotificationState WRITE setPushNotificationState NOTIFY pushNotificationStateChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The current history visibilty setting for the room.
|
||||||
|
*
|
||||||
|
* Possible values are [invited, joined, shared, world_readable].
|
||||||
|
*
|
||||||
|
* @sa https://spec.matrix.org/v1.5/client-server-api/#room-history-visibility
|
||||||
|
*/
|
||||||
Q_PROPERTY(QString historyVisibility READ historyVisibility WRITE setHistoryVisibility NOTIFY historyVisibilityChanged)
|
Q_PROPERTY(QString historyVisibility READ historyVisibility WRITE setHistoryVisibility NOTIFY historyVisibilityChanged)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,98 +178,441 @@ class NeoChatRoom : public Quotient::Room
|
|||||||
*/
|
*/
|
||||||
Q_PROPERTY(bool urlPreviewEnabled READ urlPreviewEnabled WRITE setUrlPreviewEnabled NOTIFY urlPreviewEnabledChanged)
|
Q_PROPERTY(bool urlPreviewEnabled READ urlPreviewEnabled WRITE setUrlPreviewEnabled NOTIFY urlPreviewEnabledChanged)
|
||||||
|
|
||||||
// Properties for the various permission levels for the room
|
/**
|
||||||
|
* @brief Whether the local user can encrypt the room.
|
||||||
|
*
|
||||||
|
* Requires libQuotient 0.7 compiled with the Quotient_E2EE_ENABLED parameter to
|
||||||
|
* be able to return true.
|
||||||
|
*
|
||||||
|
* A local user can encrypt a room if they have permission to send the m.room.encryption
|
||||||
|
* state event.
|
||||||
|
*
|
||||||
|
* @sa https://spec.matrix.org/v1.5/client-server-api/#mroomencryption
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(bool canEncryptRoom READ canEncryptRoom NOTIFY canEncryptRoomChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The default power level in the room for new users.
|
||||||
|
*/
|
||||||
Q_PROPERTY(int defaultUserPowerLevel READ defaultUserPowerLevel WRITE setDefaultUserPowerLevel NOTIFY defaultUserPowerLevelChanged)
|
Q_PROPERTY(int defaultUserPowerLevel READ defaultUserPowerLevel WRITE setDefaultUserPowerLevel NOTIFY defaultUserPowerLevelChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The power level required to invite users to the room.
|
||||||
|
*/
|
||||||
Q_PROPERTY(int invitePowerLevel READ invitePowerLevel WRITE setInvitePowerLevel NOTIFY invitePowerLevelChanged)
|
Q_PROPERTY(int invitePowerLevel READ invitePowerLevel WRITE setInvitePowerLevel NOTIFY invitePowerLevelChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The power level required to kick users from the room.
|
||||||
|
*/
|
||||||
Q_PROPERTY(int kickPowerLevel READ kickPowerLevel WRITE setKickPowerLevel NOTIFY kickPowerLevelChanged)
|
Q_PROPERTY(int kickPowerLevel READ kickPowerLevel WRITE setKickPowerLevel NOTIFY kickPowerLevelChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The power level required to ban users from the room.
|
||||||
|
*/
|
||||||
Q_PROPERTY(int banPowerLevel READ banPowerLevel WRITE setBanPowerLevel NOTIFY banPowerLevelChanged)
|
Q_PROPERTY(int banPowerLevel READ banPowerLevel WRITE setBanPowerLevel NOTIFY banPowerLevelChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The power level required to delete other user messages.
|
||||||
|
*/
|
||||||
Q_PROPERTY(int redactPowerLevel READ redactPowerLevel WRITE setRedactPowerLevel NOTIFY redactPowerLevelChanged)
|
Q_PROPERTY(int redactPowerLevel READ redactPowerLevel WRITE setRedactPowerLevel NOTIFY redactPowerLevelChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The default power level for state events that are not explicitly specified.
|
||||||
|
*/
|
||||||
Q_PROPERTY(int statePowerLevel READ statePowerLevel WRITE setStatePowerLevel NOTIFY statePowerLevelChanged)
|
Q_PROPERTY(int statePowerLevel READ statePowerLevel WRITE setStatePowerLevel NOTIFY statePowerLevelChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The default power level for event that are not explicitly specified.
|
||||||
|
*/
|
||||||
Q_PROPERTY(int defaultEventPowerLevel READ defaultEventPowerLevel WRITE setDefaultEventPowerLevel NOTIFY defaultEventPowerLevelChanged)
|
Q_PROPERTY(int defaultEventPowerLevel READ defaultEventPowerLevel WRITE setDefaultEventPowerLevel NOTIFY defaultEventPowerLevelChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The power level required to change power levels for the room.
|
||||||
|
*/
|
||||||
Q_PROPERTY(int powerLevelPowerLevel READ powerLevelPowerLevel WRITE setPowerLevelPowerLevel NOTIFY powerLevelPowerLevelChanged)
|
Q_PROPERTY(int powerLevelPowerLevel READ powerLevelPowerLevel WRITE setPowerLevelPowerLevel NOTIFY powerLevelPowerLevelChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The power level required to change the room name.
|
||||||
|
*/
|
||||||
Q_PROPERTY(int namePowerLevel READ namePowerLevel WRITE setNamePowerLevel NOTIFY namePowerLevelChanged)
|
Q_PROPERTY(int namePowerLevel READ namePowerLevel WRITE setNamePowerLevel NOTIFY namePowerLevelChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The power level required to change the room avatar.
|
||||||
|
*/
|
||||||
Q_PROPERTY(int avatarPowerLevel READ avatarPowerLevel WRITE setAvatarPowerLevel NOTIFY avatarPowerLevelChanged)
|
Q_PROPERTY(int avatarPowerLevel READ avatarPowerLevel WRITE setAvatarPowerLevel NOTIFY avatarPowerLevelChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The power level required to change the room aliases.
|
||||||
|
*/
|
||||||
Q_PROPERTY(int canonicalAliasPowerLevel READ canonicalAliasPowerLevel WRITE setCanonicalAliasPowerLevel NOTIFY canonicalAliasPowerLevelChanged)
|
Q_PROPERTY(int canonicalAliasPowerLevel READ canonicalAliasPowerLevel WRITE setCanonicalAliasPowerLevel NOTIFY canonicalAliasPowerLevelChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The power level required to change the room topic.
|
||||||
|
*/
|
||||||
Q_PROPERTY(int topicPowerLevel READ topicPowerLevel WRITE setTopicPowerLevel NOTIFY topicPowerLevelChanged)
|
Q_PROPERTY(int topicPowerLevel READ topicPowerLevel WRITE setTopicPowerLevel NOTIFY topicPowerLevelChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The power level required to encrypt the room.
|
||||||
|
*/
|
||||||
Q_PROPERTY(int encryptionPowerLevel READ encryptionPowerLevel WRITE setEncryptionPowerLevel NOTIFY encryptionPowerLevelChanged)
|
Q_PROPERTY(int encryptionPowerLevel READ encryptionPowerLevel WRITE setEncryptionPowerLevel NOTIFY encryptionPowerLevelChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The power level required to change the room history visibility.
|
||||||
|
*/
|
||||||
Q_PROPERTY(int historyVisibilityPowerLevel READ historyVisibilityPowerLevel WRITE setHistoryVisibilityPowerLevel NOTIFY historyVisibilityPowerLevelChanged)
|
Q_PROPERTY(int historyVisibilityPowerLevel READ historyVisibilityPowerLevel WRITE setHistoryVisibilityPowerLevel NOTIFY historyVisibilityPowerLevelChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The power level required to pin events in the room.
|
||||||
|
*/
|
||||||
Q_PROPERTY(int pinnedEventsPowerLevel READ pinnedEventsPowerLevel WRITE setPinnedEventsPowerLevel NOTIFY pinnedEventsPowerLevelChanged)
|
Q_PROPERTY(int pinnedEventsPowerLevel READ pinnedEventsPowerLevel WRITE setPinnedEventsPowerLevel NOTIFY pinnedEventsPowerLevelChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The power level required to upgrade the room.
|
||||||
|
*/
|
||||||
Q_PROPERTY(int tombstonePowerLevel READ tombstonePowerLevel WRITE setTombstonePowerLevel NOTIFY tombstonePowerLevelChanged)
|
Q_PROPERTY(int tombstonePowerLevel READ tombstonePowerLevel WRITE setTombstonePowerLevel NOTIFY tombstonePowerLevelChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The power level required to set the room server access control list (ACL).
|
||||||
|
*/
|
||||||
Q_PROPERTY(int serverAclPowerLevel READ serverAclPowerLevel WRITE setServerAclPowerLevel NOTIFY serverAclPowerLevelChanged)
|
Q_PROPERTY(int serverAclPowerLevel READ serverAclPowerLevel WRITE setServerAclPowerLevel NOTIFY serverAclPowerLevelChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The power level required to add children to a space.
|
||||||
|
*/
|
||||||
Q_PROPERTY(int spaceChildPowerLevel READ spaceChildPowerLevel WRITE setSpaceChildPowerLevel NOTIFY spaceChildPowerLevelChanged)
|
Q_PROPERTY(int spaceChildPowerLevel READ spaceChildPowerLevel WRITE setSpaceChildPowerLevel NOTIFY spaceChildPowerLevelChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The power level required to set the room parent space.
|
||||||
|
*/
|
||||||
Q_PROPERTY(int spaceParentPowerLevel READ spaceParentPowerLevel WRITE setSpaceParentPowerLevel NOTIFY spaceParentPowerLevelChanged)
|
Q_PROPERTY(int spaceParentPowerLevel READ spaceParentPowerLevel WRITE setSpaceParentPowerLevel NOTIFY spaceParentPowerLevelChanged)
|
||||||
|
|
||||||
Q_PROPERTY(QString htmlSafeDisplayName READ htmlSafeDisplayName NOTIFY displayNameChanged)
|
/**
|
||||||
Q_PROPERTY(PushNotificationState::State pushNotificationState MEMBER m_currentPushNotificationState WRITE setPushNotificationState NOTIFY
|
* @brief The current text in the chatbox for the room.
|
||||||
pushNotificationStateChanged)
|
*
|
||||||
|
* Due to problems with QTextDocument, unlike the other properties here,
|
||||||
// Due to problems with QTextDocument, unlike the other properties here, chatBoxText is *not* used to store the text when switching rooms
|
* chatBoxText is *not* used to store the text when switching rooms.
|
||||||
|
*/
|
||||||
Q_PROPERTY(QString chatBoxText READ chatBoxText WRITE setChatBoxText NOTIFY chatBoxTextChanged)
|
Q_PROPERTY(QString chatBoxText READ chatBoxText WRITE setChatBoxText NOTIFY chatBoxTextChanged)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The text for any message currently being edited in the room.
|
* @brief The text for any message currently being edited in the room.
|
||||||
*/
|
*/
|
||||||
Q_PROPERTY(QString editText READ editText WRITE setEditText NOTIFY editTextChanged)
|
Q_PROPERTY(QString editText READ editText WRITE setEditText NOTIFY editTextChanged)
|
||||||
Q_PROPERTY(QString chatBoxReplyId READ chatBoxReplyId WRITE setChatBoxReplyId NOTIFY chatBoxReplyIdChanged)
|
|
||||||
Q_PROPERTY(QString chatBoxEditId READ chatBoxEditId WRITE setChatBoxEditId NOTIFY chatBoxEditIdChanged)
|
|
||||||
Q_PROPERTY(NeoChatUser *chatBoxReplyUser READ chatBoxReplyUser NOTIFY chatBoxReplyIdChanged)
|
|
||||||
Q_PROPERTY(QString chatBoxReplyMessage READ chatBoxReplyMessage NOTIFY chatBoxReplyIdChanged)
|
|
||||||
Q_PROPERTY(NeoChatUser *chatBoxEditUser READ chatBoxEditUser NOTIFY chatBoxEditIdChanged)
|
|
||||||
Q_PROPERTY(QString chatBoxEditMessage READ chatBoxEditMessage NOTIFY chatBoxEditIdChanged)
|
|
||||||
Q_PROPERTY(QString chatBoxAttachmentPath READ chatBoxAttachmentPath WRITE setChatBoxAttachmentPath NOTIFY chatBoxAttachmentPathChanged)
|
|
||||||
Q_PROPERTY(bool canEncryptRoom READ canEncryptRoom NOTIFY canEncryptRoomChanged)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the maximum room version that the server supports.
|
* @brief The event id of a message being replied to.
|
||||||
*
|
*
|
||||||
* Only returns main integer room versions (i.e. no msc room versions).
|
* Will be QString() if not replying to a message.
|
||||||
*/
|
*/
|
||||||
Q_PROPERTY(int maxRoomVersion READ maxRoomVersion NOTIFY maxRoomVersionChanged)
|
Q_PROPERTY(QString chatBoxReplyId READ chatBoxReplyId WRITE setChatBoxReplyId NOTIFY chatBoxReplyIdChanged)
|
||||||
Q_PROPERTY(NeoChatUser *directChatRemoteUser READ directChatRemoteUser CONSTANT)
|
|
||||||
|
/**
|
||||||
|
* @brief The event id of a message being edited.
|
||||||
|
*
|
||||||
|
* Will be QString() if not editing to a message.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(QString chatBoxEditId READ chatBoxEditId WRITE setChatBoxEditId NOTIFY chatBoxEditIdChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the user object for the message being replied to.
|
||||||
|
*
|
||||||
|
* Returns a nullptr if not replying to a message.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(NeoChatUser *chatBoxReplyUser READ chatBoxReplyUser NOTIFY chatBoxReplyIdChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The content of the message being replied to.
|
||||||
|
*
|
||||||
|
* Will be QString() if not replying to a message.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(QString chatBoxReplyMessage READ chatBoxReplyMessage NOTIFY chatBoxReplyIdChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the user object for the message being edited.
|
||||||
|
*
|
||||||
|
* Returns a nullptr if not editing a message.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(NeoChatUser *chatBoxEditUser READ chatBoxEditUser NOTIFY chatBoxEditIdChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The content of the message being edited.
|
||||||
|
*
|
||||||
|
* Will be QString() if not editing a message.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(QString chatBoxEditMessage READ chatBoxEditMessage NOTIFY chatBoxEditIdChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The file path of the attachment to be sent.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(QString chatBoxAttachmentPath READ chatBoxAttachmentPath WRITE setChatBoxAttachmentPath NOTIFY chatBoxAttachmentPathChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Define the types on inline messages that can be shown.
|
||||||
|
*/
|
||||||
enum MessageType {
|
enum MessageType {
|
||||||
Positive,
|
Positive, /**< Positive message, typically green. */
|
||||||
Info,
|
Info, /**< Info message, typically highlight color. */
|
||||||
Error,
|
Error, /**< Error message, typically red. */
|
||||||
};
|
};
|
||||||
Q_ENUM(MessageType);
|
Q_ENUM(MessageType);
|
||||||
|
|
||||||
explicit NeoChatRoom(Quotient::Connection *connection, QString roomId, Quotient::JoinState joinState = {});
|
explicit NeoChatRoom(Quotient::Connection *connection, QString roomId, Quotient::JoinState joinState = {});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get a list of users in the context of this room.
|
||||||
|
*
|
||||||
|
* This is different to getting a list of NeoChatUser objects or Quotient::User objects
|
||||||
|
* as neither of those can provide details like the displayName or avatarMediaId
|
||||||
|
* without the room context as these can vary from room to room. This function
|
||||||
|
* provides the room context and returns the result as a list of QVariantMap objects.
|
||||||
|
*
|
||||||
|
* @param keyword filters the users based on the displayname containing keyword.
|
||||||
|
* @param limit max number of user returned, -1 is infinite.
|
||||||
|
*
|
||||||
|
* @return a QVariantList containing a QVariantMap for each user with the following
|
||||||
|
* properties:
|
||||||
|
* - id - User ID.
|
||||||
|
* - displayName - Display name in the context of this room.
|
||||||
|
* - avatarMediaId - Avatar id in the context of this room.
|
||||||
|
* - color - Color for the user.
|
||||||
|
*
|
||||||
|
* @sa Quotient::User, NeoChatUser
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE [[nodiscard]] QVariantList getUsers(const QString &keyword, int limit = -1) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get a user in the context of this room.
|
||||||
|
*
|
||||||
|
* This is different to getting a NeoChatUser object or Quotient::User object
|
||||||
|
* as neither of those can provide details like the displayName or avatarMediaId
|
||||||
|
* without the room context as these can vary from room to room. This function
|
||||||
|
* provides the room context and outputs the result as QVariantMap.
|
||||||
|
*
|
||||||
|
* @param userID the ID of the user to output.
|
||||||
|
*
|
||||||
|
* @return a QVariantMap for the user with the following properties:
|
||||||
|
* - id - User ID.
|
||||||
|
* - displayName - Display name in the context of this room.
|
||||||
|
* - avatarMediaId - Avatar id in the context of this room.
|
||||||
|
* - color - Color for the user.
|
||||||
|
*
|
||||||
|
* @sa Quotient::User, NeoChatUser
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE [[nodiscard]] QVariantMap getUser(const QString &userID) const;
|
||||||
|
|
||||||
[[nodiscard]] QVariantList getUsersTyping() const;
|
[[nodiscard]] QVariantList getUsersTyping() const;
|
||||||
|
|
||||||
/// Get the interesting last event.
|
[[nodiscard]] QDateTime lastActiveTime();
|
||||||
///
|
|
||||||
/// This function respect the showLeaveJoinEvent setting and discard
|
/**
|
||||||
/// other not interesting events. This function can return an empty pointer
|
* @brief Get the last interesting event.
|
||||||
/// when the room is empty of RoomMessageEvent.
|
*
|
||||||
|
* This function respects the user's state event setting and discards
|
||||||
|
* other not interesting events.
|
||||||
|
*
|
||||||
|
* @warning This function can return an empty pointer if the room does not have
|
||||||
|
* any RoomMessageEvents loaded.
|
||||||
|
*/
|
||||||
[[nodiscard]] const Quotient::RoomEvent *lastEvent() const;
|
[[nodiscard]] const Quotient::RoomEvent *lastEvent() const;
|
||||||
|
|
||||||
/// Convenient way to get the last event but in a string format.
|
/**
|
||||||
///
|
* @brief Output a string for the message content ready for display.
|
||||||
/// \see lastEvent
|
*
|
||||||
/// \see lastEventIsSpoiler
|
* The output string is dependant upon the event type and the desired output format.
|
||||||
|
*
|
||||||
|
* For most messages this is the body content of the message. For media messages
|
||||||
|
* This will be the caption and for state events it will be a string specific
|
||||||
|
* to that event with some dynamic details about the event added.
|
||||||
|
*
|
||||||
|
* E.g. For a room topic state event the text will be:
|
||||||
|
* "set the topic to: <new topic text>"
|
||||||
|
*
|
||||||
|
* @param evt the event for which a string is desired.
|
||||||
|
* @param format the output format, usually Qt::PlainText or Qt::RichText.
|
||||||
|
* @param stripNewlines whether the output should have new lines in it.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] QString eventToString(const Quotient::RoomEvent &evt, Qt::TextFormat format = Qt::PlainText, bool stripNewlines = false) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Output a generic string for the message content ready for display.
|
||||||
|
*
|
||||||
|
* The output string is dependant upon the event type.
|
||||||
|
*
|
||||||
|
* Unlike NeoChatRoom::eventToString the string is the same for all events of
|
||||||
|
* the same type
|
||||||
|
*
|
||||||
|
* E.g. For a message the text will be:
|
||||||
|
* "sent a message"
|
||||||
|
*
|
||||||
|
* @sa eventToString()
|
||||||
|
*/
|
||||||
|
[[nodiscard]] QString eventToGenericString(const Quotient::RoomEvent &evt) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Convenient way to call eventToString on the last event.
|
||||||
|
*
|
||||||
|
* @sa lastEvent()
|
||||||
|
* @sa eventToString()
|
||||||
|
*/
|
||||||
[[nodiscard]] QString lastEventToString(Qt::TextFormat format = Qt::PlainText, bool stripNewlines = false) const;
|
[[nodiscard]] QString lastEventToString(Qt::TextFormat format = Qt::PlainText, bool stripNewlines = false) const;
|
||||||
|
|
||||||
/// Convenient way to check if the last event looks like it has spoilers.
|
/**
|
||||||
///
|
* @brief Convenient way to check if the last event looks like it has spoilers.
|
||||||
/// \see lastEvent
|
*
|
||||||
/// \see lastEventToString
|
* This does a basic check to see if the message contains a data-mx-spoiler
|
||||||
|
* attribute within the text which makes it likely that the message has a spoiler
|
||||||
|
* section. However this is not 100% reliable as during parsing it may be
|
||||||
|
* removed if used within an illegal tag or on a tag for which data-mx-spoiler
|
||||||
|
* is not a valid attribute.
|
||||||
|
*
|
||||||
|
* @sa lastEvent()
|
||||||
|
*/
|
||||||
[[nodiscard]] bool lastEventIsSpoiler() const;
|
[[nodiscard]] bool lastEventIsSpoiler() const;
|
||||||
|
|
||||||
/// Convenient way to get the QDateTime of the last event.
|
[[nodiscard]] bool hasFileUploading() const;
|
||||||
///
|
void setHasFileUploading(bool value);
|
||||||
/// \see lastEvent
|
|
||||||
[[nodiscard]] QDateTime lastActiveTime();
|
[[nodiscard]] int fileUploadingProgress() const;
|
||||||
|
void setFileUploadingProgress(int value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Download a file for the given event to a local file location.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE void download(const QString &eventId, const QUrl &localFilename = {});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Download a file for the given event as a temporary file.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE bool downloadTempFile(const QString &eventId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if the given event is highlighted.
|
||||||
|
*
|
||||||
|
* An event is highlighted if it contains the local user's id but was not sent by the
|
||||||
|
* local user.
|
||||||
|
*/
|
||||||
|
bool isEventHighlighted(const Quotient::RoomEvent *e) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Convenience function to find out if the room contains the given user.
|
||||||
|
*
|
||||||
|
* A room contains the user if the user can be found and their JoinState is
|
||||||
|
* not JoinState::Leave.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE [[nodiscard]] bool containsUser(const QString &userID) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief True if the given user ID is banned from the room.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE [[nodiscard]] bool isUserBanned(const QString &user) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief True if the local user can send the given event type.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE [[nodiscard]] bool canSendEvent(const QString &eventType) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief True if the local user can send the given state event type.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE [[nodiscard]] bool canSendState(const QString &eventType) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Send a report to the server for an event.
|
||||||
|
*
|
||||||
|
* @param eventId the ID of the event being reported.
|
||||||
|
* @param reason the reason given for reporting the event.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE void reportEvent(const QString &eventId, const QString &reason);
|
||||||
|
|
||||||
|
[[nodiscard]] bool readMarkerLoaded() const;
|
||||||
|
|
||||||
|
QString htmlSafeDisplayName() const;
|
||||||
|
|
||||||
|
#ifndef QUOTIENT_07
|
||||||
|
/**
|
||||||
|
* @brief Get a display name for the user with html escaped.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE QString htmlSafeMemberName(const QString &userId) const
|
||||||
|
{
|
||||||
|
return safeMemberName(userId).toHtmlEscaped();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get subtitle text for room
|
||||||
|
*
|
||||||
|
* Fetches last event and removes markdown formatting
|
||||||
|
*
|
||||||
|
* @see lastEventToString()
|
||||||
|
*/
|
||||||
|
[[nodiscard]] QString subtitleText();
|
||||||
|
|
||||||
|
[[nodiscard]] QString avatarMediaId() const;
|
||||||
|
|
||||||
|
NeoChatUser *directChatRemoteUser() const;
|
||||||
|
|
||||||
[[nodiscard]] bool isSpace();
|
[[nodiscard]] bool isSpace();
|
||||||
|
|
||||||
bool isEventHighlighted(const Quotient::RoomEvent *e) const;
|
bool isInvite() const;
|
||||||
|
|
||||||
|
Q_INVOKABLE void clearInvitationNotification();
|
||||||
|
|
||||||
[[nodiscard]] QString joinRule() const;
|
[[nodiscard]] QString joinRule() const;
|
||||||
void setJoinRule(const QString &joinRule);
|
void setJoinRule(const QString &joinRule);
|
||||||
|
|
||||||
|
int maxRoomVersion() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Map an alias to the room and publish.
|
||||||
|
*
|
||||||
|
* The alias is first mapped to the room and then published as an
|
||||||
|
* alternate alias. Publishing the alias will fail if the user does not have
|
||||||
|
* permission to send m.room.canonical_alias event messages.
|
||||||
|
*
|
||||||
|
* @note This is different to Quotient::Room::setLocalAliases() as that can only
|
||||||
|
* get the room to publish an alias that is already mapped.
|
||||||
|
*
|
||||||
|
* @property alias QString in the form #new_alias:server.org
|
||||||
|
*
|
||||||
|
* @sa Quotient::Room::setLocalAliases()
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE void mapAlias(const QString &alias);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Unmap an alias from the room.
|
||||||
|
*
|
||||||
|
* An unmapped alias is also removed as either the canonical alias or an alternate
|
||||||
|
* alias.
|
||||||
|
*
|
||||||
|
* @note This is different to Quotient::Room::setLocalAliases() as that can only
|
||||||
|
* get the room to un-publish an alias, while the mapping still exists.
|
||||||
|
*
|
||||||
|
* @property alias QString in the form #mapped_alias:server.org
|
||||||
|
*
|
||||||
|
* @sa Quotient::Room::setLocalAliases()
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE void unmapAlias(const QString &alias);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the canonical alias of the room to an available mapped alias.
|
||||||
|
*
|
||||||
|
* If the new alias was already published as an alternate alias it will be removed
|
||||||
|
* from that list.
|
||||||
|
*
|
||||||
|
* @note This is an overload of the function Quotient::Room::setCanonicalAlias().
|
||||||
|
* This is to provide the functionality to remove the new canonical alias as a
|
||||||
|
* published alt alias when set.
|
||||||
|
*
|
||||||
|
* @property newAlias QString in the form #new_alias:server.org
|
||||||
|
*
|
||||||
|
* @sa Quotient::Room::setCanonicalAlias()
|
||||||
|
* */
|
||||||
|
Q_INVOKABLE void setCanonicalAlias(const QString &newAlias);
|
||||||
|
|
||||||
|
PushNotificationState::State pushNotificationState() const;
|
||||||
|
void setPushNotificationState(PushNotificationState::State state);
|
||||||
|
|
||||||
[[nodiscard]] QString historyVisibility() const;
|
[[nodiscard]] QString historyVisibility() const;
|
||||||
void setHistoryVisibility(const QString &historyVisibilityRule);
|
void setHistoryVisibility(const QString &historyVisibilityRule);
|
||||||
|
|
||||||
@@ -166,6 +622,8 @@ public:
|
|||||||
[[nodiscard]] bool urlPreviewEnabled() const;
|
[[nodiscard]] bool urlPreviewEnabled() const;
|
||||||
void setUrlPreviewEnabled(const bool &urlPreviewEnabled);
|
void setUrlPreviewEnabled(const bool &urlPreviewEnabled);
|
||||||
|
|
||||||
|
bool canEncryptRoom() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the power level for the given user ID in the room.
|
* @brief Get the power level for the given user ID in the room.
|
||||||
*
|
*
|
||||||
@@ -236,65 +694,6 @@ public:
|
|||||||
[[nodiscard]] int spaceParentPowerLevel() const;
|
[[nodiscard]] int spaceParentPowerLevel() const;
|
||||||
void setSpaceParentPowerLevel(const int &newPowerLevel);
|
void setSpaceParentPowerLevel(const int &newPowerLevel);
|
||||||
|
|
||||||
[[nodiscard]] bool hasFileUploading() const
|
|
||||||
{
|
|
||||||
return m_hasFileUploading;
|
|
||||||
}
|
|
||||||
void setHasFileUploading(bool value)
|
|
||||||
{
|
|
||||||
if (value == m_hasFileUploading) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_hasFileUploading = value;
|
|
||||||
Q_EMIT hasFileUploadingChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] int fileUploadingProgress() const
|
|
||||||
{
|
|
||||||
return m_fileUploadingProgress;
|
|
||||||
}
|
|
||||||
void setFileUploadingProgress(int value)
|
|
||||||
{
|
|
||||||
if (m_fileUploadingProgress == value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_fileUploadingProgress = value;
|
|
||||||
Q_EMIT fileUploadingProgressChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] bool readMarkerLoaded() const;
|
|
||||||
|
|
||||||
Q_INVOKABLE [[nodiscard]] int savedTopVisibleIndex() const;
|
|
||||||
Q_INVOKABLE [[nodiscard]] int savedBottomVisibleIndex() const;
|
|
||||||
Q_INVOKABLE void saveViewport(int topIndex, int bottomIndex);
|
|
||||||
|
|
||||||
Q_INVOKABLE [[nodiscard]] QVariantList getUsers(const QString &keyword, int limit = -1) const;
|
|
||||||
Q_INVOKABLE [[nodiscard]] QVariantMap getUser(const QString &userID) const;
|
|
||||||
|
|
||||||
Q_INVOKABLE QUrl urlToMxcUrl(const QUrl &mxcUrl);
|
|
||||||
|
|
||||||
[[nodiscard]] QString avatarMediaId() const;
|
|
||||||
|
|
||||||
[[nodiscard]] QString eventToString(const Quotient::RoomEvent &evt, Qt::TextFormat format = Qt::PlainText, bool stripNewlines = false) const;
|
|
||||||
[[nodiscard]] QString eventToGenericString(const Quotient::RoomEvent &evt) const;
|
|
||||||
|
|
||||||
Q_INVOKABLE [[nodiscard]] bool containsUser(const QString &userID) const;
|
|
||||||
Q_INVOKABLE [[nodiscard]] bool isUserBanned(const QString &user) const;
|
|
||||||
|
|
||||||
Q_INVOKABLE [[nodiscard]] bool canSendEvent(const QString &eventType) const;
|
|
||||||
Q_INVOKABLE [[nodiscard]] bool canSendState(const QString &eventType) const;
|
|
||||||
|
|
||||||
bool isInvite() const;
|
|
||||||
|
|
||||||
Q_INVOKABLE QString htmlSafeName() const;
|
|
||||||
Q_INVOKABLE QString htmlSafeDisplayName() const;
|
|
||||||
Q_INVOKABLE void clearInvitationNotification();
|
|
||||||
Q_INVOKABLE void reportEvent(const QString &eventId, const QString &reason);
|
|
||||||
|
|
||||||
Q_INVOKABLE void setPushNotificationState(PushNotificationState::State state);
|
|
||||||
|
|
||||||
Q_INVOKABLE void download(const QString &eventId, const QUrl &localFilename = {});
|
|
||||||
|
|
||||||
QString chatBoxText() const;
|
QString chatBoxText() const;
|
||||||
void setChatBoxText(const QString &text);
|
void setChatBoxText(const QString &text);
|
||||||
|
|
||||||
@@ -316,44 +715,40 @@ public:
|
|||||||
QString chatBoxAttachmentPath() const;
|
QString chatBoxAttachmentPath() const;
|
||||||
void setChatBoxAttachmentPath(const QString &attachmentPath);
|
void setChatBoxAttachmentPath(const QString &attachmentPath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retrieve the mentions for the current chatbox text.
|
||||||
|
*/
|
||||||
QVector<Mention> *mentions();
|
QVector<Mention> *mentions();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Vector of mentions in the current edit text.
|
* @brief Retrieve the mentions for the current edit text.
|
||||||
*/
|
*/
|
||||||
QVector<Mention> *editMentions();
|
QVector<Mention> *editMentions();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the saved chatbox text for the room.
|
||||||
|
*/
|
||||||
QString savedText() const;
|
QString savedText() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Save the chatbox text for the room.
|
||||||
|
*/
|
||||||
void setSavedText(const QString &savedText);
|
void setSavedText(const QString &savedText);
|
||||||
|
|
||||||
bool canEncryptRoom() const;
|
|
||||||
|
|
||||||
Q_INVOKABLE bool downloadTempFile(const QString &eventId);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Map an alias to the room
|
|
||||||
*
|
|
||||||
* Note: this is different to setLocalAliases as that can only
|
|
||||||
* get the room to publish and alias that is already mapped.
|
|
||||||
*/
|
|
||||||
Q_INVOKABLE void mapAlias(const QString &alias);
|
|
||||||
Q_INVOKABLE void unmapAlias(const QString &alias);
|
|
||||||
Q_INVOKABLE void setCanonicalAlias(const QString &newAlias);
|
|
||||||
|
|
||||||
#ifdef QUOTIENT_07
|
#ifdef QUOTIENT_07
|
||||||
|
/**
|
||||||
|
* @brief Get a PollHandler object for the given event Id.
|
||||||
|
*
|
||||||
|
* Will return an existing PollHandler if one already exists for the event ID.
|
||||||
|
* A new PollHandler will be created if one doesn't exist.
|
||||||
|
*
|
||||||
|
* @note Requires libQuotient 0.7.
|
||||||
|
*
|
||||||
|
* @sa PollHandler
|
||||||
|
*/
|
||||||
Q_INVOKABLE PollHandler *poll(const QString &eventId);
|
Q_INVOKABLE PollHandler *poll(const QString &eventId);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef QUOTIENT_07
|
|
||||||
Q_INVOKABLE QString htmlSafeMemberName(const QString &userId) const
|
|
||||||
{
|
|
||||||
return safeMemberName(userId).toHtmlEscaped();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int maxRoomVersion() const;
|
|
||||||
NeoChatUser *directChatRemoteUser() const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QSet<const Quotient::RoomEvent *> highlights;
|
QSet<const Quotient::RoomEvent *> highlights;
|
||||||
|
|
||||||
@@ -432,26 +827,78 @@ Q_SIGNALS:
|
|||||||
void spaceParentPowerLevelChanged();
|
void spaceParentPowerLevelChanged();
|
||||||
|
|
||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
|
/**
|
||||||
|
* @brief Upload a file to the matrix server and post the file to the room.
|
||||||
|
*
|
||||||
|
* @param url the location of the file to be uploaded.
|
||||||
|
* @param body the caption that is to be given to the file.
|
||||||
|
*/
|
||||||
void uploadFile(const QUrl &url, const QString &body = QString());
|
void uploadFile(const QUrl &url, const QString &body = QString());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Accept an invitation for the local user to join the room.
|
||||||
|
*/
|
||||||
void acceptInvitation();
|
void acceptInvitation();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Leave and forget the room for the local user.
|
||||||
|
*
|
||||||
|
* @note This means that not only will the user no longer receive events in
|
||||||
|
* the room but the will forget any history up to this point.
|
||||||
|
*
|
||||||
|
* @sa https://spec.matrix.org/latest/client-server-api/#leaving-rooms
|
||||||
|
*/
|
||||||
void forget();
|
void forget();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the typing notification state on the room for the local user.
|
||||||
|
*/
|
||||||
void sendTypingNotification(bool isTyping);
|
void sendTypingNotification(bool isTyping);
|
||||||
|
|
||||||
/// @param rawText The text as it was typed.
|
/**
|
||||||
/// @param cleanedText The text with link to the users.
|
* @brief Send a message to the room.
|
||||||
|
*
|
||||||
|
* @param rawText the text as it was typed.
|
||||||
|
* @param cleanedText the text marked up as html.
|
||||||
|
* @param type the type of message being sent.
|
||||||
|
* @param replyEventId the id of the message being replied to if a reply.
|
||||||
|
* @param relateToEventId the id of the message being edited if an edit.
|
||||||
|
*/
|
||||||
void postMessage(const QString &rawText,
|
void postMessage(const QString &rawText,
|
||||||
const QString &cleanedText,
|
const QString &cleanedText,
|
||||||
Quotient::MessageEventType type = Quotient::MessageEventType::Text,
|
Quotient::MessageEventType type = Quotient::MessageEventType::Text,
|
||||||
const QString &replyEventId = QString(),
|
const QString &replyEventId = QString(),
|
||||||
const QString &relateToEventId = QString());
|
const QString &relateToEventId = QString());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Send an html message to the room.
|
||||||
|
*
|
||||||
|
* @param text the text as it was typed.
|
||||||
|
* @param html the text marked up as html.
|
||||||
|
* @param type the type of message being sent.
|
||||||
|
* @param replyEventId the id of the message being replied to if a reply.
|
||||||
|
* @param relateToEventId the id of the message being edited if an edit.
|
||||||
|
*/
|
||||||
void postHtmlMessage(const QString &text,
|
void postHtmlMessage(const QString &text,
|
||||||
const QString &html,
|
const QString &html,
|
||||||
Quotient::MessageEventType type = Quotient::MessageEventType::Text,
|
Quotient::MessageEventType type = Quotient::MessageEventType::Text,
|
||||||
const QString &replyEventId = QString(),
|
const QString &replyEventId = QString(),
|
||||||
const QString &relateToEventId = QString());
|
const QString &relateToEventId = QString());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the room avatar.
|
||||||
|
*/
|
||||||
void changeAvatar(const QUrl &localFile);
|
void changeAvatar(const QUrl &localFile);
|
||||||
void addLocalAlias(const QString &alias);
|
|
||||||
void removeLocalAlias(const QString &alias);
|
/**
|
||||||
|
* @brief Toggle the reaction state of the given reaction for the local user.
|
||||||
|
*/
|
||||||
void toggleReaction(const QString &eventId, const QString &reaction);
|
void toggleReaction(const QString &eventId, const QString &reaction);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Delete recent messages by the given user.
|
||||||
|
*
|
||||||
|
* This will delete all messages by that user in this room that are currently loaded.
|
||||||
|
*/
|
||||||
void deleteMessagesByUser(const QString &user, const QString &reason);
|
void deleteMessagesByUser(const QString &user, const QString &reason);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ QQC2.Control {
|
|||||||
if (event.key === Qt.Key_V && event.modifiers & Qt.ControlModifier) {
|
if (event.key === Qt.Key_V && event.modifiers & Qt.ControlModifier) {
|
||||||
chatBar.pasteImage();
|
chatBar.pasteImage();
|
||||||
} else if (event.key === Qt.Key_Up && event.modifiers & Qt.ControlModifier) {
|
} else if (event.key === Qt.Key_Up && event.modifiers & Qt.ControlModifier) {
|
||||||
let replyEvent = messageEventModel.getLatestMessageFromIndex(0)
|
let replyEvent = messageEventModel.getLatestMessageFromRow(0)
|
||||||
if (replyEvent && replyEvent["event_id"]) {
|
if (replyEvent && replyEvent["event_id"]) {
|
||||||
currentRoom.chatBoxReplyId = replyEvent["event_id"]
|
currentRoom.chatBoxReplyId = replyEvent["event_id"]
|
||||||
}
|
}
|
||||||
@@ -394,6 +394,8 @@ QQC2.Control {
|
|||||||
cursorPosition: textField.cursorPosition
|
cursorPosition: textField.cursorPosition
|
||||||
selectionStart: textField.selectionStart
|
selectionStart: textField.selectionStart
|
||||||
selectionEnd: textField.selectionEnd
|
selectionEnd: textField.selectionEnd
|
||||||
|
mentionColor: Kirigami.Theme.linkColor
|
||||||
|
errorColor: Kirigami.Theme.negativeTextColor
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
RoomManager.chatDocumentHandler = documentHandler;
|
RoomManager.chatDocumentHandler = documentHandler;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,115 +1,132 @@
|
|||||||
// SPDX-FileCopyrightText: 2021 Carson Black <uhhadd@gmail.com>
|
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
import QtQuick 2.15
|
import QtQuick 2.15
|
||||||
import QtQuick.Layouts 1.10
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
import QtQuick.Controls 2.12 as QQC2
|
import QtQuick.Layouts 1.15
|
||||||
import org.kde.kirigami 2.14 as Kirigami
|
|
||||||
|
import org.kde.kirigami 2.20 as Kirigami
|
||||||
import org.kde.kitemmodels 1.0
|
import org.kde.kitemmodels 1.0
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
QQC2.Popup {
|
QQC2.Dialog {
|
||||||
id: _popup
|
id: root
|
||||||
|
|
||||||
|
parent: applicationWindow().overlay
|
||||||
|
width: Math.min(700, parent.width)
|
||||||
|
height: 400
|
||||||
|
|
||||||
|
leftPadding: 0
|
||||||
|
rightPadding: 0
|
||||||
|
bottomPadding: 0
|
||||||
|
topPadding: 0
|
||||||
|
|
||||||
|
anchors.centerIn: applicationWindow().overlay
|
||||||
|
|
||||||
|
Keys.forwardTo: searchField
|
||||||
|
|
||||||
Shortcut {
|
Shortcut {
|
||||||
sequence: "Ctrl+K"
|
sequence: "Ctrl+K"
|
||||||
enabled: !Kirigami.Settings.hasPlatformMenuBar
|
onActivated: root.open()
|
||||||
onActivated: _popup.open()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
quickSearch.forceActiveFocus()
|
searchField.forceActiveFocus()
|
||||||
quickSearch.text = ""
|
searchField.text = ""
|
||||||
|
roomList.currentIndex = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
anchors.centerIn: QQC2.Overlay.overlay
|
header: Kirigami.SearchField {
|
||||||
background: Kirigami.Card {}
|
id: searchField
|
||||||
height: 2 * Math.round(implicitHeight / 2)
|
Keys.onDownPressed: {
|
||||||
padding: Kirigami.Units.largeSpacing * 2
|
roomList.forceActiveFocus()
|
||||||
|
if (roomList.currentIndex < roomList.count - 1) {
|
||||||
contentItem: ColumnLayout {
|
roomList.currentIndex++
|
||||||
spacing: Kirigami.Units.largeSpacing * 2
|
} else {
|
||||||
|
roomList.currentIndex = 0
|
||||||
Kirigami.SearchField {
|
|
||||||
id: quickSearch
|
|
||||||
|
|
||||||
// TODO: get this broken property removed/disabled by default in Kirigami,
|
|
||||||
// we used to be able to expect that the text field wouldn't attempt to
|
|
||||||
// perform a mini-DDOS attack using signals.
|
|
||||||
autoAccept: false
|
|
||||||
/**
|
|
||||||
* The focus is manged by the popup and we don't want to use the standard
|
|
||||||
* shortcut as it could block other SearchFields from using it.
|
|
||||||
*/
|
|
||||||
focusSequence: ""
|
|
||||||
|
|
||||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 21 // 3 * 7 = 21, roughly 7 avatars on screen
|
|
||||||
Keys.onLeftPressed: cView.decrementCurrentIndex()
|
|
||||||
Keys.onRightPressed: cView.incrementCurrentIndex()
|
|
||||||
onAccepted: {
|
|
||||||
const item = cView.itemAtIndex(cView.currentIndex)
|
|
||||||
|
|
||||||
RoomManager.enterRoom(item.currentRoom)
|
|
||||||
|
|
||||||
_popup.close()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Keys.onUpPressed: {
|
||||||
|
if (roomList.currentIndex === 0) {
|
||||||
|
roomList.currentIndex = roomList.count - 1
|
||||||
|
} else {
|
||||||
|
roomList.currentIndex--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Keys.onEnterPressed: {
|
||||||
|
RoomManager.enterRoom(roomList.currentItem.currentRoom);
|
||||||
|
root.close();
|
||||||
|
}
|
||||||
|
Keys.onReturnPressed: {
|
||||||
|
RoomManager.enterRoom(roomList.currentItem.currentRoom);
|
||||||
|
root.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.ScrollView {
|
||||||
|
anchors.fill: parent
|
||||||
ListView {
|
ListView {
|
||||||
id: cView
|
id: roomList
|
||||||
|
|
||||||
orientation: Qt.Horizontal
|
|
||||||
spacing: Kirigami.Units.largeSpacing
|
|
||||||
|
|
||||||
|
currentIndex: 0
|
||||||
|
highlightMoveDuration: 200
|
||||||
|
Keys.forwardTo: searchField
|
||||||
|
keyNavigationEnabled: true
|
||||||
model: SortFilterRoomListModel {
|
model: SortFilterRoomListModel {
|
||||||
id: sortFilterRoomListModel
|
filterText: searchField.text
|
||||||
sourceModel: RoomListModel {
|
sourceModel: RoomListModel {
|
||||||
id: roomListModel
|
id: roomListModel
|
||||||
connection: Controller.activeConnection
|
connection: Controller.activeConnection
|
||||||
}
|
}
|
||||||
filterText: quickSearch.text
|
|
||||||
roomSortOrder: SortFilterRoomListModel.LastActivity
|
|
||||||
}
|
}
|
||||||
|
delegate: Kirigami.BasicListItem {
|
||||||
|
id: roomListItem
|
||||||
|
|
||||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 3
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
delegate: Kirigami.Avatar {
|
|
||||||
id: del
|
|
||||||
|
|
||||||
implicitHeight: Kirigami.Units.gridUnit * 3
|
|
||||||
implicitWidth: Kirigami.Units.gridUnit * 3
|
|
||||||
|
|
||||||
required property string avatar
|
|
||||||
required property var currentRoom
|
required property var currentRoom
|
||||||
|
required property string name
|
||||||
required property int index
|
required property int index
|
||||||
|
required property int unreadCount
|
||||||
|
required property string subtitleText
|
||||||
|
required property string avatar
|
||||||
|
|
||||||
name: currentRoom.displayName
|
topPadding: Kirigami.Units.largeSpacing
|
||||||
|
bottomPadding: Kirigami.Units.largeSpacing
|
||||||
// When an item is hovered set the currentIndex of listview to it so that it is highlighted
|
highlighted: roomList.currentIndex === roomListItem.index
|
||||||
onHoveredChanged: {
|
focus: true
|
||||||
if (!hovered) {
|
icon: undefined
|
||||||
return
|
onClicked: {
|
||||||
}
|
RoomManager.enterRoom(roomListItem.currentRoom);
|
||||||
cView.currentIndex = index
|
root.close()
|
||||||
|
}
|
||||||
|
Keys.onEnterPressed: {
|
||||||
|
RoomManager.enterRoom(roomListItem.currentRoom);
|
||||||
|
root.close();
|
||||||
|
}
|
||||||
|
Keys.onReturnPressed: {
|
||||||
|
RoomManager.enterRoom(roomListItem.currentRoom);
|
||||||
|
root.close();
|
||||||
|
}
|
||||||
|
bold: roomListItem.unreadCount > 0
|
||||||
|
label: roomListItem.name ?? ""
|
||||||
|
labelItem.textFormat: Text.PlainText
|
||||||
|
subtitle: roomListItem.subtitleText
|
||||||
|
subtitleItem.textFormat: Text.PlainText
|
||||||
|
onPressAndHold: {
|
||||||
|
createRoomListContextMenu()
|
||||||
}
|
}
|
||||||
|
|
||||||
actions.main: Kirigami.Action {
|
leading: Kirigami.Avatar {
|
||||||
id: enterRoomAction
|
source: roomListItem.avatar ? "image://mxc/" + roomListItem.avatar : ""
|
||||||
onTriggered: {
|
name: roomListItem.name || i18n("No Name")
|
||||||
RoomManager.enterRoom(currentRoom);
|
implicitWidth: height
|
||||||
|
sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
||||||
_popup.close()
|
sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
source: avatar != "" ? "image://mxc/" + avatar : ""
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
modal: true
|
|
||||||
focus: true
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import org.kde.kirigami 2.15 as Kirigami
|
|||||||
import org.kde.neochat 1.0
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
DelegateChooser {
|
DelegateChooser {
|
||||||
role: "eventType"
|
role: "delegateType"
|
||||||
|
|
||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
roleValue: MessageEventModel.State
|
roleValue: MessageEventModel.State
|
||||||
@@ -20,9 +20,7 @@ DelegateChooser {
|
|||||||
|
|
||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
roleValue: MessageEventModel.Emote
|
roleValue: MessageEventModel.Emote
|
||||||
delegate: MessageDelegate {
|
delegate: MessageDelegate {}
|
||||||
isEmote: true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
@@ -75,6 +73,11 @@ DelegateChooser {
|
|||||||
delegate: PollDelegate {}
|
delegate: PollDelegate {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DelegateChoice {
|
||||||
|
roleValue: MessageEventModel.Location
|
||||||
|
delegate: LocationDelegate {}
|
||||||
|
}
|
||||||
|
|
||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
roleValue: MessageEventModel.Other
|
roleValue: MessageEventModel.Other
|
||||||
delegate: Item {}
|
delegate: Item {}
|
||||||
|
|||||||
@@ -110,14 +110,18 @@ TimelineContainer {
|
|||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
spacing: 0
|
spacing: 0
|
||||||
QQC2.Label {
|
QQC2.Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
text: model.display
|
text: model.display
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
QQC2.Label {
|
QQC2.Label {
|
||||||
id: sizeLabel
|
id: sizeLabel
|
||||||
|
Layout.fillWidth: true
|
||||||
text: Controller.formatByteSize(content.info ? content.info.size : 0)
|
text: Controller.formatByteSize(content.info ? content.info.size : 0)
|
||||||
opacity: 0.7
|
opacity: 0.7
|
||||||
|
elide: Text.ElideRight
|
||||||
|
maximumLineCount: 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
90
src/qml/Component/Timeline/LocationDelegate.qml
Normal file
90
src/qml/Component/Timeline/LocationDelegate.qml
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import QtLocation 5.15
|
||||||
|
import QtPositioning 5.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
|
TimelineContainer {
|
||||||
|
id: locationDelegate
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.maximumWidth: locationDelegate.contentMaxWidth
|
||||||
|
Layout.preferredWidth: locationDelegate.contentMaxWidth
|
||||||
|
Map {
|
||||||
|
id: map
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: locationDelegate.contentMaxWidth / 16 * 9
|
||||||
|
|
||||||
|
center: QtPositioning.coordinate(model.latitude, model.longitude)
|
||||||
|
zoomLevel: 15
|
||||||
|
plugin: Plugin {
|
||||||
|
name: "osm"
|
||||||
|
PluginParameter {
|
||||||
|
name: "osm.useragent"
|
||||||
|
value: Application.name + "/" + Application.version + " (kde-devel@kde.org)"
|
||||||
|
}
|
||||||
|
PluginParameter {
|
||||||
|
name: "osm.mapping.providersrepository.address"
|
||||||
|
value: "https://autoconfig.kde.org/qtlocation/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onCopyrightLinkActivated: Qt.openUrlExternally(link)
|
||||||
|
|
||||||
|
|
||||||
|
MapQuickItem {
|
||||||
|
id: point
|
||||||
|
|
||||||
|
anchorPoint.x: sourceItem.width / 2
|
||||||
|
anchorPoint.y: sourceItem.height
|
||||||
|
coordinate: QtPositioning.coordinate(model.latitude, model.longitude)
|
||||||
|
autoFadeIn: false
|
||||||
|
|
||||||
|
sourceItem: Kirigami.Icon {
|
||||||
|
width: height
|
||||||
|
height: Kirigami.Units.iconSizes.huge
|
||||||
|
source: "gps"
|
||||||
|
isMask: true
|
||||||
|
color: Kirigami.Theme.highlightColor
|
||||||
|
|
||||||
|
Kirigami.Icon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
anchors.verticalCenterOffset: -parent.height / 8
|
||||||
|
visible: model.asset === "m.pin"
|
||||||
|
width: height
|
||||||
|
height: parent.height / 3 + 1
|
||||||
|
source: "pin"
|
||||||
|
isMask: true
|
||||||
|
color: Kirigami.Theme.highlightColor
|
||||||
|
}
|
||||||
|
Kirigami.Avatar {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
anchors.verticalCenterOffset: -parent.height / 8
|
||||||
|
visible: model.asset === "m.self"
|
||||||
|
width: height
|
||||||
|
height: parent.height / 3 + 1
|
||||||
|
name: model.author.name ?? model.author.displayName
|
||||||
|
source: model.author.avatarMediaId ? ("image://mxc/" + model.author.avatarMediaId) : ""
|
||||||
|
color: model.author.color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TapHandler {
|
||||||
|
acceptedButtons: Qt.LeftButton
|
||||||
|
onLongPressed: openMessageContext(model, "", model.message)
|
||||||
|
}
|
||||||
|
TapHandler {
|
||||||
|
acceptedButtons: Qt.RightButton
|
||||||
|
onTapped: openMessageContext(model, "", model.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,6 @@ import org.kde.neochat 1.0
|
|||||||
TimelineContainer {
|
TimelineContainer {
|
||||||
id: messageDelegate
|
id: messageDelegate
|
||||||
|
|
||||||
property bool isEmote: false
|
|
||||||
onOpenContextMenu: openMessageContext(model, label.selectedText, Controller.plainText(label.textDocument))
|
onOpenContextMenu: openMessageContext(model, label.selectedText, Controller.plainText(label.textDocument))
|
||||||
|
|
||||||
innerObject: ColumnLayout {
|
innerObject: ColumnLayout {
|
||||||
@@ -22,7 +21,6 @@ TimelineContainer {
|
|||||||
id: label
|
id: label
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
visible: currentRoom.chatBoxEditId !== model.eventId
|
visible: currentRoom.chatBoxEditId !== model.eventId
|
||||||
isEmote: messageDelegate.isEmote
|
|
||||||
}
|
}
|
||||||
Loader {
|
Loader {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|||||||
@@ -119,6 +119,8 @@ QQC2.TextArea {
|
|||||||
selectionStart: root.selectionStart
|
selectionStart: root.selectionStart
|
||||||
selectionEnd: root.selectionEnd
|
selectionEnd: root.selectionEnd
|
||||||
room: root.room // We don't care about saving for edits so this is OK.
|
room: root.room // We don't care about saving for edits so this is OK.
|
||||||
|
mentionColor: Kirigami.Theme.linkColor
|
||||||
|
errorColor: Kirigami.Theme.negativeTextColor
|
||||||
}
|
}
|
||||||
|
|
||||||
TextMetrics {
|
TextMetrics {
|
||||||
|
|||||||
@@ -96,7 +96,6 @@ Item {
|
|||||||
RichLabel {
|
RichLabel {
|
||||||
textMessage: reply.display
|
textMessage: reply.display
|
||||||
textFormat: Text.RichText
|
textFormat: Text.RichText
|
||||||
isReplyLabel: true
|
|
||||||
|
|
||||||
HoverHandler {
|
HoverHandler {
|
||||||
enabled: !hoveredLink
|
enabled: !hoveredLink
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ TextEdit {
|
|||||||
readonly property var isEmoji: /^(<span style='.*'>)?(\u00a9|\u00ae|[\u20D0-\u2fff]|[\u3190-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])+(<\/span>)?$/
|
readonly property var isEmoji: /^(<span style='.*'>)?(\u00a9|\u00ae|[\u20D0-\u2fff]|[\u3190-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])+(<\/span>)?$/
|
||||||
readonly property var hasSpoiler: /data-mx-spoiler/g
|
readonly property var hasSpoiler: /data-mx-spoiler/g
|
||||||
|
|
||||||
property bool isEmote: false
|
|
||||||
property bool isReplyLabel: false
|
|
||||||
property string textMessage: model.display
|
property string textMessage: model.display
|
||||||
property bool spoilerRevealed: !hasSpoiler.test(textMessage)
|
property bool spoilerRevealed: !hasSpoiler.test(textMessage)
|
||||||
|
|
||||||
@@ -35,6 +33,9 @@ table {
|
|||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
}
|
}
|
||||||
|
code {
|
||||||
|
background-color:" + Kirigami.Theme.alternateBackgroundColor + ";
|
||||||
|
}
|
||||||
table th,
|
table th,
|
||||||
table td {
|
table td {
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
@@ -57,7 +58,7 @@ a{
|
|||||||
background: " + Kirigami.Theme.textColor + ";
|
background: " + Kirigami.Theme.textColor + ";
|
||||||
}
|
}
|
||||||
" : "") + "
|
" : "") + "
|
||||||
</style>" + (isEmote ? "* <a href='https://matrix.to/#/" + author.id + "' style='color: " + author.color + "'>" + author.displayName + "</a> " : "") + textMessage + (isEdited && !contentLabel.isReplyLabel ? (" <span style=\"color: " + Kirigami.Theme.disabledTextColor + "\">" + "<span style='font-size: " + Kirigami.Theme.defaultFont.pixelSize +"px'>" + i18n(" (edited)") + "</span>") : "")
|
</style>" + textMessage
|
||||||
|
|
||||||
color: Kirigami.Theme.textColor
|
color: Kirigami.Theme.textColor
|
||||||
selectedTextColor: Kirigami.Theme.highlightedTextColor
|
selectedTextColor: Kirigami.Theme.highlightedTextColor
|
||||||
|
|||||||
@@ -42,6 +42,6 @@ QQC2.ItemDelegate {
|
|||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
color: Config.blur ? "transparent" : Kirigami.Theme.backgroundColor
|
color: Config.blur ? "transparent" : Kirigami.Theme.backgroundColor
|
||||||
Kirigami.Theme.inherit: false
|
Kirigami.Theme.inherit: false
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
Kirigami.Theme.colorSet: Config.compactLayout ? Kirigami.Theme.View : Kirigami.Theme.Window
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ ColumnLayout {
|
|||||||
default property alias innerObject : column.children
|
default property alias innerObject : column.children
|
||||||
|
|
||||||
property Item hoverComponent: hoverActions ?? null
|
property Item hoverComponent: hoverActions ?? null
|
||||||
property bool isEmote: false
|
|
||||||
property bool cardBackground: true
|
property bool cardBackground: true
|
||||||
property bool showUserMessageOnRight: Config.showLocalMessagesOnRight && model.author.isLocalUser && !Config.compactLayout
|
property bool showUserMessageOnRight: Config.showLocalMessagesOnRight && model.author.isLocalUser && !Config.compactLayout
|
||||||
property bool isHighlighted: model.isHighlighted || isTemporaryHighlighted
|
property bool isHighlighted: model.isHighlighted || isTemporaryHighlighted
|
||||||
@@ -212,7 +211,7 @@ ColumnLayout {
|
|||||||
id: rowLayout
|
id: rowLayout
|
||||||
|
|
||||||
spacing: Kirigami.Units.smallSpacing
|
spacing: Kirigami.Units.smallSpacing
|
||||||
visible: model.showAuthor && !isEmote
|
visible: model.showAuthor
|
||||||
|
|
||||||
QQC2.Label {
|
QQC2.Label {
|
||||||
id: nameLabel
|
id: nameLabel
|
||||||
@@ -306,6 +305,7 @@ ColumnLayout {
|
|||||||
border.width: 1
|
border.width: 1
|
||||||
|
|
||||||
Behavior on color {
|
Behavior on color {
|
||||||
|
enabled: isTemporaryHighlighted
|
||||||
ColorAnimation {target: bubbleBackground; duration: Kirigami.Units.veryLongDuration; easing.type: Easing.InOutCubic}
|
ColorAnimation {target: bubbleBackground; duration: Kirigami.Units.veryLongDuration; easing.type: Easing.InOutCubic}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -336,7 +336,7 @@ ColumnLayout {
|
|||||||
Layout.leftMargin: showUserMessageOnRight ? 0 : bubble.x + bubble.anchors.leftMargin
|
Layout.leftMargin: showUserMessageOnRight ? 0 : bubble.x + bubble.anchors.leftMargin
|
||||||
Layout.rightMargin: showUserMessageOnRight ? Kirigami.Units.largeSpacing : 0
|
Layout.rightMargin: showUserMessageOnRight ? Kirigami.Units.largeSpacing : 0
|
||||||
|
|
||||||
visible: eventType !== MessageEventModel.State && eventType !== MessageEventModel.Notice && reaction != undefined && reaction.length > 0
|
visible: delegateType !== MessageEventModel.State && delegateType !== MessageEventModel.Notice && reaction != undefined && reaction.length > 0
|
||||||
}
|
}
|
||||||
AvatarFlow {
|
AvatarFlow {
|
||||||
Layout.alignment: Qt.AlignRight
|
Layout.alignment: Qt.AlignRight
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ QQC2.Dialog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
x: Math.round((parent.width - width) / 2)
|
x: parent ? Math.round((parent.width - width) / 2) : 0
|
||||||
y: Math.round((parent.height - height) / 2)
|
y: parent ? Math.round((parent.height - height) / 2) : 0
|
||||||
modal: true
|
modal: true
|
||||||
|
|
||||||
footer: QQC2.DialogButtonBox {
|
footer: QQC2.DialogButtonBox {
|
||||||
@@ -18,13 +18,13 @@ Kirigami.OverlaySheet {
|
|||||||
contentItem: Kirigami.FormLayout {
|
contentItem: Kirigami.FormLayout {
|
||||||
QQC2.TextField {
|
QQC2.TextField {
|
||||||
id: roomNameField
|
id: roomNameField
|
||||||
Kirigami.FormData.label: i18n("Room Name")
|
Kirigami.FormData.label: i18n("Room name:")
|
||||||
onAccepted: roomTopicField.forceActiveFocus();
|
onAccepted: roomTopicField.forceActiveFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
QQC2.TextField {
|
QQC2.TextField {
|
||||||
id: roomTopicField
|
id: roomTopicField
|
||||||
Kirigami.FormData.label: i18n("Room Topic")
|
Kirigami.FormData.label: i18n("Room topic:")
|
||||||
onAccepted: okButton.forceActiveFocus();
|
onAccepted: okButton.forceActiveFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import QtQuick.Layouts 1.15
|
|||||||
import org.kde.kirigami 2.19 as Kirigami
|
import org.kde.kirigami 2.19 as Kirigami
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
import org.kde.neochat 1.0
|
||||||
|
import '../Dialog' as Dialog
|
||||||
|
|
||||||
QQC2.Menu {
|
QQC2.Menu {
|
||||||
id: root
|
id: root
|
||||||
@@ -38,7 +39,7 @@ QQC2.Menu {
|
|||||||
onTriggered: confirmLogoutDialog.open()
|
onTriggered: confirmLogoutDialog.open()
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfirmLogoutDialog {
|
Dialog.ConfirmLogout {
|
||||||
id: confirmLogoutDialog
|
id: confirmLogoutDialog
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
53
src/qml/Page/RoomList/CollapsedRoomDelegate.qml
Normal file
53
src/qml/Page/RoomList/CollapsedRoomDelegate.qml
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 Carl Schwan <carl@carlschwan.eu>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import QtQml.Models 2.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
import org.kde.kitemmodels 1.0
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
|
import './' as RoomList
|
||||||
|
|
||||||
|
QQC2.ItemDelegate {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property var currentRoom
|
||||||
|
required property bool categoryVisible
|
||||||
|
required property string filterText
|
||||||
|
required property string avatar
|
||||||
|
required property string name
|
||||||
|
|
||||||
|
topPadding: Kirigami.Units.largeSpacing
|
||||||
|
leftPadding: Kirigami.Units.largeSpacing
|
||||||
|
rightPadding: Kirigami.Units.largeSpacing
|
||||||
|
bottomPadding: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
|
width: ListView.view.width
|
||||||
|
height: visible ? ListView.view.width : 0
|
||||||
|
|
||||||
|
visible: root.categoryVisible || filterText.length > 0 || Config.mergeRoomList
|
||||||
|
|
||||||
|
contentItem: Kirigami.Avatar {
|
||||||
|
source: root.avatar ? `image://mxc/${root.avatar}` : ""
|
||||||
|
name: root.name || i18n("No Name")
|
||||||
|
|
||||||
|
sourceSize {
|
||||||
|
width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
||||||
|
height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: RoomManager.enterRoom(root.currentRoom)
|
||||||
|
|
||||||
|
Keys.onEnterPressed: RoomManager.enterRoom(root.currentRoom)
|
||||||
|
Keys.onReturnPressed: RoomManager.enterRoom(root.currentRoom)
|
||||||
|
|
||||||
|
QQC2.ToolTip.visible: text.length > 0 && hovered
|
||||||
|
QQC2.ToolTip.text: root.name ?? ""
|
||||||
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
|
}
|
||||||
301
src/qml/Page/RoomList/Page.qml
Normal file
301
src/qml/Page/RoomList/Page.qml
Normal file
@@ -0,0 +1,301 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||||
|
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import QtQml.Models 2.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
import org.kde.kitemmodels 1.0
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
|
import './' as RoomList
|
||||||
|
|
||||||
|
Kirigami.ScrollablePage {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The current width of the room list.
|
||||||
|
*
|
||||||
|
* @note Other objects can access the value but the private function makes sure
|
||||||
|
* that only the internal members can modify it.
|
||||||
|
*/
|
||||||
|
readonly property int currentWidth: _private.currentWidth
|
||||||
|
|
||||||
|
readonly property RoomListModel roomListModel: RoomListModel {
|
||||||
|
connection: Controller.activeConnection
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property bool collapsed: Config.collapsed
|
||||||
|
|
||||||
|
property var enteredRoom: null
|
||||||
|
|
||||||
|
onCollapsedChanged: if (collapsed) {
|
||||||
|
sortFilterRoomListModel.filterText = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
header: ColumnLayout {
|
||||||
|
visible: !root.collapsed
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
RoomList.SpaceListView {
|
||||||
|
roomListModel: root.roomListModel
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.Separator {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: spaceListContextMenu
|
||||||
|
SpaceListContextMenu {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: RoomManager
|
||||||
|
function onCurrentRoomChanged() {
|
||||||
|
itemSelection.setCurrentIndex(roomListModel.index(roomListModel.indexForRoom(RoomManager.currentRoom), 0), ItemSelectionModel.SelectCurrent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToNextRoomFiltered(condition) {
|
||||||
|
let index = listView.currentIndex;
|
||||||
|
while (index++ !== listView.count - 1) {
|
||||||
|
if (condition(listView.itemAtIndex(index))) {
|
||||||
|
listView.currentIndex = index;
|
||||||
|
listView.currentItem.action.trigger();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToPreviousRoomFiltered(condition) {
|
||||||
|
let index = listView.currentIndex;
|
||||||
|
while (index-- !== 0) {
|
||||||
|
if (condition(listView.itemAtIndex(index))) {
|
||||||
|
listView.currentIndex = index;
|
||||||
|
listView.currentItem.action.trigger();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToNextRoom() {
|
||||||
|
goToNextRoomFiltered((item) => item.visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToPreviousRoom() {
|
||||||
|
goToPreviousRoomFiltered((item) => item.visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToNextUnreadRoom() {
|
||||||
|
goToNextRoomFiltered((item) => (item.visible && item.hasUnread));
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToPreviousUnreadRoom() {
|
||||||
|
goToPreviousRoomFiltered((item) => (item.visible && item.hasUnread));
|
||||||
|
}
|
||||||
|
|
||||||
|
titleDelegate: ExploreComponent {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
desiredWidth: root.width - Kirigami.Units.largeSpacing
|
||||||
|
collapsed: root.collapsed
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: listView
|
||||||
|
|
||||||
|
activeFocusOnTab: true
|
||||||
|
clip: AccountRegistry.count > 1
|
||||||
|
|
||||||
|
header: QQC2.ItemDelegate {
|
||||||
|
width: visible ? ListView.view.width : 0
|
||||||
|
height: visible ? Kirigami.Units.gridUnit * 2 : 0
|
||||||
|
|
||||||
|
visible: root.collapsed
|
||||||
|
|
||||||
|
topPadding: Kirigami.Units.largeSpacing
|
||||||
|
leftPadding: Kirigami.Units.largeSpacing
|
||||||
|
rightPadding: Kirigami.Units.largeSpacing
|
||||||
|
bottomPadding: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
|
onClicked: quickView.item.open();
|
||||||
|
|
||||||
|
Kirigami.Icon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: Kirigami.Units.iconSizes.smallMedium
|
||||||
|
height: Kirigami.Units.iconSizes.smallMedium
|
||||||
|
source: "search"
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.Separator {
|
||||||
|
width: parent.width
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
Kirigami.PlaceholderMessage {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: parent.width - (Kirigami.Units.largeSpacing * 4)
|
||||||
|
visible: listView.count == 0
|
||||||
|
text: sortFilterRoomListModel.filterText.length > 0 ? i18n("No rooms found") : i18n("Join some rooms to get started")
|
||||||
|
helpfulAction: Kirigami.Action {
|
||||||
|
icon.name: sortFilterRoomListModel.filterText.length > 0 ? "search" : "list-add"
|
||||||
|
text: sortFilterRoomListModel.filterText.length > 0 ? i18n("Search in room directory") : i18n("Explore rooms")
|
||||||
|
onTriggered: pageStack.layers.push("qrc:/JoinRoomPage.qml", {
|
||||||
|
connection: Controller.activeConnection,
|
||||||
|
keyword: sortFilterRoomListModel.filterText
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemSelectionModel {
|
||||||
|
id: itemSelection
|
||||||
|
model: root.roomListModel
|
||||||
|
onCurrentChanged: listView.currentIndex = sortFilterRoomListModel.mapFromSource(current).row
|
||||||
|
}
|
||||||
|
|
||||||
|
model: SortFilterRoomListModel {
|
||||||
|
id: sortFilterRoomListModel
|
||||||
|
|
||||||
|
sourceModel: root.roomListModel
|
||||||
|
roomSortOrder: Config.mergeRoomList ? SortFilterRoomListModel.LastActivity : SortFilterRoomListModel.Categories
|
||||||
|
onLayoutChanged: {
|
||||||
|
listView.currentIndex = sortFilterRoomListModel.mapFromSource(itemSelection.currentIndex).row
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
section.property: sortFilterRoomListModel.filterText.length === 0 && !Config.mergeRoomList ? "category" : null
|
||||||
|
section.delegate: root.collapsed ? foldButton : sectionHeader
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: sectionHeader
|
||||||
|
Kirigami.ListSectionHeader {
|
||||||
|
height: implicitHeight
|
||||||
|
label: roomListModel.categoryName(section)
|
||||||
|
action: Kirigami.Action {
|
||||||
|
onTriggered: roomListModel.setCategoryVisible(section, !roomListModel.categoryVisible(section))
|
||||||
|
}
|
||||||
|
contentItem.children: QQC2.ToolButton {
|
||||||
|
icon {
|
||||||
|
name: roomListModel.categoryVisible(section) ? "go-up" : "go-down"
|
||||||
|
width: Kirigami.Units.iconSizes.small
|
||||||
|
height: Kirigami.Units.iconSizes.small
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: roomListModel.setCategoryVisible(section, !roomListModel.categoryVisible(section))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Component {
|
||||||
|
id: foldButton
|
||||||
|
Item {
|
||||||
|
width: ListView.view.width
|
||||||
|
height: visible ? width : 0
|
||||||
|
QQC2.ToolButton {
|
||||||
|
id: button
|
||||||
|
anchors.centerIn: parent
|
||||||
|
|
||||||
|
icon {
|
||||||
|
name: hovered ? (roomListModel.categoryVisible(section) ? "go-up" : "go-down") : roomListModel.categoryIconName(section)
|
||||||
|
width: Kirigami.Units.iconSizes.smallMedium
|
||||||
|
height: Kirigami.Units.iconSizes.smallMedium
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: roomListModel.setCategoryVisible(section, !roomListModel.categoryVisible(section))
|
||||||
|
|
||||||
|
QQC2.ToolTip.text: roomListModel.categoryName(section)
|
||||||
|
QQC2.ToolTip.visible: hovered
|
||||||
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reuseItems: true
|
||||||
|
currentIndex: -1 // we don't want any room highlighted by default
|
||||||
|
|
||||||
|
delegate: root.collapsed ? collapsedModeListComponent : normalModeListComponent
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: collapsedModeListComponent
|
||||||
|
|
||||||
|
RoomList.CollapsedRoomDelegate {
|
||||||
|
filterText: sortFilterRoomListModel.filterText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: normalModeListComponent
|
||||||
|
|
||||||
|
RoomList.RoomDelegate {
|
||||||
|
filterText: sortFilterRoomListModel.filterText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
footer: UserInfo {
|
||||||
|
width: parent.width
|
||||||
|
visible: !root.collapsed
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
parent: applicationWindow().overlay.parent
|
||||||
|
|
||||||
|
x: root.currentWidth - width / 2
|
||||||
|
width: Kirigami.Units.smallSpacing * 2
|
||||||
|
z: root.z + 1
|
||||||
|
enabled: RoomManager.hasOpenRoom && applicationWindow().width >= Kirigami.Units.gridUnit * 35
|
||||||
|
visible: enabled
|
||||||
|
cursorShape: Qt.SplitHCursor
|
||||||
|
|
||||||
|
property int _lastX
|
||||||
|
|
||||||
|
onPressed: mouse => {
|
||||||
|
_lastX = mouse.x;
|
||||||
|
}
|
||||||
|
onPositionChanged: mouse => {
|
||||||
|
if (_lastX == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mouse.x > _lastX) {
|
||||||
|
// we moved to the right
|
||||||
|
if (_private.currentWidth < _private.collapseWidth && _private.currentWidth + (mouse.x - _lastX) >= _private.collapseWidth) {
|
||||||
|
// Here we get back directly to a more wide mode.
|
||||||
|
_private.currentWidth = _private.collapseWidth;
|
||||||
|
Config.collapsed = false;
|
||||||
|
} else if (_private.currentWidth >= _private.collapseWidth) {
|
||||||
|
// Increase page width
|
||||||
|
_private.currentWidth = Math.min(_private.defaultWidth, _private.currentWidth + (mouse.x - _lastX));
|
||||||
|
}
|
||||||
|
} else if (mouse.x < _lastX) {
|
||||||
|
const tmpWidth = _private.currentWidth - (_lastX - mouse.x);
|
||||||
|
if (tmpWidth < _private.collapseWidth) {
|
||||||
|
_private.currentWidth = Qt.binding(() => _private.collapsedSize + (root.contentItem.QQC2.ScrollBar.vertical.visible ? root.contentItem.QQC2.ScrollBar.vertical.width : 0));
|
||||||
|
Config.collapsed = true;
|
||||||
|
} else {
|
||||||
|
_private.currentWidth = tmpWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Hold the modifiable currentWidth in a private object so that only internal
|
||||||
|
* members can modify it.
|
||||||
|
*/
|
||||||
|
QtObject {
|
||||||
|
id: _private
|
||||||
|
property int currentWidth: Config.collapsed ? collapsedSize : defaultWidth
|
||||||
|
readonly property int defaultWidth: Kirigami.Units.gridUnit * 17
|
||||||
|
readonly property int collapseWidth: Kirigami.Units.gridUnit * 10
|
||||||
|
readonly property int collapsedSize: Kirigami.Units.gridUnit * 3 - Kirigami.Units.smallSpacing * 3
|
||||||
|
}
|
||||||
|
}
|
||||||
136
src/qml/Page/RoomList/RoomDelegate.qml
Normal file
136
src/qml/Page/RoomList/RoomDelegate.qml
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 Carl Schwan <carl@carlschwan.eu>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import QtQml.Models 2.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
import org.kde.kitemmodels 1.0
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
|
import './' as RoomList
|
||||||
|
|
||||||
|
Kirigami.BasicListItem {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property int index
|
||||||
|
required property int unreadCount
|
||||||
|
required property int notificationCount
|
||||||
|
required property int highlightCount
|
||||||
|
required property var currentRoom
|
||||||
|
required property bool categoryVisible
|
||||||
|
required property string filterText
|
||||||
|
required property string avatar
|
||||||
|
required property string subtitleText
|
||||||
|
|
||||||
|
required property string name
|
||||||
|
|
||||||
|
readonly property bool hasUnread: unreadCount > 0
|
||||||
|
|
||||||
|
topPadding: Kirigami.Units.largeSpacing
|
||||||
|
bottomPadding: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
|
visible: root.categoryVisible || root.filterText.length > 0 || Config.mergeRoomList
|
||||||
|
highlighted: ListView.view.currentIndex === index
|
||||||
|
focus: true
|
||||||
|
icon: undefined
|
||||||
|
bold: root.unreadCount > 0
|
||||||
|
|
||||||
|
label: root.name ?? ""
|
||||||
|
labelItem.textFormat: Text.PlainText
|
||||||
|
|
||||||
|
subtitle: root.subtitleText
|
||||||
|
subtitleItem {
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
visible: !Config.compactRoomList
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: RoomManager.enterRoom(root.currentRoom)
|
||||||
|
onPressAndHold: createRoomListContextMenu()
|
||||||
|
|
||||||
|
Keys.onEnterPressed: RoomManager.enterRoom(root.currentRoom)
|
||||||
|
Keys.onReturnPressed: RoomManager.enterRoom(root.currentRoom)
|
||||||
|
|
||||||
|
TapHandler {
|
||||||
|
acceptedButtons: Qt.RightButton
|
||||||
|
acceptedDevices: PointerDevice.Mouse
|
||||||
|
onTapped: createRoomListContextMenu()
|
||||||
|
}
|
||||||
|
|
||||||
|
leading: Kirigami.Avatar {
|
||||||
|
source: root.avatar ? `image://mxc/${root.avatar}` : ""
|
||||||
|
name: root.name || i18n("No Name")
|
||||||
|
implicitWidth: visible ? height : 0
|
||||||
|
visible: Config.showAvatarInRoomDrawer
|
||||||
|
sourceSize {
|
||||||
|
width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
||||||
|
height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trailing: RowLayout {
|
||||||
|
Kirigami.Icon {
|
||||||
|
source: "notifications-disabled"
|
||||||
|
enabled: false
|
||||||
|
implicitWidth: Kirigami.Units.iconSizes.smallMedium
|
||||||
|
implicitHeight: Kirigami.Units.iconSizes.smallMedium
|
||||||
|
visible: currentRoom.pushNotificationState === PushNotificationState.Mute && !configButton.visible && unreadCount <= 0
|
||||||
|
Accessible.name: i18n("Muted room")
|
||||||
|
Layout.rightMargin: Kirigami.Units.smallSpacing
|
||||||
|
}
|
||||||
|
QQC2.Label {
|
||||||
|
id: notificationCountLabel
|
||||||
|
text: notificationCount > 0 ? notificationCount : "●"
|
||||||
|
visible: unreadCount > 0
|
||||||
|
color: Kirigami.Theme.textColor
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
background: Rectangle {
|
||||||
|
visible: notificationCount > 0
|
||||||
|
Kirigami.Theme.colorSet: Kirigami.Theme.Button
|
||||||
|
color: highlightCount > 0 ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.disabledTextColor
|
||||||
|
opacity: highlightCount > 0 ? 1 : 0.3
|
||||||
|
radius: height / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
Layout.rightMargin: Kirigami.Units.smallSpacing
|
||||||
|
Layout.minimumHeight: Kirigami.Units.iconSizes.smallMedium
|
||||||
|
Layout.minimumWidth: Math.max(notificationCountTextMetrics.advanceWidth + Kirigami.Units.smallSpacing * 2, height)
|
||||||
|
|
||||||
|
TextMetrics {
|
||||||
|
id: notificationCountTextMetrics
|
||||||
|
text: notificationCountLabel.text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QQC2.Button {
|
||||||
|
id: configButton
|
||||||
|
visible: root.hovered && !Kirigami.Settings.isMobile && !Config.compactRoomList
|
||||||
|
text: i18n("Configure room")
|
||||||
|
display: QQC2.Button.IconOnly
|
||||||
|
|
||||||
|
icon.name: "configure"
|
||||||
|
onClicked: createRoomListContextMenu()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRoomListContextMenu() {
|
||||||
|
const component = Qt.createComponent(Qt.resolvedUrl("./ContextMenu.qml"))
|
||||||
|
const menu = component.createObject(root, {
|
||||||
|
room: root.currentRoom,
|
||||||
|
});
|
||||||
|
if (!Kirigami.Settings.isMobile && !Config.compactRoomList) {
|
||||||
|
configButton.visible = true;
|
||||||
|
configButton.down = true;
|
||||||
|
}
|
||||||
|
menu.closed.connect(function() {
|
||||||
|
configButton.down = undefined;
|
||||||
|
configButton.visible = Qt.binding(() => {
|
||||||
|
return root.hovered && !Kirigami.Settings.isMobile
|
||||||
|
&& !Config.compactRoomList;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
menu.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
47
src/qml/Page/RoomList/SpaceDelegate.qml
Normal file
47
src/qml/Page/RoomList/SpaceDelegate.qml
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2022 Snehit Sah <snehitsah@protonmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.20 as Kirigami
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
|
QQC2.ItemDelegate {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property string avatar
|
||||||
|
required property var currentRoom
|
||||||
|
required property int index
|
||||||
|
required property string id
|
||||||
|
|
||||||
|
signal createContextMenu(currentRoom: var)
|
||||||
|
signal spaceSelected(spaceId: string)
|
||||||
|
|
||||||
|
height: ListView.view.height
|
||||||
|
width: height
|
||||||
|
|
||||||
|
leftPadding: topPadding
|
||||||
|
rightPadding: topPadding
|
||||||
|
|
||||||
|
contentItem: Kirigami.Avatar {
|
||||||
|
name: currentRoom.displayName
|
||||||
|
source: avatar !== "" ? "image://mxc/" + avatar : ""
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: root.spaceSelected(id)
|
||||||
|
onPressAndHold: root.createContextMenu(root.currentRoom)
|
||||||
|
|
||||||
|
Accessible.name: currentRoom.displayName
|
||||||
|
|
||||||
|
QQC2.ToolTip.text: currentRoom.displayName
|
||||||
|
QQC2.ToolTip.visible: hovered
|
||||||
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
|
|
||||||
|
TapHandler {
|
||||||
|
acceptedButtons: Qt.RightButton
|
||||||
|
acceptedDevices: PointerDevice.Mouse
|
||||||
|
onTapped: root.createContextMenu(root.currentRoom)
|
||||||
|
}
|
||||||
|
}
|
||||||
64
src/qml/Page/RoomList/SpaceListView.qml
Normal file
64
src/qml/Page/RoomList/SpaceListView.qml
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2022 Snehit Sah <snehitsah@protonmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.20 as Kirigami
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property RoomListModel roomListModel
|
||||||
|
|
||||||
|
orientation: Qt.Horizontal
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
clip: true
|
||||||
|
visible: root.count > 0
|
||||||
|
|
||||||
|
model: SortFilterSpaceListModel {
|
||||||
|
id: sortFilterSpaceListModel
|
||||||
|
sourceModel: root.roomListModel
|
||||||
|
}
|
||||||
|
|
||||||
|
header: QQC2.ItemDelegate {
|
||||||
|
id: homeButton
|
||||||
|
icon.name: "home"
|
||||||
|
text: i18nc("@action:button", "Show All Rooms")
|
||||||
|
height: parent.height
|
||||||
|
width: height
|
||||||
|
leftPadding: topPadding
|
||||||
|
rightPadding: topPadding
|
||||||
|
|
||||||
|
contentItem: Kirigami.Icon {
|
||||||
|
source: "home"
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
sortFilterRoomListModel.activeSpaceId = "";
|
||||||
|
listView.positionViewAtIndex(0, ListView.Beginning);
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.ToolTip.text: homeButton.text
|
||||||
|
QQC2.ToolTip.visible: hovered
|
||||||
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: SpaceDelegate {
|
||||||
|
onSpaceSelected: (spaceId) => {
|
||||||
|
sortFilterRoomListModel.activeSpaceId = spaceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
onCreateContextMenu: () => {
|
||||||
|
const menu = spaceListContextMenu.createObject(page, {
|
||||||
|
room: currentRoom,
|
||||||
|
});
|
||||||
|
menu.open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
@@ -1,505 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
|
||||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
|
|
||||||
import QtQuick 2.15
|
|
||||||
import QtQuick.Controls 2.15 as QQC2
|
|
||||||
import QtQuick.Layouts 1.15
|
|
||||||
import QtQml.Models 2.15
|
|
||||||
|
|
||||||
import org.kde.kirigami 2.15 as Kirigami
|
|
||||||
import org.kde.kitemmodels 1.0
|
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
|
||||||
|
|
||||||
Kirigami.ScrollablePage {
|
|
||||||
id: page
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The current width of the room list.
|
|
||||||
*
|
|
||||||
* @note Other objects can access the value but the private function makes sure
|
|
||||||
* that only the internal members can modify it.
|
|
||||||
*/
|
|
||||||
readonly property int currentWidth: _private.currentWidth
|
|
||||||
|
|
||||||
readonly property bool collapsed: Config.collapsed
|
|
||||||
onCollapsedChanged: if (collapsed) {
|
|
||||||
sortFilterRoomListModel.filterText = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
header: ColumnLayout {
|
|
||||||
visible: !page.collapsed
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
ListView {
|
|
||||||
id: spaceList
|
|
||||||
property string activeSpaceId: ""
|
|
||||||
|
|
||||||
orientation: Qt.Horizontal
|
|
||||||
spacing: Kirigami.Units.smallSpacing
|
|
||||||
clip: true
|
|
||||||
visible: spaceList.count > 0
|
|
||||||
|
|
||||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
model: SortFilterSpaceListModel {
|
|
||||||
id: sortFilterSpaceListModel
|
|
||||||
sourceModel: RoomListModel {
|
|
||||||
id: spaceListModel
|
|
||||||
connection: Controller.activeConnection
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
header: QQC2.ItemDelegate {
|
|
||||||
id: homeButton
|
|
||||||
icon.name: "home"
|
|
||||||
text: i18nc("@action:button", "Show All Rooms")
|
|
||||||
height: parent.height
|
|
||||||
width: height
|
|
||||||
leftPadding: topPadding
|
|
||||||
rightPadding: topPadding
|
|
||||||
|
|
||||||
contentItem: Kirigami.Icon {
|
|
||||||
source: "home"
|
|
||||||
}
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
sortFilterRoomListModel.activeSpaceId = "";
|
|
||||||
spaceList.activeSpaceId = '';
|
|
||||||
listView.positionViewAtIndex(0, ListView.Beginning);
|
|
||||||
}
|
|
||||||
|
|
||||||
QQC2.ToolTip.text: homeButton.text
|
|
||||||
QQC2.ToolTip.visible: hovered
|
|
||||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
|
||||||
}
|
|
||||||
|
|
||||||
delegate: QQC2.ItemDelegate {
|
|
||||||
required property string avatar
|
|
||||||
required property var currentRoom
|
|
||||||
required property int index
|
|
||||||
required property string id
|
|
||||||
|
|
||||||
height: parent.height
|
|
||||||
width: height
|
|
||||||
leftPadding: topPadding
|
|
||||||
rightPadding: topPadding
|
|
||||||
|
|
||||||
contentItem: Kirigami.Avatar {
|
|
||||||
name: currentRoom.displayName
|
|
||||||
source: avatar !== "" ? "image://mxc/" + avatar : ""
|
|
||||||
}
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
spaceList.activeSpaceId = id;
|
|
||||||
sortFilterRoomListModel.activeSpaceId = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
Accessible.name: currentRoom.displayName
|
|
||||||
|
|
||||||
QQC2.ToolTip.text: currentRoom.displayName
|
|
||||||
QQC2.ToolTip.visible: hovered
|
|
||||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
|
||||||
|
|
||||||
onPressAndHold: {
|
|
||||||
spaceList.createContextMenu(currentRoom)
|
|
||||||
}
|
|
||||||
TapHandler {
|
|
||||||
acceptedButtons: Qt.RightButton
|
|
||||||
acceptedDevices: PointerDevice.Mouse
|
|
||||||
onTapped: spaceList.createContextMenu(currentRoom)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function createContextMenu(room) {
|
|
||||||
const menu = spaceListContextMenu.createObject(page, {room: room})
|
|
||||||
menu.open()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Kirigami.Separator {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: spaceListContextMenu
|
|
||||||
SpaceListContextMenu {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
property var enteredRoom
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: RoomManager
|
|
||||||
function onCurrentRoomChanged() {
|
|
||||||
itemSelection.setCurrentIndex(roomListModel.index(roomListModel.indexForRoom(RoomManager.currentRoom), 0), ItemSelectionModel.SelectCurrent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function goToNextRoomFiltered(condition) {
|
|
||||||
let index = listView.currentIndex;
|
|
||||||
while (index++ !== listView.count - 1) {
|
|
||||||
if (condition(listView.itemAtIndex(index))) {
|
|
||||||
listView.currentIndex = index;
|
|
||||||
listView.currentItem.action.trigger();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function goToPreviousRoomFiltered(condition) {
|
|
||||||
let index = listView.currentIndex;
|
|
||||||
while (index-- !== 0) {
|
|
||||||
if (condition(listView.itemAtIndex(index))) {
|
|
||||||
listView.currentIndex = index;
|
|
||||||
listView.currentItem.action.trigger();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function goToNextRoom() {
|
|
||||||
goToNextRoomFiltered((item) => item.visible);
|
|
||||||
}
|
|
||||||
|
|
||||||
function goToPreviousRoom() {
|
|
||||||
goToPreviousRoomFiltered((item) => item.visible);
|
|
||||||
}
|
|
||||||
|
|
||||||
function goToNextUnreadRoom() {
|
|
||||||
goToNextRoomFiltered((item) => (item.visible && item.hasUnread));
|
|
||||||
}
|
|
||||||
|
|
||||||
function goToPreviousUnreadRoom() {
|
|
||||||
goToPreviousRoomFiltered((item) => (item.visible && item.hasUnread));
|
|
||||||
}
|
|
||||||
|
|
||||||
titleDelegate: ExploreComponent {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
desiredWidth: page.width - Kirigami.Units.largeSpacing
|
|
||||||
collapsed: page.collapsed
|
|
||||||
}
|
|
||||||
|
|
||||||
ListView {
|
|
||||||
id: listView
|
|
||||||
|
|
||||||
activeFocusOnTab: true
|
|
||||||
clip: AccountRegistry.count > 1
|
|
||||||
|
|
||||||
header: QQC2.ItemDelegate {
|
|
||||||
visible: page.collapsed
|
|
||||||
action: Kirigami.Action {
|
|
||||||
id: enterRoomAction
|
|
||||||
onTriggered: quickView.item.open();
|
|
||||||
}
|
|
||||||
topPadding: Kirigami.Units.largeSpacing
|
|
||||||
leftPadding: Kirigami.Units.largeSpacing
|
|
||||||
rightPadding: Kirigami.Units.largeSpacing
|
|
||||||
bottomPadding: Kirigami.Units.largeSpacing
|
|
||||||
width: visible ? ListView.view.width : 0
|
|
||||||
height: visible ? Kirigami.Units.gridUnit * 2 : 0
|
|
||||||
|
|
||||||
Kirigami.Icon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: 22
|
|
||||||
height: 22
|
|
||||||
source: "search"
|
|
||||||
}
|
|
||||||
Kirigami.Separator {
|
|
||||||
width: parent.width
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
Kirigami.PlaceholderMessage {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: parent.width - (Kirigami.Units.largeSpacing * 4)
|
|
||||||
visible: listView.count == 0
|
|
||||||
text: sortFilterRoomListModel.filterText.length > 0 ? i18n("No rooms found") : i18n("Join some rooms to get started")
|
|
||||||
helpfulAction: Kirigami.Action {
|
|
||||||
icon.name: sortFilterRoomListModel.filterText.length > 0 ? "search" : "list-add"
|
|
||||||
text: sortFilterRoomListModel.filterText.length > 0 ? i18n("Search in room directory") : i18n("Explore rooms")
|
|
||||||
onTriggered: pageStack.layers.push("qrc:/JoinRoomPage.qml", {
|
|
||||||
connection: Controller.activeConnection,
|
|
||||||
keyword: sortFilterRoomListModel.filterText
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
ItemSelectionModel {
|
|
||||||
id: itemSelection
|
|
||||||
model: roomListModel
|
|
||||||
onCurrentChanged: {
|
|
||||||
listView.currentIndex = sortFilterRoomListModel.mapFromSource(current).row
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
model: SortFilterRoomListModel {
|
|
||||||
id: sortFilterRoomListModel
|
|
||||||
sourceModel: RoomListModel {
|
|
||||||
id: roomListModel
|
|
||||||
connection: Controller.activeConnection
|
|
||||||
}
|
|
||||||
roomSortOrder: Config.mergeRoomList ? SortFilterRoomListModel.LastActivity : SortFilterRoomListModel.Categories
|
|
||||||
onLayoutChanged: {
|
|
||||||
listView.currentIndex = sortFilterRoomListModel.mapFromSource(itemSelection.currentIndex).row
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
section.property: sortFilterRoomListModel.filterText.length === 0 && !Config.mergeRoomList ? "category" : null
|
|
||||||
section.delegate: page.collapsed ? foldButton : sectionHeader
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: sectionHeader
|
|
||||||
Kirigami.ListSectionHeader {
|
|
||||||
height: implicitHeight
|
|
||||||
label: roomListModel.categoryName(section)
|
|
||||||
action: Kirigami.Action {
|
|
||||||
onTriggered: roomListModel.setCategoryVisible(section, !roomListModel.categoryVisible(section))
|
|
||||||
}
|
|
||||||
contentItem.children: QQC2.ToolButton {
|
|
||||||
icon.name: (roomListModel.categoryVisible(section) ? "go-up" : "go-down")
|
|
||||||
icon.width: Kirigami.Units.iconSizes.small
|
|
||||||
icon.height: Kirigami.Units.iconSizes.small
|
|
||||||
|
|
||||||
onClicked: roomListModel.setCategoryVisible(section, !roomListModel.categoryVisible(section))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Component {
|
|
||||||
id: foldButton
|
|
||||||
Item {
|
|
||||||
width: ListView.view.width
|
|
||||||
height: visible ? width : 0
|
|
||||||
QQC2.ToolButton {
|
|
||||||
id: button
|
|
||||||
anchors.centerIn: parent
|
|
||||||
|
|
||||||
icon.name: hovered ? (roomListModel.categoryVisible(section) ? "go-up" : "go-down") : roomListModel.categoryIconName(section)
|
|
||||||
icon.width: Kirigami.Units.iconSizes.smallMedium
|
|
||||||
icon.height: Kirigami.Units.iconSizes.smallMedium
|
|
||||||
|
|
||||||
onClicked: roomListModel.setCategoryVisible(section, !roomListModel.categoryVisible(section))
|
|
||||||
|
|
||||||
QQC2.ToolTip.text: roomListModel.categoryName(section)
|
|
||||||
QQC2.ToolTip.visible: hovered
|
|
||||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reuseItems: true
|
|
||||||
currentIndex: -1 // we don't want any room highlighted by default
|
|
||||||
|
|
||||||
delegate: page.collapsed ? collapsedModeListComponent : normalModeListComponent
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: collapsedModeListComponent
|
|
||||||
|
|
||||||
QQC2.ItemDelegate {
|
|
||||||
action: Kirigami.Action {
|
|
||||||
id: enterRoomAction
|
|
||||||
onTriggered: {
|
|
||||||
RoomManager.enterRoom(currentRoom);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Keys.onEnterPressed: enterRoomAction.trigger()
|
|
||||||
Keys.onReturnPressed: enterRoomAction.trigger()
|
|
||||||
topPadding: Kirigami.Units.largeSpacing
|
|
||||||
leftPadding: Kirigami.Units.largeSpacing
|
|
||||||
rightPadding: Kirigami.Units.largeSpacing
|
|
||||||
bottomPadding: Kirigami.Units.largeSpacing
|
|
||||||
width: ListView.view.width
|
|
||||||
height: visible ? ListView.view.width : 0
|
|
||||||
visible: model.categoryVisible || sortFilterRoomListModel.filterText.length > 0 || Config.mergeRoomList
|
|
||||||
|
|
||||||
contentItem: Kirigami.Avatar {
|
|
||||||
source: avatar ? "image://mxc/" + avatar : ""
|
|
||||||
name: model.name || i18n("No Name")
|
|
||||||
sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
|
||||||
sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
|
||||||
}
|
|
||||||
|
|
||||||
QQC2.ToolTip {
|
|
||||||
enabled: text.length !== 0
|
|
||||||
text: name ?? ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: roomListContextMenu
|
|
||||||
RoomListContextMenu {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: normalModeListComponent
|
|
||||||
Kirigami.BasicListItem {
|
|
||||||
id: roomListItem
|
|
||||||
visible: model.categoryVisible || sortFilterRoomListModel.filterText.length > 0 || Config.mergeRoomList
|
|
||||||
topPadding: Kirigami.Units.largeSpacing
|
|
||||||
bottomPadding: Kirigami.Units.largeSpacing
|
|
||||||
highlighted: listView.currentIndex === index
|
|
||||||
focus: true
|
|
||||||
icon: undefined
|
|
||||||
action: Kirigami.Action {
|
|
||||||
id: enterRoomAction
|
|
||||||
onTriggered: {
|
|
||||||
RoomManager.enterRoom(currentRoom);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Keys.onEnterPressed: enterRoomAction.trigger()
|
|
||||||
Keys.onReturnPressed: enterRoomAction.trigger()
|
|
||||||
bold: unreadCount > 0
|
|
||||||
label: name ?? ""
|
|
||||||
labelItem.textFormat: Text.PlainText
|
|
||||||
subtitle: subtitleText
|
|
||||||
subtitleItem.textFormat: Text.PlainText
|
|
||||||
subtitleItem.visible: !Config.compactRoomList
|
|
||||||
onPressAndHold: {
|
|
||||||
createRoomListContextMenu()
|
|
||||||
}
|
|
||||||
TapHandler {
|
|
||||||
acceptedButtons: Qt.RightButton
|
|
||||||
acceptedDevices: PointerDevice.Mouse
|
|
||||||
onTapped: createRoomListContextMenu()
|
|
||||||
}
|
|
||||||
|
|
||||||
leading: Kirigami.Avatar {
|
|
||||||
source: avatar ? "image://mxc/" + avatar : ""
|
|
||||||
name: model.name || i18n("No Name")
|
|
||||||
implicitWidth: visible ? height : 0
|
|
||||||
visible: Config.showAvatarInRoomDrawer
|
|
||||||
sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
|
||||||
sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
|
||||||
}
|
|
||||||
|
|
||||||
trailing: RowLayout {
|
|
||||||
Kirigami.Icon {
|
|
||||||
source: "notifications-disabled"
|
|
||||||
enabled: false
|
|
||||||
implicitWidth: Kirigami.Units.iconSizes.smallMedium
|
|
||||||
implicitHeight: Kirigami.Units.iconSizes.smallMedium
|
|
||||||
Layout.rightMargin: Kirigami.Units.smallSpacing
|
|
||||||
visible: currentRoom.pushNotificationState === PushNotificationState.Mute && !configButton.visible && unreadCount <= 0
|
|
||||||
Accessible.name: i18n("Muted room")
|
|
||||||
}
|
|
||||||
QQC2.Label {
|
|
||||||
id: notificationCountLabel
|
|
||||||
text: notificationCount > 0 ? notificationCount : "●"
|
|
||||||
visible: unreadCount > 0
|
|
||||||
color: Kirigami.Theme.textColor
|
|
||||||
Layout.rightMargin: Kirigami.Units.smallSpacing
|
|
||||||
Layout.minimumHeight: Kirigami.Units.iconSizes.smallMedium
|
|
||||||
Layout.minimumWidth: Math.max(notificationCountTextMetrics.advanceWidth + Kirigami.Units.smallSpacing * 2, height)
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
background: Rectangle {
|
|
||||||
visible: notificationCount > 0
|
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.Button
|
|
||||||
color: highlightCount > 0 ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.disabledTextColor
|
|
||||||
opacity: highlightCount > 0 ? 1 : 0.3
|
|
||||||
radius: height / 2
|
|
||||||
}
|
|
||||||
|
|
||||||
TextMetrics {
|
|
||||||
id: notificationCountTextMetrics
|
|
||||||
text: notificationCountLabel.text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
QQC2.Button {
|
|
||||||
id: configButton
|
|
||||||
visible: roomListItem.hovered && !Kirigami.Settings.isMobile && !Config.compactRoomList
|
|
||||||
Accessible.name: i18n("Configure room")
|
|
||||||
|
|
||||||
action: Kirigami.Action {
|
|
||||||
id: optionAction
|
|
||||||
icon.name: "configure"
|
|
||||||
onTriggered: {
|
|
||||||
createRoomListContextMenu()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createRoomListContextMenu() {
|
|
||||||
const menu = roomListContextMenu.createObject(page, {room: currentRoom})
|
|
||||||
if (!Kirigami.Settings.isMobile && !Config.compactRoomList) {
|
|
||||||
configButton.visible = true
|
|
||||||
configButton.down = true
|
|
||||||
}
|
|
||||||
menu.closed.connect(function() {
|
|
||||||
configButton.down = undefined
|
|
||||||
configButton.visible = Qt.binding(function() { return roomListItem.hovered && !Kirigami.Settings.isMobile && !Config.compactRoomList })
|
|
||||||
})
|
|
||||||
menu.open()
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property bool hasUnread: unreadCount > 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
footer: UserInfo {
|
|
||||||
width: parent.width
|
|
||||||
visible: !page.collapsed
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
parent: applicationWindow().overlay.parent
|
|
||||||
|
|
||||||
x: page.currentWidth - width / 2
|
|
||||||
width: Kirigami.Units.smallSpacing * 2
|
|
||||||
z: page.z + 1
|
|
||||||
enabled: RoomManager.hasOpenRoom && applicationWindow().width >= Kirigami.Units.gridUnit * 35
|
|
||||||
visible: enabled
|
|
||||||
cursorShape: Qt.SplitHCursor
|
|
||||||
|
|
||||||
property int _lastX
|
|
||||||
|
|
||||||
onPressed: mouse => {
|
|
||||||
_lastX = mouse.x;
|
|
||||||
}
|
|
||||||
onPositionChanged: mouse => {
|
|
||||||
if (_lastX == -1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (mouse.x > _lastX) {
|
|
||||||
// we moved to the right
|
|
||||||
if (_private.currentWidth < _private.collapseWidth && _private.currentWidth + (mouse.x - _lastX) >= _private.collapseWidth) {
|
|
||||||
// Here we get back directly to a more wide mode.
|
|
||||||
_private.currentWidth = _private.collapseWidth;
|
|
||||||
Config.collapsed = false;
|
|
||||||
} else if (_private.currentWidth >= _private.collapseWidth) {
|
|
||||||
// Increase page width
|
|
||||||
_private.currentWidth = Math.min(_private.defaultWidth, _private.currentWidth + (mouse.x - _lastX));
|
|
||||||
}
|
|
||||||
} else if (mouse.x < _lastX) {
|
|
||||||
const tmpWidth = _private.currentWidth - (_lastX - mouse.x);
|
|
||||||
if (tmpWidth < _private.collapseWidth) {
|
|
||||||
_private.currentWidth = Qt.binding(() => _private.collapsedSize + (page.contentItem.QQC2.ScrollBar.vertical.visible ? page.contentItem.QQC2.ScrollBar.vertical.width : 0));
|
|
||||||
Config.collapsed = true;
|
|
||||||
} else {
|
|
||||||
_private.currentWidth = tmpWidth;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Hold the modifiable currentWidth in a private object so that only internal
|
|
||||||
* members can modify it.
|
|
||||||
*/
|
|
||||||
QtObject {
|
|
||||||
id: _private
|
|
||||||
property int currentWidth: Config.collapsed ? collapsedSize : defaultWidth
|
|
||||||
readonly property int defaultWidth: Kirigami.Units.gridUnit * 17
|
|
||||||
readonly property int collapseWidth: Kirigami.Units.gridUnit * 10
|
|
||||||
readonly property int collapsedSize: Kirigami.Units.gridUnit * 3 - Kirigami.Units.smallSpacing * 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -41,6 +41,7 @@ Kirigami.ScrollablePage {
|
|||||||
} else if (page.currentRoom.isInvite) {
|
} else if (page.currentRoom.isInvite) {
|
||||||
page.currentRoom.clearInvitationNotification();
|
page.currentRoom.clearInvitationNotification();
|
||||||
}
|
}
|
||||||
|
currentRoom.markAllMessagesAsRead();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,6 +52,7 @@ Kirigami.ScrollablePage {
|
|||||||
if (!Kirigami.Settings.isMobile) {
|
if (!Kirigami.Settings.isMobile) {
|
||||||
chatBox.chatBar.forceActiveFocus();
|
chatBox.chatBar.forceActiveFocus();
|
||||||
}
|
}
|
||||||
|
currentRoom.markAllMessagesAsRead();
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
@@ -263,7 +265,7 @@ Kirigami.ScrollablePage {
|
|||||||
anchors.leftMargin: messageListView.sectionBannerItem ? messageListView.sectionBannerItem.x : 0
|
anchors.leftMargin: messageListView.sectionBannerItem ? messageListView.sectionBannerItem.x : 0
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
|
|
||||||
maxWidth: messageListView.sectionBannerItem ? messageListView.sectionBannerItem.width - Kirigami.Units.largeSpacing * 2 : 0
|
maxWidth: Config.compactLayout ? messageListView.width : (messageListView.sectionBannerItem ? messageListView.sectionBannerItem.width - Kirigami.Units.largeSpacing * 2 : 0)
|
||||||
z: 3
|
z: 3
|
||||||
visible: messageListView.sectionBannerItem != undefined && messageListView.sectionBannerItem.ListView.section != "" && !Config.blur
|
visible: messageListView.sectionBannerItem != undefined && messageListView.sectionBannerItem.ListView.section != "" && !Config.blur
|
||||||
labelText: messageListView.sectionBannerItem ? messageListView.sectionBannerItem.ListView.section : ""
|
labelText: messageListView.sectionBannerItem ? messageListView.sectionBannerItem.ListView.section : ""
|
||||||
@@ -476,7 +478,7 @@ Kirigami.ScrollablePage {
|
|||||||
id: hoverActions
|
id: hoverActions
|
||||||
property var event: null
|
property var event: null
|
||||||
property bool userMsg: event && event.author.id === Controller.activeConnection.localUserId
|
property bool userMsg: event && event.author.id === Controller.activeConnection.localUserId
|
||||||
property bool showEdit: event && (userMsg && (event.eventType === MessageEventModel.Emote || event.eventType === MessageEventModel.Message))
|
property bool showEdit: event && (userMsg && (event.delegateType === MessageEventModel.Emote || event.delegateType === MessageEventModel.Message))
|
||||||
property var delegate: null
|
property var delegate: null
|
||||||
property var bubble: null
|
property var bubble: null
|
||||||
property var hovered: bubble && bubble.hovered
|
property var hovered: bubble && bubble.hovered
|
||||||
@@ -581,37 +583,42 @@ Kirigami.ScrollablePage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
background: FancyEffectsContainer {
|
background: Rectangle {
|
||||||
id: fancyEffectsContainer
|
color: Config.compactLayout ? Kirigami.Theme.backgroundColor : "transparent"
|
||||||
z: 100
|
|
||||||
|
|
||||||
enabled: Config.showFancyEffects
|
FancyEffectsContainer {
|
||||||
|
id: fancyEffectsContainer
|
||||||
|
anchors.fill: parent
|
||||||
|
z: 100
|
||||||
|
|
||||||
function processFancyEffectsReason(fancyEffect) {
|
|
||||||
if (fancyEffect === "snowflake") {
|
|
||||||
fancyEffectsContainer.showSnowEffect()
|
|
||||||
}
|
|
||||||
if (fancyEffect === "fireworks") {
|
|
||||||
fancyEffectsContainer.showFireworksEffect()
|
|
||||||
}
|
|
||||||
if (fancyEffect === "confetti") {
|
|
||||||
fancyEffectsContainer.showConfettiEffect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
enabled: Config.showFancyEffects
|
enabled: Config.showFancyEffects
|
||||||
target: messageEventModel
|
|
||||||
function onFancyEffectsReasonFound(fancyEffect) {
|
|
||||||
fancyEffectsContainer.processFancyEffectsReason(fancyEffect)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
function processFancyEffectsReason(fancyEffect) {
|
||||||
enabled: Config.showFancyEffects
|
if (fancyEffect === "snowflake") {
|
||||||
target: actionsHandler
|
fancyEffectsContainer.showSnowEffect()
|
||||||
function onShowEffect(fancyEffect) {
|
}
|
||||||
fancyEffectsContainer.processFancyEffectsReason(fancyEffect)
|
if (fancyEffect === "fireworks") {
|
||||||
|
fancyEffectsContainer.showFireworksEffect()
|
||||||
|
}
|
||||||
|
if (fancyEffect === "confetti") {
|
||||||
|
fancyEffectsContainer.showConfettiEffect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
enabled: Config.showFancyEffects
|
||||||
|
target: messageEventModel
|
||||||
|
function onFancyEffectsReasonFound(fancyEffect) {
|
||||||
|
fancyEffectsContainer.processFancyEffectsReason(fancyEffect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
enabled: Config.showFancyEffects
|
||||||
|
target: actionsHandler
|
||||||
|
function onShowEffect(fancyEffect) {
|
||||||
|
fancyEffectsContainer.processFancyEffectsReason(fancyEffect)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -637,7 +644,7 @@ Kirigami.ScrollablePage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function eventToIndex(eventID) {
|
function eventToIndex(eventID) {
|
||||||
const index = messageEventModel.eventIDToIndex(eventID)
|
const index = messageEventModel.eventIdToRow(eventID)
|
||||||
if (index === -1)
|
if (index === -1)
|
||||||
return -1
|
return -1
|
||||||
return sortedMessageEventModel.mapFromSource(messageEventModel.index(index, 0)).row
|
return sortedMessageEventModel.mapFromSource(messageEventModel.index(index, 0)).row
|
||||||
@@ -697,7 +704,7 @@ Kirigami.ScrollablePage {
|
|||||||
eventId: event.eventId,
|
eventId: event.eventId,
|
||||||
formattedBody: event.formattedBody,
|
formattedBody: event.formattedBody,
|
||||||
source: event.source,
|
source: event.source,
|
||||||
eventType: event.eventType,
|
eventType: event.delegateType,
|
||||||
plainMessage: plainMessage,
|
plainMessage: plainMessage,
|
||||||
});
|
});
|
||||||
contextMenu.open();
|
contextMenu.open();
|
||||||
|
|||||||
@@ -157,24 +157,35 @@ Kirigami.OverlayDrawer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TextEdit {
|
QQC2.ScrollView {
|
||||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: room && room.topic ? room.topic.replace(replaceLinks, "<a href=\"$1\">$1</a>") : i18n("No Topic")
|
Layout.fillHeight: true
|
||||||
readonly property var replaceLinks: /(http[s]?:\/\/[^ \r\n]*)/g
|
Layout.minimumHeight: Math.min(topicText.contentHeight, Kirigami.Units.gridUnit * 15)
|
||||||
textFormat: TextEdit.MarkdownText
|
|
||||||
wrapMode: Text.WordWrap
|
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
||||||
selectByMouse: true
|
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
|
||||||
color: Kirigami.Theme.textColor
|
|
||||||
selectedTextColor: Kirigami.Theme.highlightedTextColor
|
QQC2.TextArea {
|
||||||
selectionColor: Kirigami.Theme.highlightColor
|
id: topicText
|
||||||
onLinkActivated: UrlHelper.openUrl(link)
|
padding: 0
|
||||||
readOnly: true
|
text: room && room.topic ? room.topic.replace(replaceLinks, "<a href=\"$1\">$1</a>") : i18n("No Topic")
|
||||||
MouseArea {
|
readonly property var replaceLinks: /(http[s]?:\/\/[^ \r\n]*)/g
|
||||||
anchors.fill: parent
|
textFormat: TextEdit.MarkdownText
|
||||||
acceptedButtons: Qt.NoButton
|
wrapMode: Text.WordWrap
|
||||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor
|
selectByMouse: true
|
||||||
|
color: Kirigami.Theme.textColor
|
||||||
|
selectedTextColor: Kirigami.Theme.highlightedTextColor
|
||||||
|
selectionColor: Kirigami.Theme.highlightColor
|
||||||
|
onLinkActivated: UrlHelper.openUrl(link)
|
||||||
|
readOnly: true
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
acceptedButtons: Qt.NoButton
|
||||||
|
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor
|
||||||
|
}
|
||||||
|
background: Item {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,6 +241,9 @@ Kirigami.OverlayDrawer {
|
|||||||
QQC2.ToolTip.text: i18n("Search user in room")
|
QQC2.ToolTip.text: i18n("Search user in room")
|
||||||
QQC2.ToolTip.visible: hovered
|
QQC2.ToolTip.visible: hovered
|
||||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
|
onToggled: {
|
||||||
|
userListSearchField.text = "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QQC2.ToolButton {
|
QQC2.ToolButton {
|
||||||
@@ -248,7 +262,7 @@ Kirigami.OverlayDrawer {
|
|||||||
|
|
||||||
QQC2.Label {
|
QQC2.Label {
|
||||||
Layout.alignment: Qt.AlignRight
|
Layout.alignment: Qt.AlignRight
|
||||||
text: room ? i18np("%1 Member", "%1 Members", room.joinedCount) : i18n("No Member Count")
|
text: room ? i18np("%1 member", "%1 members", room.joinedCount) : i18n("No member count")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,6 +284,7 @@ Kirigami.OverlayDrawer {
|
|||||||
QQC2.ScrollView {
|
QQC2.ScrollView {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
Layout.minimumHeight: Math.min(topicText.contentHeight, Kirigami.Units.gridUnit * 15)
|
||||||
|
|
||||||
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
||||||
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
|
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import QtQuick.Layouts 1.15
|
|||||||
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 './RoomList' as RoomList
|
||||||
|
import './Dialog' as Dialog
|
||||||
|
|
||||||
Kirigami.ApplicationWindow {
|
Kirigami.ApplicationWindow {
|
||||||
id: root
|
id: root
|
||||||
@@ -24,7 +26,7 @@ Kirigami.ApplicationWindow {
|
|||||||
pageStack.initialPage: LoadingPage {}
|
pageStack.initialPage: LoadingPage {}
|
||||||
pageStack.globalToolBar.canContainHandles: true
|
pageStack.globalToolBar.canContainHandles: true
|
||||||
|
|
||||||
property RoomListPage roomListPage
|
property RoomList.Page roomListPage
|
||||||
property bool roomListLoaded: false
|
property bool roomListLoaded: false
|
||||||
|
|
||||||
property RoomPage roomPage
|
property RoomPage roomPage
|
||||||
@@ -97,10 +99,6 @@ Kirigami.ApplicationWindow {
|
|||||||
roomItem.forceActiveFocus();
|
roomItem.forceActiveFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onPushWelcomePage() {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
function onOpenRoomInNewWindow(room) {
|
function onOpenRoomInNewWindow(room) {
|
||||||
const secondaryWindow = roomWindow.createObject(undefined, {currentRoom: room});
|
const secondaryWindow = roomWindow.createObject(undefined, {currentRoom: room});
|
||||||
secondaryWindow.width = root.width - pageStack.get(0).width;
|
secondaryWindow.width = root.width - pageStack.get(0).width;
|
||||||
@@ -167,7 +165,7 @@ Kirigami.ApplicationWindow {
|
|||||||
pageStack.globalToolBar.style: Kirigami.ApplicationHeaderStyle.ToolBar
|
pageStack.globalToolBar.style: Kirigami.ApplicationHeaderStyle.ToolBar
|
||||||
pageStack.globalToolBar.showNavigationButtons: pageStack.currentIndex > 0 || pageStack.layers.depth > 1 ? Kirigami.ApplicationHeaderStyle.ShowBackButton : 0
|
pageStack.globalToolBar.showNavigationButtons: pageStack.currentIndex > 0 || pageStack.layers.depth > 1 ? Kirigami.ApplicationHeaderStyle.ShowBackButton : 0
|
||||||
|
|
||||||
ConfirmLogoutDialog {
|
Dialog.ConfirmLogout {
|
||||||
id: confirmLogoutDialog
|
id: confirmLogoutDialog
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,7 +198,7 @@ Kirigami.ApplicationWindow {
|
|||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: roomListComponent
|
id: roomListComponent
|
||||||
RoomListPage {
|
RoomList.Page {
|
||||||
id: roomList
|
id: roomList
|
||||||
|
|
||||||
Shortcut {
|
Shortcut {
|
||||||
|
|||||||
22
src/res.qrc
22
src/res.qrc
@@ -2,9 +2,21 @@
|
|||||||
<qresource prefix="/">
|
<qresource prefix="/">
|
||||||
<file alias="icons/org.kde.neochat.svg">../org.kde.neochat.svg</file>
|
<file alias="icons/org.kde.neochat.svg">../org.kde.neochat.svg</file>
|
||||||
<file alias="icons/org.kde.neochat.tray.svg">../org.kde.neochat.tray.svg</file>
|
<file alias="icons/org.kde.neochat.tray.svg">../org.kde.neochat.tray.svg</file>
|
||||||
|
|
||||||
<file alias="main.qml">qml/main.qml</file>
|
<file alias="main.qml">qml/main.qml</file>
|
||||||
|
|
||||||
|
<file alias="RoomList/AccountMenu.qml">qml/Page/RoomList/AccountMenu.qml</file>
|
||||||
|
<file alias="RoomList/ExploreComponent.qml">qml/Page/RoomList/ExploreComponent.qml</file>
|
||||||
|
<file alias="RoomList/ContextMenu.qml">qml/Page/RoomList/ContextMenu.qml</file>
|
||||||
|
<file alias="RoomList/CollapsedRoomDelegate.qml">qml/Page/RoomList/CollapsedRoomDelegate.qml</file>
|
||||||
|
<file alias="RoomList/RoomDelegate.qml">qml/Page/RoomList/RoomDelegate.qml</file>
|
||||||
|
<file alias="RoomList/Page.qml">qml/Page/RoomList/Page.qml</file>
|
||||||
|
<file alias="RoomList/SpaceListContextMenu.qml">qml/Page/RoomList/SpaceListContextMenu.qml</file>
|
||||||
|
<file alias="RoomList/SpaceDelegate.qml">qml/Page/RoomList/SpaceDelegate.qml</file>
|
||||||
|
<file alias="RoomList/SpaceListView.qml">qml/Page/RoomList/SpaceListView.qml</file>
|
||||||
|
<file alias="RoomList/UserInfo.qml">qml/Page/RoomList/UserInfo.qml</file>
|
||||||
|
|
||||||
<file alias="LoadingPage.qml">qml/Page/LoadingPage.qml</file>
|
<file alias="LoadingPage.qml">qml/Page/LoadingPage.qml</file>
|
||||||
<file alias="RoomListPage.qml">qml/Page/RoomListPage.qml</file>
|
|
||||||
<file alias="RoomPage.qml">qml/Page/RoomPage.qml</file>
|
<file alias="RoomPage.qml">qml/Page/RoomPage.qml</file>
|
||||||
<file alias="RoomWindow.qml">qml/Page/RoomWindow.qml</file>
|
<file alias="RoomWindow.qml">qml/Page/RoomWindow.qml</file>
|
||||||
<file alias="JoinRoomPage.qml">qml/Page/JoinRoomPage.qml</file>
|
<file alias="JoinRoomPage.qml">qml/Page/JoinRoomPage.qml</file>
|
||||||
@@ -18,12 +30,10 @@
|
|||||||
<file alias="Categories.qml">qml/RoomSettings/Categories.qml</file>
|
<file alias="Categories.qml">qml/RoomSettings/Categories.qml</file>
|
||||||
<file alias="Permissions.qml">qml/RoomSettings/Permissions.qml</file>
|
<file alias="Permissions.qml">qml/RoomSettings/Permissions.qml</file>
|
||||||
<file alias="FullScreenImage.qml">qml/Component/FullScreenImage.qml</file>
|
<file alias="FullScreenImage.qml">qml/Component/FullScreenImage.qml</file>
|
||||||
<file alias="UserInfo.qml">qml/Component/UserInfo.qml</file>
|
|
||||||
<file alias="FancyEffectsContainer.qml">qml/Component/FancyEffectsContainer.qml</file>
|
<file alias="FancyEffectsContainer.qml">qml/Component/FancyEffectsContainer.qml</file>
|
||||||
<file alias="TypingPane.qml">qml/Component/TypingPane.qml</file>
|
<file alias="TypingPane.qml">qml/Component/TypingPane.qml</file>
|
||||||
<file alias="ShimmerGradient.qml">qml/Component/ShimmerGradient.qml</file>
|
<file alias="ShimmerGradient.qml">qml/Component/ShimmerGradient.qml</file>
|
||||||
<file alias="QuickSwitcher.qml">qml/Component/QuickSwitcher.qml</file>
|
<file alias="QuickSwitcher.qml">qml/Component/QuickSwitcher.qml</file>
|
||||||
<file alias="ExploreComponent.qml">qml/Component/ExploreComponent.qml</file>
|
|
||||||
<file alias="ChatBox.qml">qml/Component/ChatBox/ChatBox.qml</file>
|
<file alias="ChatBox.qml">qml/Component/ChatBox/ChatBox.qml</file>
|
||||||
<file alias="ChatBar.qml">qml/Component/ChatBox/ChatBar.qml</file>
|
<file alias="ChatBar.qml">qml/Component/ChatBox/ChatBar.qml</file>
|
||||||
<file alias="AttachmentPane.qml">qml/Component/ChatBox/AttachmentPane.qml</file>
|
<file alias="AttachmentPane.qml">qml/Component/ChatBox/AttachmentPane.qml</file>
|
||||||
@@ -66,7 +76,7 @@
|
|||||||
<file alias="EmojiDialog.qml">qml/Dialog/EmojiDialog.qml</file>
|
<file alias="EmojiDialog.qml">qml/Dialog/EmojiDialog.qml</file>
|
||||||
<file alias="OpenFileDialog.qml">qml/Dialog/OpenFileDialog.qml</file>
|
<file alias="OpenFileDialog.qml">qml/Dialog/OpenFileDialog.qml</file>
|
||||||
<file alias="KeyVerificationDialog.qml">qml/Dialog/KeyVerification/KeyVerificationDialog.qml</file>
|
<file alias="KeyVerificationDialog.qml">qml/Dialog/KeyVerification/KeyVerificationDialog.qml</file>
|
||||||
<file alias="ConfirmLogoutDialog.qml">qml/Dialog/ConfirmLogoutDialog.qml</file>
|
<file alias="Dialog/ConfirmLogout.qml">qml/Dialog/ConfirmLogout.qml</file>
|
||||||
<file alias="PowerLevelDialog.qml">qml/Dialog/PowerLevelDialog.qml</file>
|
<file alias="PowerLevelDialog.qml">qml/Dialog/PowerLevelDialog.qml</file>
|
||||||
<file alias="Message.qml">qml/Dialog/KeyVerification/Message.qml</file>
|
<file alias="Message.qml">qml/Dialog/KeyVerification/Message.qml</file>
|
||||||
<file alias="EmojiItem.qml">qml/Dialog/KeyVerification/EmojiItem.qml</file>
|
<file alias="EmojiItem.qml">qml/Dialog/KeyVerification/EmojiItem.qml</file>
|
||||||
@@ -75,13 +85,10 @@
|
|||||||
<file alias="VerificationCanceled.qml">qml/Dialog/KeyVerification/VerificationCanceled.qml</file>
|
<file alias="VerificationCanceled.qml">qml/Dialog/KeyVerification/VerificationCanceled.qml</file>
|
||||||
<file alias="GlobalMenu.qml">qml/Menu/GlobalMenu.qml</file>
|
<file alias="GlobalMenu.qml">qml/Menu/GlobalMenu.qml</file>
|
||||||
<file alias="EditMenu.qml">qml/Menu/EditMenu.qml</file>
|
<file alias="EditMenu.qml">qml/Menu/EditMenu.qml</file>
|
||||||
<file alias="AccountMenu.qml">qml/Menu/AccountMenu.qml</file>
|
|
||||||
<file alias="MessageDelegateContextMenu.qml">qml/Menu/Timeline/MessageDelegateContextMenu.qml</file>
|
<file alias="MessageDelegateContextMenu.qml">qml/Menu/Timeline/MessageDelegateContextMenu.qml</file>
|
||||||
<file alias="FileDelegateContextMenu.qml">qml/Menu/Timeline/FileDelegateContextMenu.qml</file>
|
<file alias="FileDelegateContextMenu.qml">qml/Menu/Timeline/FileDelegateContextMenu.qml</file>
|
||||||
<file alias="MessageSourceSheet.qml">qml/Menu/Timeline/MessageSourceSheet.qml</file>
|
<file alias="MessageSourceSheet.qml">qml/Menu/Timeline/MessageSourceSheet.qml</file>
|
||||||
<file alias="ReportSheet.qml">qml/Menu/Timeline/ReportSheet.qml</file>
|
<file alias="ReportSheet.qml">qml/Menu/Timeline/ReportSheet.qml</file>
|
||||||
<file alias="RoomListContextMenu.qml">qml/Menu/RoomListContextMenu.qml</file>
|
|
||||||
<file alias="SpaceListContextMenu.qml">qml/Menu/SpaceListContextMenu.qml</file>
|
|
||||||
<file alias="glowdot.png">qml/Component/glowdot.png</file>
|
<file alias="glowdot.png">qml/Component/glowdot.png</file>
|
||||||
<file alias="confetti.png">qml/Component/confetti.png</file>
|
<file alias="confetti.png">qml/Component/confetti.png</file>
|
||||||
<file alias="SettingsPage.qml">qml/Settings/SettingsPage.qml</file>
|
<file alias="SettingsPage.qml">qml/Settings/SettingsPage.qml</file>
|
||||||
@@ -107,5 +114,6 @@
|
|||||||
<file alias="EmojiDelegate.qml">qml/Component/Emoji/EmojiDelegate.qml</file>
|
<file alias="EmojiDelegate.qml">qml/Component/Emoji/EmojiDelegate.qml</file>
|
||||||
<file alias="EmojiGrid.qml">qml/Component/Emoji/EmojiGrid.qml</file>
|
<file alias="EmojiGrid.qml">qml/Component/Emoji/EmojiGrid.qml</file>
|
||||||
<file alias="SearchPage.qml">qml/Page/SearchPage.qml</file>
|
<file alias="SearchPage.qml">qml/Page/SearchPage.qml</file>
|
||||||
|
<file alias="LocationDelegate.qml">qml/Component/Timeline/LocationDelegate.qml</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|||||||
@@ -121,8 +121,6 @@ void RoomManager::openRoomForActiveConnection()
|
|||||||
if (room) {
|
if (room) {
|
||||||
enterRoom(room);
|
enterRoom(room);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Q_EMIT pushWelcomePage();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -91,9 +91,6 @@ Q_SIGNALS:
|
|||||||
/// Go to the specified event in the current room.
|
/// Go to the specified event in the current room.
|
||||||
void goToEvent(const QString &event);
|
void goToEvent(const QString &event);
|
||||||
|
|
||||||
/// Signal triggered when the pageStack should push a welcome page.
|
|
||||||
void pushWelcomePage();
|
|
||||||
|
|
||||||
/// Signal triggered when a room need to be opened in a new window.
|
/// Signal triggered when a room need to be opened in a new window.
|
||||||
void openRoomInNewWindow(NeoChatRoom *room);
|
void openRoomInNewWindow(NeoChatRoom *room);
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,14 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
|
#include <events/roommessageevent.h>
|
||||||
|
#include <qstringliteral.h>
|
||||||
#include <util.h>
|
#include <util.h>
|
||||||
|
|
||||||
#include <cmark.h>
|
#include <cmark.h>
|
||||||
|
|
||||||
|
#include <Kirigami/PlatformTheme>
|
||||||
|
|
||||||
static const QStringList allowedTags = {
|
static const QStringList allowedTags = {
|
||||||
QStringLiteral("font"), QStringLiteral("del"), QStringLiteral("h1"), QStringLiteral("h2"), QStringLiteral("h3"), QStringLiteral("h4"),
|
QStringLiteral("font"), QStringLiteral("del"), QStringLiteral("h1"), QStringLiteral("h2"), QStringLiteral("h3"), QStringLiteral("h4"),
|
||||||
QStringLiteral("h5"), QStringLiteral("h6"), QStringLiteral("blockquote"), QStringLiteral("p"), QStringLiteral("a"), QStringLiteral("ul"),
|
QStringLiteral("h5"), QStringLiteral("h6"), QStringLiteral("blockquote"), QStringLiteral("p"), QStringLiteral("a"), QStringLiteral("ul"),
|
||||||
@@ -134,6 +138,46 @@ QString TextHandler::handleRecieveRichText(Qt::TextFormat inputFormat, const Neo
|
|||||||
nextTokenType();
|
nextTokenType();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the message is an emote add the user pill to the front of the message.
|
||||||
|
if (event != nullptr) {
|
||||||
|
auto e = eventCast<const Quotient::RoomMessageEvent>(event);
|
||||||
|
if (e->msgtype() == Quotient::MessageEventType::Emote) {
|
||||||
|
auto author = static_cast<NeoChatUser *>(room->user(e->senderId()));
|
||||||
|
QString emoteString = QStringLiteral("* <a href=\"https://matrix.to/#/") + e->senderId() + QStringLiteral("\" style=\"color:")
|
||||||
|
+ author->color().name() + QStringLiteral("\">") + author->displayname(room) + QStringLiteral("</a> ");
|
||||||
|
if (outputString.startsWith(QStringLiteral("<p>"))) {
|
||||||
|
outputString.insert(3, emoteString);
|
||||||
|
} else {
|
||||||
|
outputString.prepend(emoteString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto e = eventCast<const Quotient::RoomMessageEvent>(event)) {
|
||||||
|
bool isEdited =
|
||||||
|
!e->unsignedJson().isEmpty() && e->unsignedJson().contains("m.relations") && e->unsignedJson()["m.relations"].toObject().contains("m.replace");
|
||||||
|
if (isEdited) {
|
||||||
|
Kirigami::PlatformTheme *theme = static_cast<Kirigami::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::PlatformTheme>(this, true));
|
||||||
|
|
||||||
|
QString editTextColor;
|
||||||
|
if (theme != nullptr) {
|
||||||
|
editTextColor = theme->disabledTextColor().name();
|
||||||
|
} else {
|
||||||
|
editTextColor = QStringLiteral("#000000");
|
||||||
|
}
|
||||||
|
QString editedString = QStringLiteral(" <span style=\"color:") + editTextColor + QStringLiteral("\">(edited)</span>");
|
||||||
|
if (outputString.endsWith(QStringLiteral("</p>"))) {
|
||||||
|
outputString.insert(outputString.length() - 4, editedString);
|
||||||
|
} else if (outputString.endsWith(QStringLiteral("</pre>")) || outputString.endsWith(QStringLiteral("</blockquote>"))
|
||||||
|
|| outputString.endsWith(QStringLiteral("</table>")) || outputString.endsWith(QStringLiteral("</ol>"))
|
||||||
|
|| outputString.endsWith(QStringLiteral("</ul>"))) {
|
||||||
|
outputString.append("<p>" + editedString + "</p>");
|
||||||
|
} else {
|
||||||
|
outputString.append(editedString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replace <del> with <s>
|
* Replace <del> with <s>
|
||||||
* Note: <s> is still not a valid tag for the message from the server. We
|
* Note: <s> is still not a valid tag for the message from the server. We
|
||||||
@@ -151,12 +195,6 @@ QString TextHandler::handleRecievePlainText(Qt::TextFormat inputFormat, const bo
|
|||||||
// Strip mx-reply if present.
|
// Strip mx-reply if present.
|
||||||
m_dataBuffer.remove(TextRegex::removeRichReply);
|
m_dataBuffer.remove(TextRegex::removeRichReply);
|
||||||
|
|
||||||
if (stripNewlines) {
|
|
||||||
m_dataBuffer.replace(QStringLiteral("<br>"), QStringLiteral(" "));
|
|
||||||
m_dataBuffer.replace(QStringLiteral("<br />"), QStringLiteral(" "));
|
|
||||||
m_dataBuffer.replace(u'\n', QStringLiteral(" "));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Escaping then unescaping allows < and > to be maintained in a plain text string
|
// Escaping then unescaping allows < and > to be maintained in a plain text string
|
||||||
// otherwise markdownToHTML will strip what it thinks is a bad html tag entirely.
|
// otherwise markdownToHTML will strip what it thinks is a bad html tag entirely.
|
||||||
if (inputFormat == Qt::PlainText) {
|
if (inputFormat == Qt::PlainText) {
|
||||||
@@ -169,6 +207,14 @@ QString TextHandler::handleRecievePlainText(Qt::TextFormat inputFormat, const bo
|
|||||||
*/
|
*/
|
||||||
m_dataBuffer = markdownToHTML(m_dataBuffer);
|
m_dataBuffer = markdownToHTML(m_dataBuffer);
|
||||||
|
|
||||||
|
if (stripNewlines) {
|
||||||
|
m_dataBuffer.replace(QStringLiteral("<br>\n"), QStringLiteral(" "));
|
||||||
|
m_dataBuffer.replace(QStringLiteral("<br>"), QStringLiteral(" "));
|
||||||
|
m_dataBuffer.replace(QStringLiteral("<br />\n"), QStringLiteral(" "));
|
||||||
|
m_dataBuffer.replace(QStringLiteral("<br />"), QStringLiteral(" "));
|
||||||
|
m_dataBuffer.replace(u'\n', QStringLiteral(" "));
|
||||||
|
}
|
||||||
|
|
||||||
// Strip all tags/attributes except code blocks which will be escaped.
|
// Strip all tags/attributes except code blocks which will be escaped.
|
||||||
QString outputString;
|
QString outputString;
|
||||||
nextTokenType();
|
nextTokenType();
|
||||||
@@ -189,10 +235,9 @@ QString TextHandler::handleRecievePlainText(Qt::TextFormat inputFormat, const bo
|
|||||||
|
|
||||||
// Escaping then unescaping allows < and > to be maintained in a plain text string
|
// Escaping then unescaping allows < and > to be maintained in a plain text string
|
||||||
// otherwise markdownToHTML will strip what it thinks is a bad html tag entirely.
|
// otherwise markdownToHTML will strip what it thinks is a bad html tag entirely.
|
||||||
if (inputFormat == Qt::PlainText) {
|
outputString = unescapeHtml(outputString);
|
||||||
outputString = unescapeHtml(outputString);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
outputString = outputString.trimmed();
|
||||||
return outputString;
|
return outputString;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,6 +280,9 @@ void TextHandler::nextTokenType()
|
|||||||
|
|
||||||
QString TextHandler::getTagType() const
|
QString TextHandler::getTagType() const
|
||||||
{
|
{
|
||||||
|
if (m_nextToken.isEmpty()) {
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
const int tagTypeStart = m_nextToken[1] == u'/' ? 2 : 1;
|
const int tagTypeStart = m_nextToken[1] == u'/' ? 2 : 1;
|
||||||
const int tagTypeEnd = m_nextToken.indexOf(TextRegex::endTagType, tagTypeStart);
|
const int tagTypeEnd = m_nextToken.indexOf(TextRegex::endTagType, tagTypeStart);
|
||||||
return m_nextToken.mid(tagTypeStart, tagTypeEnd - tagTypeStart);
|
return m_nextToken.mid(tagTypeStart, tagTypeEnd - tagTypeStart);
|
||||||
@@ -242,6 +290,9 @@ QString TextHandler::getTagType() const
|
|||||||
|
|
||||||
bool TextHandler::isCloseTag() const
|
bool TextHandler::isCloseTag() const
|
||||||
{
|
{
|
||||||
|
if (m_nextToken.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return m_nextToken[1] == u'/';
|
return m_nextToken[1] == u'/';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
|
#include <QObject>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
@@ -39,8 +40,10 @@ static const QRegularExpression mxId(QStringLiteral(R"((^|[][[:space:](){}`'";])
|
|||||||
* be present as per the matrix spec
|
* be present as per the matrix spec
|
||||||
* (https://spec.matrix.org/v1.5/client-server-api/#mroommessage-msgtypes).
|
* (https://spec.matrix.org/v1.5/client-server-api/#mroommessage-msgtypes).
|
||||||
*/
|
*/
|
||||||
class TextHandler
|
class TextHandler : public QObject
|
||||||
{
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief List of token types
|
* @brief List of token types
|
||||||
|
|||||||
Reference in New Issue
Block a user