Compare commits
70 Commits
work/tobia
...
work/nvrwh
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45c46ddcbb | ||
|
|
31d83ac0e3 | ||
|
|
909eec30d2 | ||
|
|
dbed3e99c2 | ||
|
|
4d0db0b5c2 | ||
|
|
6e49aaf17b | ||
|
|
1092d75f2e | ||
|
|
8059c3797d | ||
|
|
1302d62ad9 | ||
|
|
8eaae4034d | ||
|
|
354e3414a1 | ||
|
|
fc24beae6d | ||
|
|
923cc67b55 | ||
|
|
30e24069bc | ||
|
|
f7533a454c | ||
|
|
ab4e1a86dc | ||
|
|
d28c2ed113 | ||
|
|
3a467328f5 | ||
|
|
979d83cb01 | ||
|
|
d165cd955d | ||
|
|
6eb770343e | ||
|
|
54be52b855 | ||
|
|
d201333409 | ||
|
|
3db8b4cd17 | ||
|
|
0e246a00bc | ||
|
|
e638fa8929 | ||
|
|
cdc982ad91 | ||
|
|
bff69e21ad | ||
|
|
e117cc0cfb | ||
|
|
3af1a88e05 | ||
|
|
aa116a35f5 | ||
|
|
ac232d7f55 | ||
|
|
928911e33c | ||
|
|
e3c30f5bb3 | ||
|
|
4b51855528 | ||
|
|
79c27db0a9 | ||
|
|
78e42ab352 | ||
|
|
93909c45ee | ||
|
|
eda0bf4b23 | ||
|
|
4f4b10e0b6 | ||
|
|
2b374a8bec | ||
|
|
7821de4a8d | ||
|
|
86b88c851f | ||
|
|
1157882f1b | ||
|
|
594d1373c9 | ||
|
|
1e6948bbc7 | ||
|
|
41845b97d5 | ||
|
|
bb56f74622 | ||
|
|
a98f6ac331 | ||
|
|
15d6287995 | ||
|
|
0242ab72e8 | ||
|
|
4b7cbf37d5 | ||
|
|
0ccfe7d991 | ||
|
|
ab5585cd06 | ||
|
|
b25a0a7a4e | ||
|
|
f574c12adc | ||
|
|
05a2f03c18 | ||
|
|
cbc2e65856 | ||
|
|
1df80a7d2a | ||
|
|
050a014df7 | ||
|
|
4775ae09b0 | ||
|
|
8d4c3bb4fc | ||
|
|
439260ff03 | ||
|
|
5b04ad6805 | ||
|
|
6d77ed1e0e | ||
|
|
cc7f783b50 | ||
|
|
1d7ed1983b | ||
|
|
c0151353c5 | ||
|
|
72994248fa | ||
|
|
af0d3d5ee1 |
@@ -14,7 +14,7 @@ set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_
|
||||
|
||||
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
|
||||
|
||||
set(KF_MIN_VERSION "6.4")
|
||||
set(KF_MIN_VERSION "6.5")
|
||||
set(QT_MIN_VERSION "6.5")
|
||||
|
||||
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
|
||||
|
||||
@@ -107,8 +107,13 @@ void ChatBarCacheTest::reply()
|
||||
void ChatBarCacheTest::edit()
|
||||
{
|
||||
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(room));
|
||||
|
||||
chatBarCache->setText(QLatin1String("some text"));
|
||||
chatBarCache->setAttachmentPath(QLatin1String("some/path"));
|
||||
connect(chatBarCache.get(), &ChatBarCache::relationIdChanged, this, [](const QString &oldEventId, const QString &newEventId) {
|
||||
QCOMPARE(oldEventId, QString());
|
||||
QCOMPARE(newEventId, QString(QLatin1String("$153456789:example.org")));
|
||||
});
|
||||
chatBarCache->setEditId(QLatin1String("$153456789:example.org"));
|
||||
|
||||
QCOMPARE(chatBarCache->text(), QLatin1String("some text"));
|
||||
|
||||
@@ -46,6 +46,7 @@ private Q_SLOTS:
|
||||
void sendCustomEmojiCode_data();
|
||||
void sendCustomEmojiCode();
|
||||
|
||||
void receiveSpacelessSelfClosingTag();
|
||||
void receiveStripReply();
|
||||
void receivePlainTextIn();
|
||||
|
||||
@@ -252,6 +253,19 @@ void TextHandlerTest::sendCustomEmojiCode()
|
||||
QCOMPARE(testTextHandler.handleSendText(), testOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::receiveSpacelessSelfClosingTag()
|
||||
{
|
||||
const QString testInputString = QStringLiteral("Test...<br/>...ing");
|
||||
const QString testRichOutputString = QStringLiteral("Test...<br/>...ing");
|
||||
const QString testPlainOutputString = QStringLiteral("Test...\n...ing");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(), testRichOutputString);
|
||||
QCOMPARE(testTextHandler.handleRecievePlainText(Qt::RichText), testPlainOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::receiveStripReply()
|
||||
{
|
||||
const QString testInputString = QStringLiteral(
|
||||
@@ -449,6 +463,9 @@ void TextHandlerTest::receiveRichPlainUrl()
|
||||
QString testOutputStringMxId = QStringLiteral(
|
||||
"<b><a href=\"https://matrix.to/#/@user:kde.org\">@user:kde.org</a></b> <b><a href=\"https://matrix.to/#/@user:kde.org\">Link already rich</a></b>");
|
||||
|
||||
QString testInputStringMxIdWithPrefix = QStringLiteral("a @user:kde.org b");
|
||||
QString testOutputStringMxIdWithPrefix = QStringLiteral("a <b><a href=\"https://matrix.to/#/@user:kde.org\">@user:kde.org</a></b> b");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputStringLink1);
|
||||
|
||||
@@ -462,6 +479,9 @@ void TextHandlerTest::receiveRichPlainUrl()
|
||||
|
||||
testTextHandler.setData(testInputStringMxId);
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringMxId);
|
||||
|
||||
testTextHandler.setData(testInputStringMxIdWithPrefix);
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringMxIdWithPrefix);
|
||||
}
|
||||
|
||||
void TextHandlerTest::receiveRichEdited_data()
|
||||
@@ -536,7 +556,7 @@ void TextHandlerTest::componentOutput_data()
|
||||
"someField }\nCustomQml {\n someTextProperty: someField.text\n}\n</code></pre>Sure you can, it's still local to the same file where you "
|
||||
"defined the id")
|
||||
<< QList<MessageComponent>{
|
||||
MessageComponent{MessageComponentType::Text, QStringLiteral("Ah, you mean something like"), {}},
|
||||
MessageComponent{MessageComponentType::Text, QStringLiteral("Ah, you mean something like<br/>"), {}},
|
||||
MessageComponent{
|
||||
MessageComponentType::Code,
|
||||
QStringLiteral(
|
||||
|
||||
@@ -17,7 +17,6 @@ class WindowControllerTest : public QObject
|
||||
|
||||
private Q_SLOTS:
|
||||
void nullWindow();
|
||||
void geometry();
|
||||
void showAndRaise();
|
||||
void toggle();
|
||||
|
||||
@@ -30,32 +29,10 @@ void WindowControllerTest::nullWindow()
|
||||
auto &instance = WindowController::instance();
|
||||
QCOMPARE(instance.window(), nullptr);
|
||||
|
||||
instance.restoreGeometry();
|
||||
instance.saveGeometry();
|
||||
instance.showAndRaiseWindow({});
|
||||
instance.toggleWindow();
|
||||
}
|
||||
|
||||
void WindowControllerTest::geometry()
|
||||
{
|
||||
auto &instance = WindowController::instance();
|
||||
|
||||
QWindow window;
|
||||
window.setGeometry(0, 0, 200, 200);
|
||||
instance.setWindow(&window);
|
||||
QCOMPARE(instance.window(), &window);
|
||||
|
||||
instance.saveGeometry();
|
||||
const auto stateConfig = KSharedConfig::openStateConfig();
|
||||
KConfigGroup windowGroup = stateConfig->group(QStringLiteral("Window"));
|
||||
QCOMPARE(KWindowConfig::hasSavedWindowSize(windowGroup), true);
|
||||
|
||||
window.setGeometry(0, 0, 400, 400);
|
||||
QCOMPARE(window.geometry(), QRect(0, 0, 400, 400));
|
||||
instance.restoreGeometry();
|
||||
QCOMPARE(window.geometry(), QRect(0, 0, 200, 200));
|
||||
}
|
||||
|
||||
void WindowControllerTest::showAndRaise()
|
||||
{
|
||||
auto &instance = WindowController::instance();
|
||||
|
||||
@@ -429,6 +429,7 @@
|
||||
<content_attribute id="social-chat">intense</content_attribute>
|
||||
</content_rating>
|
||||
<releases>
|
||||
<release version="24.08.1" date="2024-09-12"/>
|
||||
<release version="24.08.0" date="2024-08-22"/>
|
||||
<release version="24.05.2" date="2024-07-04"/>
|
||||
<release version="24.05.1" date="2024-06-13"/>
|
||||
|
||||
896
po/ar/neochat.po
896
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
943
po/az/neochat.po
943
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
894
po/ca/neochat.po
894
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
945
po/cs/neochat.po
945
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
879
po/da/neochat.po
879
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
948
po/de/neochat.po
948
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
945
po/el/neochat.po
945
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
956
po/eo/neochat.po
956
po/eo/neochat.po
File diff suppressed because it is too large
Load Diff
897
po/es/neochat.po
897
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
895
po/eu/neochat.po
895
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
945
po/fi/neochat.po
945
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
895
po/fr/neochat.po
895
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
887
po/gl/neochat.po
887
po/gl/neochat.po
File diff suppressed because it is too large
Load Diff
928
po/hu/neochat.po
928
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
891
po/ia/neochat.po
891
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
951
po/id/neochat.po
951
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
921
po/ie/neochat.po
921
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
966
po/it/neochat.po
966
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
788
po/ja/neochat.po
788
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
895
po/ka/neochat.po
895
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
941
po/ko/neochat.po
941
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
788
po/lt/neochat.po
788
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
933
po/lv/neochat.po
933
po/lv/neochat.po
File diff suppressed because it is too large
Load Diff
897
po/nl/neochat.po
897
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
919
po/nn/neochat.po
919
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
948
po/pa/neochat.po
948
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
893
po/pl/neochat.po
893
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
949
po/pt/neochat.po
949
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
937
po/ru/neochat.po
937
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
948
po/sk/neochat.po
948
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
890
po/sl/neochat.po
890
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
893
po/sv/neochat.po
893
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
1008
po/ta/neochat.po
1008
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
892
po/tr/neochat.po
892
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
897
po/uk/neochat.po
897
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -242,11 +242,9 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
|
||||
qml/EditMenu.qml
|
||||
qml/MessageDelegateContextMenu.qml
|
||||
qml/FileDelegateContextMenu.qml
|
||||
qml/FileDelegateContextMenuMobile.qml
|
||||
qml/MessageSourceSheet.qml
|
||||
qml/ReportSheet.qml
|
||||
qml/ConfirmEncryptionDialog.qml
|
||||
qml/RemoveSheet.qml
|
||||
qml/BanSheet.qml
|
||||
qml/RoomSearchPage.qml
|
||||
qml/LocationChooser.qml
|
||||
qml/TimelineView.qml
|
||||
@@ -291,6 +289,7 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
|
||||
qml/AskDirectChatConfirmation.qml
|
||||
qml/HoverLinkIndicator.qml
|
||||
qml/AvatarNotification.qml
|
||||
qml/ReasonDialog.qml
|
||||
DEPENDENCIES
|
||||
QtCore
|
||||
QtQuick
|
||||
@@ -308,7 +307,7 @@ add_subdirectory(devtools)
|
||||
add_subdirectory(login)
|
||||
add_subdirectory(chatbar)
|
||||
|
||||
if(UNIX)
|
||||
if(NOT ANDROID AND NOT WIN32)
|
||||
qt_target_qml_sources(neochat QML_FILES qml/ShareAction.qml)
|
||||
else()
|
||||
set_source_files_properties(qml/ShareActionStub.qml PROPERTIES
|
||||
|
||||
@@ -139,8 +139,8 @@ void ChatBarCache::setThreadId(const QString &threadId)
|
||||
if (m_threadId == threadId) {
|
||||
return;
|
||||
}
|
||||
m_threadId = threadId;
|
||||
Q_EMIT threadIdChanged();
|
||||
const auto oldThreadId = std::exchange(m_threadId, threadId);
|
||||
Q_EMIT threadIdChanged(oldThreadId, m_threadId);
|
||||
}
|
||||
|
||||
QString ChatBarCache::attachmentPath() const
|
||||
@@ -163,10 +163,10 @@ void ChatBarCache::setAttachmentPath(const QString &attachmentPath)
|
||||
void ChatBarCache::clearRelations()
|
||||
{
|
||||
const auto oldEventId = std::exchange(m_relationId, QString());
|
||||
m_threadId = QString();
|
||||
const auto oldThreadId = std::exchange(m_threadId, QString());
|
||||
m_attachmentPath = QString();
|
||||
Q_EMIT relationIdChanged(oldEventId, m_relationId);
|
||||
Q_EMIT threadIdChanged();
|
||||
Q_EMIT threadIdChanged(oldThreadId, m_threadId);
|
||||
Q_EMIT attachmentPathChanged();
|
||||
}
|
||||
|
||||
|
||||
@@ -195,7 +195,7 @@ public:
|
||||
Q_SIGNALS:
|
||||
void textChanged();
|
||||
void relationIdChanged(const QString &oldEventId, const QString &newEventId);
|
||||
void threadIdChanged();
|
||||
void threadIdChanged(const QString &oldThreadId, const QString &newThreadId);
|
||||
void attachmentPathChanged();
|
||||
|
||||
private:
|
||||
|
||||
@@ -26,23 +26,10 @@ void ColorSchemer::apply(int idx)
|
||||
c->activateScheme(c->model()->index(idx, 0));
|
||||
}
|
||||
|
||||
void ColorSchemer::apply(const QString &name)
|
||||
int ColorSchemer::indexForCurrentScheme()
|
||||
{
|
||||
c->activateScheme(c->indexForScheme(name));
|
||||
}
|
||||
|
||||
int ColorSchemer::indexForScheme(const QString &name) const
|
||||
{
|
||||
auto index = c->indexForScheme(name).row();
|
||||
if (index == -1) {
|
||||
index = 0;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
QString ColorSchemer::nameForIndex(int index) const
|
||||
{
|
||||
return c->model()->data(c->model()->index(index, 0), Qt::DisplayRole).toString();
|
||||
return -1;
|
||||
// return c->indexForSchemeId(c->activeSchemeId()).row();
|
||||
}
|
||||
|
||||
#include "moc_colorschemer.cpp"
|
||||
|
||||
@@ -44,21 +44,11 @@ public:
|
||||
Q_INVOKABLE void apply(int idx);
|
||||
|
||||
/**
|
||||
* @brief Activates the KColorScheme with the given name.
|
||||
* @brief Get the row for the current color scheme.
|
||||
*
|
||||
* @sa KColorScheme
|
||||
*/
|
||||
Q_INVOKABLE void apply(const QString &name);
|
||||
|
||||
/**
|
||||
* @brief Returns the index for the scheme with the given name.
|
||||
*/
|
||||
Q_INVOKABLE int indexForScheme(const QString &name) const;
|
||||
|
||||
/**
|
||||
* @brief Returns the name for the scheme with the given index.
|
||||
*/
|
||||
Q_INVOKABLE QString nameForIndex(int index) const;
|
||||
Q_INVOKABLE int indexForCurrentScheme();
|
||||
|
||||
private:
|
||||
KColorSchemeManager *c;
|
||||
|
||||
@@ -439,4 +439,14 @@ void Controller::revertToDefaultConfig()
|
||||
config->save();
|
||||
}
|
||||
|
||||
bool Controller::isImageShown(const QString &eventId)
|
||||
{
|
||||
return m_shownImages.contains(eventId);
|
||||
}
|
||||
|
||||
void Controller::markImageShown(const QString &eventId)
|
||||
{
|
||||
m_shownImages.append(eventId);
|
||||
}
|
||||
|
||||
#include "moc_controller.cpp"
|
||||
|
||||
@@ -116,6 +116,9 @@ public:
|
||||
*/
|
||||
Q_INVOKABLE void revertToDefaultConfig();
|
||||
|
||||
Q_INVOKABLE bool isImageShown(const QString &eventId);
|
||||
Q_INVOKABLE void markImageShown(const QString &eventId);
|
||||
|
||||
private:
|
||||
explicit Controller(QObject *parent = nullptr);
|
||||
|
||||
@@ -128,6 +131,7 @@ private:
|
||||
QStringList m_accountsLoading;
|
||||
QMap<QString, QPointer<NeoChatConnection>> m_connectionsLoading;
|
||||
QString m_endpoint;
|
||||
QStringList m_shownImages;
|
||||
|
||||
private Q_SLOTS:
|
||||
void invokeLogin();
|
||||
|
||||
@@ -50,7 +50,7 @@ public:
|
||||
Reply, /**< A component to show a replied-to message. */
|
||||
LinkPreview, /**< A preview of a URL in the message. */
|
||||
LinkPreviewLoad, /**< A loading dialog for a link preview. */
|
||||
Edit, /**< A text edit for editing a message. */
|
||||
ChatBar, /**< A text edit for editing a message. */
|
||||
Verification, /**< A user verification session start message. */
|
||||
Loading, /**< The component is loading. */
|
||||
Other, /**< Anything that cannot be classified as another type. */
|
||||
|
||||
@@ -154,7 +154,8 @@ bool EventHandler::isHidden(const NeoChatRoom *room, const Quotient::RoomEvent *
|
||||
if (auto roomMemberEvent = eventCast<const RoomMemberEvent>(event)) {
|
||||
if ((roomMemberEvent->isJoin() || roomMemberEvent->isLeave()) && !NeoChatConfig::self()->showLeaveJoinEvent()) {
|
||||
return true;
|
||||
} else if (roomMemberEvent->isRename() && !roomMemberEvent->isJoin() && !roomMemberEvent->isLeave() && !NeoChatConfig::self()->showRename()) {
|
||||
} else if (roomMemberEvent->isRename() && roomMemberEvent->prevContent() && roomMemberEvent->prevContent()->membership == roomMemberEvent->membership()
|
||||
&& !NeoChatConfig::self()->showRename()) {
|
||||
return true;
|
||||
} else if (roomMemberEvent->isAvatarUpdate() && !roomMemberEvent->isJoin() && !roomMemberEvent->isLeave()
|
||||
&& !NeoChatConfig::self()->showAvatarUpdate()) {
|
||||
@@ -366,9 +367,13 @@ QString EventHandler::getBody(const NeoChatRoom *room, const Quotient::RoomEvent
|
||||
if (e.prevContent() && e.prevContent()->membership == Membership::Ban) {
|
||||
return (e.senderId() != e.userId()) ? i18n("unbanned %1", subjectName) : i18n("self-unbanned");
|
||||
}
|
||||
return (e.senderId() != e.userId())
|
||||
? i18n("has put %1 out of the room: %2", subjectName, e.contentJson()["reason"_ls].toString().toHtmlEscaped())
|
||||
: i18n("left the room");
|
||||
if (e.senderId() == e.userId()) {
|
||||
return i18n("left the room");
|
||||
}
|
||||
if (const auto &reason = e.contentJson()["reason"_ls].toString().toHtmlEscaped(); !reason.isEmpty()) {
|
||||
return i18n("has put %1 out of the room: %2", subjectName, reason);
|
||||
}
|
||||
return i18n("has put %1 out of the room", subjectName);
|
||||
case Membership::Ban:
|
||||
if (e.senderId() != e.userId()) {
|
||||
if (e.reason().isEmpty()) {
|
||||
|
||||
@@ -40,7 +40,7 @@ float LocationHelper::zoomToFit(const QRectF &r, float mapWidth, float mapHeight
|
||||
const auto zy = std::log2((mapHeight / (p2.y() - p1.y())));
|
||||
const auto z = std::min(zx, zy);
|
||||
|
||||
return std::clamp(z, 5.0, 18.0);
|
||||
return z;
|
||||
}
|
||||
|
||||
#include "moc_locationhelper.cpp"
|
||||
|
||||
@@ -11,7 +11,7 @@ import org.kde.kirigamiaddons.formcard as FormCard
|
||||
import org.kde.neochat
|
||||
import org.kde.neochat.settings
|
||||
|
||||
FormCard.FormCardPage {
|
||||
Kirigami.Page {
|
||||
id: root
|
||||
|
||||
property bool showExisting: false
|
||||
@@ -23,202 +23,237 @@ FormCard.FormCardPage {
|
||||
signal connectionChosen
|
||||
|
||||
title: i18n("Welcome")
|
||||
globalToolBarStyle: Kirigami.ApplicationHeaderStyle.None
|
||||
|
||||
header: QQC2.Control {
|
||||
contentItem: Kirigami.InlineMessage {
|
||||
id: headerMessage
|
||||
type: Kirigami.MessageType.Error
|
||||
showCloseButton: true
|
||||
visible: false
|
||||
}
|
||||
}
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
|
||||
Kirigami.Icon {
|
||||
source: "org.kde.neochat"
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
implicitWidth: Math.round(Kirigami.Units.iconSizes.huge * 1.5)
|
||||
implicitHeight: Math.round(Kirigami.Units.iconSizes.huge * 1.5)
|
||||
}
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
|
||||
Kirigami.Heading {
|
||||
id: welcomeMessage
|
||||
|
||||
text: i18n("Welcome to NeoChat")
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
}
|
||||
|
||||
FormCard.FormHeader {
|
||||
id: existingAccountsHeader
|
||||
title: i18nc("@title", "Continue with an existing account")
|
||||
visible: (loadedAccounts.count > 0 || loadingAccounts.count > 0) && root._showExisting
|
||||
}
|
||||
|
||||
FormCard.FormCard {
|
||||
visible: existingAccountsHeader.visible
|
||||
Repeater {
|
||||
id: loadedAccounts
|
||||
model: AccountRegistry
|
||||
delegate: FormCard.FormButtonDelegate {
|
||||
text: model.userId
|
||||
onClicked: {
|
||||
Controller.activeConnection = model.connection;
|
||||
root.connectionChosen();
|
||||
}
|
||||
Kirigami.Separator {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
Repeater {
|
||||
id: loadingAccounts
|
||||
model: Controller.accountsLoading
|
||||
delegate: FormCard.AbstractFormDelegate {
|
||||
id: loadingDelegate
|
||||
|
||||
topPadding: Kirigami.Units.smallSpacing
|
||||
bottomPadding: Kirigami.Units.smallSpacing
|
||||
Kirigami.InlineMessage {
|
||||
id: headerMessage
|
||||
type: Kirigami.MessageType.Error
|
||||
showCloseButton: true
|
||||
visible: false
|
||||
|
||||
background: null
|
||||
contentItem: RowLayout {
|
||||
spacing: 0
|
||||
|
||||
QQC2.Label {
|
||||
Layout.fillWidth: true
|
||||
text: i18nc("As in 'this account is still loading'", "%1 (loading)", modelData)
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.Wrap
|
||||
maximumLineCount: 2
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
Accessible.ignored: true // base class sets this text on root already
|
||||
}
|
||||
|
||||
QQC2.ToolButton {
|
||||
text: i18nc("@action:button", "Log out of this account")
|
||||
icon.name: "edit-delete-remove"
|
||||
onClicked: Controller.removeConnection(modelData)
|
||||
display: QQC2.Button.IconOnly
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
enabled: true
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
|
||||
}
|
||||
|
||||
FormCard.FormArrow {
|
||||
Layout.leftMargin: Kirigami.Units.smallSpacing
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
direction: Qt.RightArrow
|
||||
visible: root.background.visible
|
||||
}
|
||||
}
|
||||
}
|
||||
onCountChanged: {
|
||||
if (loadingAccounts.count === 0 && loadedAccounts.count === 1 && showExisting) {
|
||||
Controller.activeConnection = AccountRegistry.data(AccountRegistry.index(0, 0), 257);
|
||||
root.connectionChosen();
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Kirigami.Units.largeSpacing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormHeader {
|
||||
title: i18nc("@title", "Log in or Create a New Account")
|
||||
}
|
||||
contentItem: Item {
|
||||
ColumnLayout {
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
FormCard.FormCard {
|
||||
Loader {
|
||||
id: module
|
||||
Layout.fillWidth: true
|
||||
sourceComponent: Qt.createComponent('org.kde.neochat.login', root.initialStep)
|
||||
spacing: 0
|
||||
|
||||
Connections {
|
||||
id: stepConnections
|
||||
target: currentStep
|
||||
Kirigami.Icon {
|
||||
source: "org.kde.neochat"
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
implicitWidth: Math.round(Kirigami.Units.iconSizes.huge * 1.5)
|
||||
implicitHeight: Math.round(Kirigami.Units.iconSizes.huge * 1.5)
|
||||
}
|
||||
|
||||
function onProcessed(nextStep: string): void {
|
||||
module.source = nextStep + ".qml";
|
||||
root.currentStepString = nextStep;
|
||||
headerMessage.text = "";
|
||||
headerMessage.visible = false;
|
||||
if (!module.item.noControls) {
|
||||
module.item.forceActiveFocus();
|
||||
} else {
|
||||
continueButton.forceActiveFocus();
|
||||
Kirigami.Heading {
|
||||
id: welcomeMessage
|
||||
|
||||
text: i18n("NeoChat")
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
}
|
||||
|
||||
FormCard.FormHeader {
|
||||
id: existingAccountsHeader
|
||||
title: i18nc("@title", "Continue with an existing account")
|
||||
visible: (loadedAccounts.count > 0 || loadingAccounts.count > 0) && root._showExisting
|
||||
maximumWidth: Kirigami.Units.gridUnit * 20
|
||||
}
|
||||
|
||||
FormCard.FormCard {
|
||||
visible: existingAccountsHeader.visible
|
||||
maximumWidth: Kirigami.Units.gridUnit * 20
|
||||
Repeater {
|
||||
id: loadedAccounts
|
||||
model: AccountRegistry
|
||||
delegate: FormCard.FormButtonDelegate {
|
||||
text: model.userId
|
||||
onClicked: {
|
||||
Controller.activeConnection = model.connection;
|
||||
root.connectionChosen();
|
||||
}
|
||||
}
|
||||
}
|
||||
Repeater {
|
||||
id: loadingAccounts
|
||||
model: Controller.accountsLoading
|
||||
delegate: FormCard.AbstractFormDelegate {
|
||||
id: loadingDelegate
|
||||
|
||||
function onShowMessage(message: string): void {
|
||||
headerMessage.text = message;
|
||||
headerMessage.visible = true;
|
||||
headerMessage.type = Kirigami.MessageType.Information;
|
||||
}
|
||||
topPadding: Kirigami.Units.smallSpacing
|
||||
bottomPadding: Kirigami.Units.smallSpacing
|
||||
|
||||
function onClearError(): void {
|
||||
headerMessage.text = "";
|
||||
headerMessage.visible = false;
|
||||
}
|
||||
background: null
|
||||
contentItem: RowLayout {
|
||||
spacing: 0
|
||||
|
||||
function onCloseDialog(): void {
|
||||
root.closeDialog();
|
||||
QQC2.Label {
|
||||
Layout.fillWidth: true
|
||||
text: i18nc("As in 'this account is still loading'", "%1 (loading)", modelData)
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.Wrap
|
||||
maximumLineCount: 2
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
Accessible.ignored: true // base class sets this text on root already
|
||||
}
|
||||
|
||||
QQC2.ToolButton {
|
||||
text: i18nc("@action:button", "Log out of this account")
|
||||
icon.name: "im-kick-user"
|
||||
onClicked: Controller.removeConnection(modelData)
|
||||
display: QQC2.Button.IconOnly
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
enabled: true
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
|
||||
}
|
||||
|
||||
FormCard.FormArrow {
|
||||
Layout.leftMargin: Kirigami.Units.smallSpacing
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
direction: Qt.RightArrow
|
||||
visible: root.background.visible
|
||||
}
|
||||
}
|
||||
}
|
||||
onCountChanged: {
|
||||
if (loadingAccounts.count === 0 && loadedAccounts.count === 1 && showExisting) {
|
||||
Controller.activeConnection = AccountRegistry.data(AccountRegistry.index(0, 0), 257);
|
||||
root.connectionChosen();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Registration
|
||||
function onNextStepChanged() {
|
||||
if (Registration.nextStep === "m.login.recaptcha") {
|
||||
stepConnections.onProcessed("Captcha");
|
||||
FormCard.FormHeader {
|
||||
title: i18nc("@title", "Log in or Create a New Account")
|
||||
maximumWidth: Kirigami.Units.gridUnit * 20
|
||||
}
|
||||
|
||||
FormCard.FormCard {
|
||||
maximumWidth: Kirigami.Units.gridUnit * 20
|
||||
Loader {
|
||||
id: module
|
||||
Layout.fillWidth: true
|
||||
sourceComponent: Qt.createComponent('org.kde.neochat.login', root.initialStep)
|
||||
|
||||
Connections {
|
||||
id: stepConnections
|
||||
target: currentStep
|
||||
|
||||
function onProcessed(nextStep: string): void {
|
||||
module.source = nextStep + ".qml";
|
||||
root.currentStepString = nextStep;
|
||||
headerMessage.text = "";
|
||||
headerMessage.visible = false;
|
||||
if (!module.item.noControls) {
|
||||
module.item.forceActiveFocus();
|
||||
} else {
|
||||
continueButton.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
function onShowMessage(message: string): void {
|
||||
headerMessage.text = message;
|
||||
headerMessage.visible = true;
|
||||
headerMessage.type = Kirigami.MessageType.Information;
|
||||
}
|
||||
|
||||
function onClearError(): void {
|
||||
headerMessage.text = "";
|
||||
headerMessage.visible = false;
|
||||
}
|
||||
|
||||
function onCloseDialog(): void {
|
||||
root.closeDialog();
|
||||
}
|
||||
}
|
||||
if (Registration.nextStep === "m.login.terms") {
|
||||
stepConnections.onProcessed("Terms");
|
||||
|
||||
Connections {
|
||||
target: Registration
|
||||
|
||||
function onNextStepChanged() {
|
||||
if (Registration.nextStep === "m.login.recaptcha") {
|
||||
stepConnections.onProcessed("Captcha");
|
||||
}
|
||||
if (Registration.nextStep === "m.login.terms") {
|
||||
stepConnections.onProcessed("Terms");
|
||||
}
|
||||
if (Registration.nextStep === "m.login.email.identity") {
|
||||
stepConnections.onProcessed("Email");
|
||||
}
|
||||
if (Registration.nextStep === "loading") {
|
||||
stepConnections.onProcessed("Loading");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Registration.nextStep === "m.login.email.identity") {
|
||||
stepConnections.onProcessed("Email");
|
||||
}
|
||||
if (Registration.nextStep === "loading") {
|
||||
stepConnections.onProcessed("Loading");
|
||||
Connections {
|
||||
target: LoginHelper
|
||||
|
||||
function onErrorOccured(message) {
|
||||
headerMessage.text = message;
|
||||
headerMessage.visible = message.length > 0;
|
||||
headerMessage.type = Kirigami.MessageType.Error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Connections {
|
||||
target: LoginHelper
|
||||
function onErrorOccured(message) {
|
||||
headerMessage.text = message;
|
||||
headerMessage.visible = message.length > 0;
|
||||
headerMessage.type = Kirigami.MessageType.Error;
|
||||
|
||||
FormCard.FormDelegateSeparator {
|
||||
below: continueButton
|
||||
visible: root.currentStep.nextAction
|
||||
}
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
id: continueButton
|
||||
text: root.currentStep.nextAction && root.currentStep.nextAction.text ? root.currentStep.nextAction.text : i18nc("@action:button", "Continue")
|
||||
visible: root.currentStep.nextAction
|
||||
onClicked: root.currentStep.nextAction.trigger()
|
||||
icon.name: "arrow-right"
|
||||
enabled: root.currentStep.nextAction ? root.currentStep.nextAction.enabled : false
|
||||
}
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
text: i18nc("@action:button", "Go back")
|
||||
visible: root.currentStep.previousAction
|
||||
onClicked: root.currentStep.previousAction.trigger()
|
||||
icon.name: "arrow-left"
|
||||
enabled: root.currentStep.previousAction ? root.currentStep.previousAction.enabled : false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormDelegateSeparator {
|
||||
below: continueButton
|
||||
}
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
id: continueButton
|
||||
text: root.currentStep.nextAction && root.currentStep.nextAction.text ? root.currentStep.nextAction.text : i18nc("@action:button", "Continue")
|
||||
visible: root.currentStep.nextAction
|
||||
onClicked: root.currentStep.nextAction.trigger()
|
||||
icon.name: "arrow-right"
|
||||
enabled: root.currentStep.nextAction ? root.currentStep.nextAction.enabled : false
|
||||
}
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
text: i18nc("@action:button", "Go back")
|
||||
visible: root.currentStep.previousAction
|
||||
onClicked: root.currentStep.previousAction.trigger()
|
||||
icon.name: "arrow-left"
|
||||
enabled: root.currentStep.previousAction ? root.currentStep.previousAction.enabled : false
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormCard {
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
FormCard.FormButtonDelegate {
|
||||
text: i18nc("@action:button", "Open proxy settings")
|
||||
icon.name: "settings-configure"
|
||||
onClicked: pageStack.pushDialogLayer(Qt.createComponent("org.kde.neochat.settings", "NetworkProxyPage"), {}, {
|
||||
title: i18nc("@title:window", "Proxy Settings")
|
||||
});
|
||||
FormCard.FormCard {
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing * 2
|
||||
maximumWidth: Kirigami.Units.gridUnit * 20
|
||||
FormCard.FormButtonDelegate {
|
||||
text: i18nc("@action:button", "Settings")
|
||||
icon.name: "settings-configure"
|
||||
onClicked: NeoChatSettingsView.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -185,9 +185,6 @@ int main(int argc, char *argv[])
|
||||
#endif
|
||||
|
||||
ColorSchemer colorScheme;
|
||||
if (!NeoChatConfig::self()->colorScheme().isEmpty()) {
|
||||
colorScheme.apply(NeoChatConfig::self()->colorScheme());
|
||||
}
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(i18n("Client for the matrix communication protocol"));
|
||||
@@ -307,7 +304,6 @@ int main(int argc, char *argv[])
|
||||
QWindow *window = windowFromEngine(&engine);
|
||||
|
||||
WindowController::instance().setWindow(window);
|
||||
WindowController::instance().restoreGeometry();
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
@@ -211,7 +211,7 @@ QList<ActionsModel::Action> actions{
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Positive, i18n("You are already in this room."));
|
||||
return QString();
|
||||
}
|
||||
if (room->members().contains(room->member(text))) {
|
||||
if (room->joinedMemberIds().contains(text)) {
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Info, i18nc("<user> is already in this room.", "%1 is already in this room.", text));
|
||||
return QString();
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ void MessageContentModel::initializeModel()
|
||||
});
|
||||
|
||||
if (m_event == nullptr) {
|
||||
m_room->getEvent(m_eventId);
|
||||
intiializeEvent(m_room->getEvent(m_eventId));
|
||||
if (m_event == nullptr) {
|
||||
m_room->downloadEventFromServer(m_eventId);
|
||||
}
|
||||
@@ -140,6 +140,13 @@ void MessageContentModel::initializeModel()
|
||||
endResetModel();
|
||||
}
|
||||
});
|
||||
connect(m_room->threadCache(), &ChatBarCache::threadIdChanged, this, [this](const QString &oldThreadId, const QString &newThreadId) {
|
||||
if (m_event != nullptr && (oldThreadId == m_eventId || newThreadId == m_eventId)) {
|
||||
beginResetModel();
|
||||
resetContent(false, newThreadId == m_eventId);
|
||||
endResetModel();
|
||||
}
|
||||
});
|
||||
connect(m_room, &NeoChatRoom::urlPreviewEnabledChanged, this, [this]() {
|
||||
resetContent();
|
||||
});
|
||||
@@ -182,14 +189,13 @@ void MessageContentModel::intiializeEvent(const QString &eventId)
|
||||
|
||||
void MessageContentModel::intiializeEvent(const Quotient::RoomEvent *event)
|
||||
{
|
||||
if (event == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_event = loadEvent<RoomEvent>(event->fullJson());
|
||||
// a pending event may not previously have had an event ID so update.
|
||||
if (m_eventId.isEmpty()) {
|
||||
m_eventId = m_event->id();
|
||||
if (m_eventId.isEmpty()) {
|
||||
m_eventId = m_event->transactionId();
|
||||
}
|
||||
}
|
||||
m_eventId = EventHandler::id(m_event.get());
|
||||
|
||||
auto senderId = m_event->senderId();
|
||||
// A pending event might not have a sender ID set yet but in that case it must
|
||||
@@ -215,7 +221,7 @@ void MessageContentModel::setShowAuthor(bool showAuthor)
|
||||
}
|
||||
m_showAuthor = showAuthor;
|
||||
|
||||
if (m_event != nullptr && m_room->connection()->ignoredUsers().contains(m_event->senderId())) {
|
||||
if (m_event != nullptr && m_room->connection()->isIgnored(m_event->senderId())) {
|
||||
if (showAuthor) {
|
||||
beginInsertRows({}, 0, 0);
|
||||
m_components.prepend(MessageComponent{MessageComponentType::Author, QString(), {}});
|
||||
@@ -245,7 +251,7 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
|
||||
const auto component = m_components[index.row()];
|
||||
|
||||
if (role == DisplayRole) {
|
||||
if (m_notFound || (m_event && m_room->connection()->ignoredUsers().contains(m_event->senderId()))) {
|
||||
if (m_notFound || (m_event && m_room->connection()->isIgnored(m_event->senderId()))) {
|
||||
Kirigami::Platform::PlatformTheme *theme =
|
||||
static_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true));
|
||||
|
||||
@@ -341,6 +347,12 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
|
||||
return QVariant::fromValue<LinkPreviewer *>(emptyLinkPreview);
|
||||
}
|
||||
}
|
||||
if (role == ChatBarCacheRole) {
|
||||
if (m_room->threadCache()->threadId() == m_eventId) {
|
||||
return QVariant::fromValue<ChatBarCache *>(m_room->threadCache());
|
||||
}
|
||||
return QVariant::fromValue<ChatBarCache *>(m_room->editCache());
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
@@ -372,6 +384,7 @@ QHash<int, QByteArray> MessageContentModel::roleNames() const
|
||||
roles[ReplyAuthorRole] = "replyAuthor";
|
||||
roles[ReplyContentModelRole] = "replyContentModel";
|
||||
roles[LinkPreviewerRole] = "linkPreviewer";
|
||||
roles[ChatBarCacheRole] = "chatBarCache";
|
||||
return roles;
|
||||
}
|
||||
|
||||
@@ -380,7 +393,7 @@ void MessageContentModel::resetModel()
|
||||
beginResetModel();
|
||||
m_components.clear();
|
||||
|
||||
if ((m_event && m_room->connection()->ignoredUsers().contains(m_event->senderId())) || m_notFound) {
|
||||
if ((m_event && m_room->connection()->isIgnored(m_event->senderId())) || m_notFound) {
|
||||
m_components += MessageComponent{MessageComponentType::Text, QString(), {}};
|
||||
endResetModel();
|
||||
return;
|
||||
@@ -400,7 +413,7 @@ void MessageContentModel::resetModel()
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void MessageContentModel::resetContent(bool isEditing)
|
||||
void MessageContentModel::resetContent(bool isEditing, bool isThreading)
|
||||
{
|
||||
Q_ASSERT(m_event != nullptr);
|
||||
|
||||
@@ -409,7 +422,7 @@ void MessageContentModel::resetContent(bool isEditing)
|
||||
m_components.remove(startRow, rowCount() - startRow);
|
||||
endRemoveRows();
|
||||
|
||||
const auto newComponents = messageContentComponents(isEditing);
|
||||
const auto newComponents = messageContentComponents(isEditing, isThreading);
|
||||
if (newComponents.size() == 0) {
|
||||
return;
|
||||
}
|
||||
@@ -418,7 +431,7 @@ void MessageContentModel::resetContent(bool isEditing)
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEditing)
|
||||
QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEditing, bool isThreading)
|
||||
{
|
||||
QList<MessageComponent> newComponents;
|
||||
|
||||
@@ -438,7 +451,7 @@ QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEdi
|
||||
}
|
||||
|
||||
if (isEditing) {
|
||||
newComponents += MessageComponent{MessageComponentType::Edit, QString(), {}};
|
||||
newComponents += MessageComponent{MessageComponentType::ChatBar, QString(), {}};
|
||||
} else {
|
||||
newComponents.append(componentsForType(MessageComponentType::typeForEvent(*m_event.get())));
|
||||
}
|
||||
@@ -447,6 +460,11 @@ QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEdi
|
||||
newComponents = addLinkPreviews(newComponents);
|
||||
}
|
||||
|
||||
// If the event is already threaded the ThreadModel will handle displaying a chat bar.
|
||||
if (isThreading && !EventHandler::isThreaded(m_event.get())) {
|
||||
newComponents += MessageComponent{MessageComponentType::ChatBar, QString(), {}};
|
||||
}
|
||||
|
||||
return newComponents;
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,7 @@ public:
|
||||
ReplyContentModelRole, /**< The MessageContentModel for the reply event. */
|
||||
|
||||
LinkPreviewerRole, /**< The link preview details. */
|
||||
ChatBarCacheRole, /**< The ChatBarCache to use. */
|
||||
};
|
||||
Q_ENUM(Roles)
|
||||
|
||||
@@ -133,8 +134,8 @@ private:
|
||||
|
||||
QList<MessageComponent> m_components;
|
||||
void resetModel();
|
||||
void resetContent(bool isEditing = false);
|
||||
QList<MessageComponent> messageContentComponents(bool isEditing = false);
|
||||
void resetContent(bool isEditing = false, bool isThreading = false);
|
||||
QList<MessageComponent> messageContentComponents(bool isEditing = false, bool isThreading = false);
|
||||
|
||||
QPointer<MessageContentModel> m_replyModel;
|
||||
void updateReplyModel();
|
||||
|
||||
@@ -2,20 +2,40 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include "permissionsmodel.h"
|
||||
#include "powerlevel.h"
|
||||
|
||||
#include <Quotient/events/roompowerlevelsevent.h>
|
||||
|
||||
#include <KLazyLocalizedString>
|
||||
|
||||
#include "powerlevel.h"
|
||||
|
||||
using namespace Qt::Literals::StringLiterals;
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr auto UsersDefaultKey = "users_default"_L1;
|
||||
constexpr auto StateDefaultKey = "state_default"_L1;
|
||||
constexpr auto EventsDefaultKey = "events_default"_L1;
|
||||
|
||||
constexpr auto InviteKey = "invite"_L1;
|
||||
constexpr auto KickKey = "kick"_L1;
|
||||
constexpr auto BanKey = "ban"_L1;
|
||||
constexpr auto RedactKey = "redact"_L1;
|
||||
|
||||
static const QStringList defaultPermissions = {
|
||||
QStringLiteral("users_default"),
|
||||
QStringLiteral("state_default"),
|
||||
QStringLiteral("events_default"),
|
||||
QStringLiteral("invite"),
|
||||
QStringLiteral("kick"),
|
||||
QStringLiteral("ban"),
|
||||
QStringLiteral("redact"),
|
||||
UsersDefaultKey,
|
||||
StateDefaultKey,
|
||||
EventsDefaultKey,
|
||||
};
|
||||
|
||||
static const QStringList basicPermissions = {
|
||||
InviteKey,
|
||||
KickKey,
|
||||
BanKey,
|
||||
RedactKey,
|
||||
};
|
||||
|
||||
static const QStringList knownPermissions = {
|
||||
QStringLiteral("m.reaction"),
|
||||
QStringLiteral("m.room.redaction"),
|
||||
QStringLiteral("m.room.power_levels"),
|
||||
@@ -33,14 +53,14 @@ static const QStringList defaultPermissions = {
|
||||
};
|
||||
|
||||
// Alternate name text for default permissions.
|
||||
static const QHash<QString, KLazyLocalizedString> defaultPermissionNames = {
|
||||
{QStringLiteral("users_default"), kli18nc("Room permission type", "Default user power level")},
|
||||
{QStringLiteral("state_default"), kli18nc("Room permission type", "Default power level to set the room state")},
|
||||
{QStringLiteral("events_default"), kli18nc("Room permission type", "Default power level to send messages")},
|
||||
{QStringLiteral("invite"), kli18nc("Room permission type", "Invite users")},
|
||||
{QStringLiteral("kick"), kli18nc("Room permission type", "Kick users")},
|
||||
{QStringLiteral("ban"), kli18nc("Room permission type", "Ban users")},
|
||||
{QStringLiteral("redact"), kli18nc("Room permission type", "Remove messages sent by other users")},
|
||||
static const QHash<QString, KLazyLocalizedString> permissionNames = {
|
||||
{UsersDefaultKey, kli18nc("Room permission type", "Default user power level")},
|
||||
{StateDefaultKey, kli18nc("Room permission type", "Default power level to set the room state")},
|
||||
{EventsDefaultKey, kli18nc("Room permission type", "Default power level to send messages")},
|
||||
{InviteKey, kli18nc("Room permission type", "Invite users")},
|
||||
{KickKey, kli18nc("Room permission type", "Kick users")},
|
||||
{BanKey, kli18nc("Room permission type", "Ban users")},
|
||||
{RedactKey, kli18nc("Room permission type", "Remove messages sent by other users")},
|
||||
{QStringLiteral("m.reaction"), kli18nc("Room permission type", "Send reactions")},
|
||||
{QStringLiteral("m.room.redaction"), kli18nc("Room permission type", "Remove their own messages")},
|
||||
{QStringLiteral("m.room.power_levels"), kli18nc("Room permission type", "Change user permissions")},
|
||||
@@ -58,10 +78,10 @@ static const QHash<QString, KLazyLocalizedString> defaultPermissionNames = {
|
||||
};
|
||||
|
||||
// Subtitles for the default values.
|
||||
static const QHash<QString, KLazyLocalizedString> defaultSubtitles = {
|
||||
{QStringLiteral("users_default"), kli18nc("Room permission type", "This is the power level for all new users when joining the room")},
|
||||
{QStringLiteral("state_default"), kli18nc("Room permission type", "This is used for all state events that do not have their own entry here")},
|
||||
{QStringLiteral("events_default"), kli18nc("Room permission type", "This is used for all message events that do not have their own entry here")},
|
||||
static const QHash<QString, KLazyLocalizedString> permissionSubtitles = {
|
||||
{UsersDefaultKey, kli18nc("Room permission type", "This is the power level for all new users when joining the room")},
|
||||
{StateDefaultKey, kli18nc("Room permission type", "This is used for all state events that do not have their own entry here")},
|
||||
{EventsDefaultKey, kli18nc("Room permission type", "This is used for all message events that do not have their own entry here")},
|
||||
};
|
||||
|
||||
// Permissions that should use the event default.
|
||||
@@ -70,6 +90,7 @@ static const QStringList eventPermissions = {
|
||||
QStringLiteral("m.reaction"),
|
||||
QStringLiteral("m.room.redaction"),
|
||||
};
|
||||
};
|
||||
|
||||
PermissionsModel::PermissionsModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
@@ -109,6 +130,8 @@ void PermissionsModel::initializeModel()
|
||||
}
|
||||
|
||||
m_permissions.append(defaultPermissions);
|
||||
m_permissions.append(basicPermissions);
|
||||
m_permissions.append(knownPermissions);
|
||||
|
||||
for (const auto &event : currentPowerLevelEvent->events().keys()) {
|
||||
if (!m_permissions.contains(event)) {
|
||||
@@ -131,17 +154,17 @@ QVariant PermissionsModel::data(const QModelIndex &index, int role) const
|
||||
|
||||
const auto permission = m_permissions.value(index.row());
|
||||
if (role == NameRole) {
|
||||
if (defaultPermissionNames.keys().contains(permission)) {
|
||||
return defaultPermissionNames.value(permission).toString();
|
||||
if (permissionNames.keys().contains(permission)) {
|
||||
return permissionNames.value(permission).toString();
|
||||
}
|
||||
return permission;
|
||||
}
|
||||
if (role == SubtitleRole) {
|
||||
if (permission.startsWith(QLatin1String("m.")) && defaultPermissionNames.keys().contains(permission)) {
|
||||
if (knownPermissions.contains(permission) && permissionNames.keys().contains(permission)) {
|
||||
return permission;
|
||||
}
|
||||
if (defaultSubtitles.contains(permission)) {
|
||||
return defaultSubtitles.value(permission).toString();
|
||||
if (permissionSubtitles.contains(permission)) {
|
||||
return permissionSubtitles.value(permission).toString();
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
@@ -166,11 +189,10 @@ QVariant PermissionsModel::data(const QModelIndex &index, int role) const
|
||||
return QString();
|
||||
}
|
||||
if (role == IsDefaultValueRole) {
|
||||
return permission.contains(QLatin1String("default"));
|
||||
return defaultPermissions.contains(permission);
|
||||
}
|
||||
if (role == IsBasicPermissionRole) {
|
||||
return permission == QStringLiteral("invite") || permission == QStringLiteral("kick") || permission == QStringLiteral("ban")
|
||||
|| permission == QStringLiteral("redact");
|
||||
return basicPermissions.contains(permission);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -201,19 +223,19 @@ std::optional<int> PermissionsModel::powerLevel(const QString &permission) const
|
||||
}
|
||||
|
||||
if (const auto currentPowerLevelEvent = m_room->currentState().get<Quotient::RoomPowerLevelsEvent>()) {
|
||||
if (permission == QStringLiteral("ban")) {
|
||||
if (permission == BanKey) {
|
||||
return currentPowerLevelEvent->ban();
|
||||
} else if (permission == QStringLiteral("kick")) {
|
||||
} else if (permission == KickKey) {
|
||||
return currentPowerLevelEvent->kick();
|
||||
} else if (permission == QStringLiteral("invite")) {
|
||||
} else if (permission == InviteKey) {
|
||||
return currentPowerLevelEvent->invite();
|
||||
} else if (permission == QStringLiteral("redact")) {
|
||||
} else if (permission == RedactKey) {
|
||||
return currentPowerLevelEvent->redact();
|
||||
} else if (permission == QStringLiteral("users_default")) {
|
||||
} else if (permission == UsersDefaultKey) {
|
||||
return currentPowerLevelEvent->usersDefault();
|
||||
} else if (permission == QStringLiteral("state_default")) {
|
||||
} else if (permission == StateDefaultKey) {
|
||||
return currentPowerLevelEvent->stateDefault();
|
||||
} else if (permission == QStringLiteral("events_default")) {
|
||||
} else if (permission == EventsDefaultKey) {
|
||||
return currentPowerLevelEvent->eventsDefault();
|
||||
} else if (eventPermissions.contains(permission)) {
|
||||
return currentPowerLevelEvent->powerLevelForEvent(permission);
|
||||
@@ -241,6 +263,9 @@ void PermissionsModel::setPowerLevel(const QString &permission, const int &newPo
|
||||
auto powerLevelContent = currentPowerLevelEvent->contentJson();
|
||||
if (powerLevelContent.contains(permission)) {
|
||||
powerLevelContent[permission] = clampPowerLevel;
|
||||
// Deal with the case where a default or basic permission is missing from the event content erroneously.
|
||||
} else if (defaultPermissions.contains(permission) || basicPermissions.contains(permission)) {
|
||||
powerLevelContent[permission] = clampPowerLevel;
|
||||
} else {
|
||||
auto eventPowerLevels = powerLevelContent[QLatin1String("events")].toObject();
|
||||
eventPowerLevels[permission] = clampPowerLevel;
|
||||
|
||||
@@ -2,15 +2,10 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include "reactionmodel.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QFont>
|
||||
#ifdef HAVE_ICU
|
||||
#include <QTextBoundaryFinder>
|
||||
#include <QTextCharFormat>
|
||||
#include <unicode/uchar.h>
|
||||
#include <unicode/urename.h>
|
||||
#endif
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
@@ -154,30 +149,6 @@ QHash<int, QByteArray> ReactionModel::roleNames() const
|
||||
};
|
||||
}
|
||||
|
||||
bool isEmoji(const QString &text)
|
||||
{
|
||||
#ifdef HAVE_ICU
|
||||
QTextBoundaryFinder finder(QTextBoundaryFinder::Grapheme, text);
|
||||
int from = 0;
|
||||
while (finder.toNextBoundary() != -1) {
|
||||
auto to = finder.position();
|
||||
if (text[from].isSpace()) {
|
||||
from = to;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto first = text.mid(from, to - from).toUcs4()[0];
|
||||
if (!u_hasBinaryProperty(first, UCHAR_EMOJI)) {
|
||||
return false;
|
||||
}
|
||||
from = to;
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
QString ReactionModel::reactionText(QString text) const
|
||||
{
|
||||
text = text.toHtmlEscaped();
|
||||
@@ -191,7 +162,7 @@ QString ReactionModel::reactionText(QString text) const
|
||||
.arg(m_room->connection()->makeMediaUrl(QUrl(text)).toString(), QString::number(size));
|
||||
}
|
||||
|
||||
return isEmoji(text) ? QStringLiteral("<span style=\"font-family: 'emoji';\">") + text + QStringLiteral("</span>") : text;
|
||||
return Utils::isEmoji(text) ? QStringLiteral("<span style=\"font-family: 'emoji';\">") + text + QStringLiteral("</span>") : text;
|
||||
}
|
||||
|
||||
#include "moc_reactionmodel.cpp"
|
||||
|
||||
@@ -9,12 +9,15 @@
|
||||
#include <Quotient/omittable.h>
|
||||
#include <memory>
|
||||
|
||||
#include "chatbarcache.h"
|
||||
#include "eventhandler.h"
|
||||
#include "messagecomponenttype.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
ThreadModel::ThreadModel(const QString &threadRootId, NeoChatRoom *room)
|
||||
: QConcatenateTablesProxyModel(room)
|
||||
, m_threadRootId(threadRootId)
|
||||
, m_threadChatBarModel(new ThreadChatBarModel(this, room))
|
||||
{
|
||||
Q_ASSERT(!m_threadRootId.isEmpty());
|
||||
Q_ASSERT(room);
|
||||
@@ -25,7 +28,6 @@ ThreadModel::ThreadModel(const QString &threadRootId, NeoChatRoom *room)
|
||||
if (auto roomEvent = eventCast<const Quotient::RoomMessageEvent>(event)) {
|
||||
if (EventHandler::isThreaded(roomEvent) && EventHandler::threadRoot(roomEvent) == m_threadRootId) {
|
||||
addNewEvent(event);
|
||||
clearModels();
|
||||
addModels();
|
||||
}
|
||||
}
|
||||
@@ -38,7 +40,6 @@ ThreadModel::ThreadModel(const QString &threadRootId, NeoChatRoom *room)
|
||||
}
|
||||
}
|
||||
}
|
||||
clearModels();
|
||||
addModels();
|
||||
});
|
||||
|
||||
@@ -46,6 +47,11 @@ ThreadModel::ThreadModel(const QString &threadRootId, NeoChatRoom *room)
|
||||
addModels();
|
||||
}
|
||||
|
||||
QString ThreadModel::threadRootId() const
|
||||
{
|
||||
return m_threadRootId;
|
||||
}
|
||||
|
||||
MessageContentModel *ThreadModel::threadRootContentModel() const
|
||||
{
|
||||
return m_threadRootContentModel.get();
|
||||
@@ -77,7 +83,6 @@ void ThreadModel::fetchMore(const QModelIndex &parent)
|
||||
m_contentModels.push_back(new MessageContentModel(room, event.get()));
|
||||
}
|
||||
|
||||
clearModels();
|
||||
addModels();
|
||||
|
||||
const auto newNextBatch = m_currentJob->nextBatch();
|
||||
@@ -103,10 +108,15 @@ void ThreadModel::addNewEvent(const Quotient::RoomEvent *event)
|
||||
|
||||
void ThreadModel::addModels()
|
||||
{
|
||||
if (!sourceModels().isEmpty()) {
|
||||
clearModels();
|
||||
}
|
||||
|
||||
addSourceModel(m_threadRootContentModel.get());
|
||||
for (auto it = m_contentModels.crbegin(); it != m_contentModels.crend(); ++it) {
|
||||
addSourceModel(*it);
|
||||
}
|
||||
addSourceModel(m_threadChatBarModel);
|
||||
|
||||
beginResetModel();
|
||||
endResetModel();
|
||||
@@ -120,6 +130,61 @@ void ThreadModel::clearModels()
|
||||
removeSourceModel(model);
|
||||
}
|
||||
}
|
||||
removeSourceModel(m_threadChatBarModel);
|
||||
}
|
||||
|
||||
ThreadChatBarModel::ThreadChatBarModel(QObject *parent, NeoChatRoom *room)
|
||||
: QAbstractListModel(parent)
|
||||
, m_room(room)
|
||||
{
|
||||
if (m_room != nullptr) {
|
||||
connect(m_room->threadCache(), &ChatBarCache::threadIdChanged, this, [this](const QString &oldThreadId, const QString &newThreadId) {
|
||||
const auto threadModel = dynamic_cast<ThreadModel *>(this->parent());
|
||||
if (threadModel != nullptr && (oldThreadId == threadModel->threadRootId() || newThreadId == threadModel->threadRootId())) {
|
||||
beginResetModel();
|
||||
endResetModel();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
QVariant ThreadChatBarModel::data(const QModelIndex &idx, int role) const
|
||||
{
|
||||
if (idx.row() > 1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (role == ComponentTypeRole) {
|
||||
return MessageComponentType::ChatBar;
|
||||
}
|
||||
if (role == ChatBarCacheRole) {
|
||||
if (m_room == nullptr) {
|
||||
return {};
|
||||
}
|
||||
return QVariant::fromValue<ChatBarCache *>(m_room->threadCache());
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
int ThreadChatBarModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
if (m_room == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
const auto threadModel = dynamic_cast<ThreadModel *>(this->parent());
|
||||
if (threadModel != nullptr) {
|
||||
return m_room->threadCache()->threadId() == threadModel->threadRootId() ? 1 : 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> ThreadChatBarModel::roleNames() const
|
||||
{
|
||||
return {
|
||||
{ComponentTypeRole, "componentType"},
|
||||
{ChatBarCacheRole, "chatBarCache"},
|
||||
};
|
||||
}
|
||||
|
||||
#include "moc_threadmodel.cpp"
|
||||
|
||||
@@ -21,6 +21,56 @@
|
||||
class NeoChatRoom;
|
||||
class ReactionModel;
|
||||
|
||||
/**
|
||||
* @class ThreadChatBarModel
|
||||
*
|
||||
* A model to provide a chat bar component to send new messages in a thread.
|
||||
*/
|
||||
class ThreadChatBarModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
*
|
||||
* The role values need to match MessageContentModel not to blow up.
|
||||
*
|
||||
* @sa MessageContentModel
|
||||
*/
|
||||
enum Roles {
|
||||
ComponentTypeRole = MessageContentModel::ComponentTypeRole, /**< The type of component to visualise the message. */
|
||||
ChatBarCacheRole = MessageContentModel::ChatBarCacheRole, /**< The ChatBarCache to use. */
|
||||
};
|
||||
Q_ENUM(Roles)
|
||||
|
||||
explicit ThreadChatBarModel(QObject *parent, NeoChatRoom *room);
|
||||
|
||||
/**
|
||||
* @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;
|
||||
|
||||
/**
|
||||
* @brief 1 or 0, depending on whether a chat bar should be shown.
|
||||
*
|
||||
* @sa QAbstractItemModel::rowCount
|
||||
*/
|
||||
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
/**
|
||||
* @brief Returns a map with ComponentTypeRole it's the only one.
|
||||
*
|
||||
* @sa Roles, QAbstractItemModel::roleNames()
|
||||
*/
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
private:
|
||||
QPointer<NeoChatRoom> m_room;
|
||||
};
|
||||
|
||||
/**
|
||||
* @class ThreadModel
|
||||
*
|
||||
@@ -38,6 +88,8 @@ class ThreadModel : public QConcatenateTablesProxyModel
|
||||
public:
|
||||
explicit ThreadModel(const QString &threadRootId, NeoChatRoom *room);
|
||||
|
||||
QString threadRootId() const;
|
||||
|
||||
/**
|
||||
* @brief The content model for the thread root event.
|
||||
*/
|
||||
@@ -70,6 +122,7 @@ private:
|
||||
std::unique_ptr<MessageContentModel> m_threadRootContentModel;
|
||||
|
||||
std::deque<MessageContentModel *> m_contentModels;
|
||||
ThreadChatBarModel *m_threadChatBarModel;
|
||||
|
||||
QList<QString> m_events;
|
||||
QList<QString> m_pendingEvents;
|
||||
|
||||
@@ -62,7 +62,7 @@ void TimelineBeginningModel::setRoom(NeoChatRoom *room)
|
||||
|
||||
if (m_room != nullptr) {
|
||||
Quotient::connectUntil(m_room.get(), &Quotient::Room::eventsHistoryJobChanged, this, [this]() {
|
||||
if (m_room->allHistoryLoaded()) {
|
||||
if (m_room && m_room->allHistoryLoaded()) {
|
||||
// HACK: We have to do it this way because DelegateChooser doesn't update dynamically.
|
||||
beginRemoveRows({}, 0, 0);
|
||||
endRemoveRows();
|
||||
|
||||
@@ -125,7 +125,7 @@ void WebShortcutModel::trigger(const QString &data)
|
||||
void WebShortcutModel::configureWebShortcuts()
|
||||
{
|
||||
#ifdef HAVE_KIO
|
||||
auto job = new KIO::CommandLauncherJob(QStringLiteral("kcmshell5"), QStringList() << QStringLiteral("webshortcuts"), this);
|
||||
auto job = new KIO::CommandLauncherJob(QStringLiteral("kcmshell6"), QStringList() << QStringLiteral("webshortcuts"), this);
|
||||
job->exec();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -15,9 +15,6 @@
|
||||
<entry name="OpenRoom" type="String">
|
||||
<label>Latest opened room</label>
|
||||
</entry>
|
||||
<entry name="ColorScheme" type="String">
|
||||
<label>Color scheme</label>
|
||||
</entry>
|
||||
<entry name="Blur" type="bool">
|
||||
<label>Make NeoChat blurry</label>
|
||||
<default>false</default>
|
||||
@@ -89,6 +86,10 @@
|
||||
<label>Show deleted messages in the timeline</label>
|
||||
<default>false</default>
|
||||
</entry>
|
||||
<entry name="HideImages" type="bool">
|
||||
<label>Hide images in the timeline</label>
|
||||
<default>false</default>
|
||||
</entry>
|
||||
<entry name="ShowLinkPreview" type="bool">
|
||||
<label>Show preview of the links in the chat messages</label>
|
||||
</entry>
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include "controller.h"
|
||||
#include "jobs/neochatchangepasswordjob.h"
|
||||
#include "jobs/neochatdeactivateaccountjob.h"
|
||||
#include "linkpreviewer.h"
|
||||
#include "neochatconfig.h"
|
||||
#include "neochatroom.h"
|
||||
#include "notificationsmanager.h"
|
||||
@@ -45,6 +44,7 @@ NeoChatConnection::NeoChatConnection(QObject *parent)
|
||||
: Connection(parent)
|
||||
, m_threePIdModel(new ThreePIdModel(this))
|
||||
{
|
||||
m_linkPreviewers.setMaxCost(20);
|
||||
connectSignals();
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ NeoChatConnection::NeoChatConnection(const QUrl &server, QObject *parent)
|
||||
: Connection(server, parent)
|
||||
, m_threePIdModel(new ThreePIdModel(this))
|
||||
{
|
||||
m_linkPreviewers.setMaxCost(20);
|
||||
connectSignals();
|
||||
}
|
||||
|
||||
@@ -563,13 +564,13 @@ LinkPreviewer *NeoChatConnection::previewerForLink(const QUrl &link)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto previewer = m_linkPreviewers.value(link, nullptr);
|
||||
auto previewer = m_linkPreviewers.object(link);
|
||||
if (previewer != nullptr) {
|
||||
return previewer;
|
||||
}
|
||||
|
||||
previewer = new LinkPreviewer(link, this);
|
||||
m_linkPreviewers[link] = previewer;
|
||||
m_linkPreviewers.insert(link, previewer);
|
||||
return previewer;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QCache>
|
||||
#include <QObject>
|
||||
#include <QQmlEngine>
|
||||
|
||||
@@ -13,10 +14,9 @@
|
||||
#include <Quotient/keyimport.h>
|
||||
#endif
|
||||
|
||||
#include "linkpreviewer.h"
|
||||
#include "models/threepidmodel.h"
|
||||
|
||||
class LinkPreviewer;
|
||||
|
||||
class NeoChatConnection : public Quotient::Connection
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -222,7 +222,7 @@ private:
|
||||
|
||||
int m_badgeNotificationCount = 0;
|
||||
|
||||
QHash<QUrl, LinkPreviewer *> m_linkPreviewers;
|
||||
QCache<QUrl, LinkPreviewer> m_linkPreviewers;
|
||||
|
||||
bool m_canCheckMutualRooms = false;
|
||||
};
|
||||
|
||||
@@ -149,7 +149,7 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
|
||||
|
||||
if (NeoChatConfig::rejectUnknownInvites()) {
|
||||
auto job = this->connection()->callApi<NeochatGetCommonRoomsJob>(roomMemberEvent->senderId());
|
||||
connect(job, &BaseJob::result, this, [this, job, roomMemberEvent, showNotification] {
|
||||
connect(job, &BaseJob::result, this, [this, job, showNotification] {
|
||||
QJsonObject replyData = job->jsonData();
|
||||
if (replyData.contains(QStringLiteral("joined"))) {
|
||||
const bool inAnyOfOurRooms = !replyData[QStringLiteral("joined")].toArray().isEmpty();
|
||||
|
||||
@@ -4,9 +4,34 @@
|
||||
{
|
||||
"Name": "Tobias Fella",
|
||||
"Name[ar]": "توبياس فلة",
|
||||
"Name[ca@valencia]": "Tobias Fella",
|
||||
"Name[ca]": "Tobias Fella",
|
||||
"Name[cs]": "Tobias Fella",
|
||||
"Name[de]": "Tobias Fella",
|
||||
"Name[en_GB]": "Tobias Fella",
|
||||
"Name[eo]": "Tobias Fella",
|
||||
"Name[es]": "Tobias Fella",
|
||||
"Name[eu]": "Tobias Fella",
|
||||
"Name[fi]": "Tobias Fella",
|
||||
"Name[fr]": "Tobias Fella",
|
||||
"Name[gl]": "Tobias Fella",
|
||||
"Name[he]": "טוביאס פלה",
|
||||
"Name[hu]": "Tobias Fella",
|
||||
"Name[ia]": "Tobias Fella",
|
||||
"Name[it]": "Tobias Fella",
|
||||
"Name[ka]": "Tobias Fella",
|
||||
"Name[lv]": "Tobias Fella",
|
||||
"Name[nl]": "Tobias Fella",
|
||||
"Name[nn]": "Tobias Fella",
|
||||
"Name[pl]": "Tobias Fella",
|
||||
"Name[ru]": "Tobias Fella",
|
||||
"Name[sl]": "Tobias Fella",
|
||||
"Name[sv]": "Tobias Fella",
|
||||
"Name[ta]": "டோபியாஸ் ஃபெல்லா",
|
||||
"Name[x-test]": "xxTobias Fellaxx"
|
||||
"Name[tr]": "Tobias Fella",
|
||||
"Name[uk]": "Tobias Fella",
|
||||
"Name[x-test]": "xxTobias Fellaxx",
|
||||
"Name[zh_TW]": "Tobias Fella"
|
||||
}
|
||||
],
|
||||
"Category": "Utilities",
|
||||
@@ -15,6 +40,7 @@
|
||||
"Description[ca@valencia]": "Compartix a través de NeoChat",
|
||||
"Description[ca]": "Comparteix a través del NeoChat",
|
||||
"Description[de]": "Über NeoChat teilen",
|
||||
"Description[en_GB]": "Share via NeoChat",
|
||||
"Description[eo]": "Kundividi per NeoChat",
|
||||
"Description[es]": "Compartir mediante NeoChat",
|
||||
"Description[eu]": "Partekatu NeoChat bidez",
|
||||
@@ -42,9 +68,35 @@
|
||||
"License": "GPL",
|
||||
"Name": "NeoChat",
|
||||
"Name[ar]": "نيوتشات",
|
||||
"Name[ast]": "NeoChat",
|
||||
"Name[ca@valencia]": "NeoChat",
|
||||
"Name[ca]": "NeoChat",
|
||||
"Name[cs]": "NeoChat",
|
||||
"Name[de]": "NeoChat",
|
||||
"Name[en_GB]": "NeoChat",
|
||||
"Name[eo]": "NeoChat",
|
||||
"Name[es]": "NeoChat",
|
||||
"Name[eu]": "NeoChat",
|
||||
"Name[fi]": "NeoChat",
|
||||
"Name[fr]": "NeoChat",
|
||||
"Name[gl]": "NeoChat",
|
||||
"Name[he]": "NeoChat",
|
||||
"Name[hu]": "NeoChat",
|
||||
"Name[ia]": "Neochat",
|
||||
"Name[it]": "NeoChat",
|
||||
"Name[ka]": "NeoChat",
|
||||
"Name[lv]": "NeoChat",
|
||||
"Name[nl]": "NeoChat",
|
||||
"Name[nn]": "NeoChat",
|
||||
"Name[pl]": "NeoChat",
|
||||
"Name[ru]": "NeoChat",
|
||||
"Name[sl]": "NeoChat",
|
||||
"Name[sv]": "NeoChat",
|
||||
"Name[ta]": "நியோச்சாட்",
|
||||
"Name[tr]": "NeoChat",
|
||||
"Name[uk]": "NeoChat",
|
||||
"Name[x-test]": "xxNeoChatxx",
|
||||
"Name[zh_TW]": "NeoChat",
|
||||
"X-Purpose-ActionDisplay": "NeoChat"
|
||||
},
|
||||
"X-Purpose-PluginTypes": [
|
||||
|
||||
@@ -86,7 +86,7 @@ QQC2.Menu {
|
||||
}
|
||||
QQC2.MenuItem {
|
||||
text: i18n("Logout")
|
||||
icon.name: "list-remove-user"
|
||||
icon.name: "im-kick-user"
|
||||
onTriggered: confirmLogoutDialogComponent.createObject(QQC2.ApplicationWindow.window.overlay).open()
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ Kirigami.Dialog {
|
||||
if (switchUserButton.checked) {
|
||||
switchUserButton.checked = false;
|
||||
}
|
||||
accountView.currentIndex = Controller.activeConnectionIndex;
|
||||
root.close();
|
||||
}
|
||||
Keys.onUpPressed: {
|
||||
accountView.currentIndex = accountView.count - 1;
|
||||
|
||||
@@ -38,20 +38,20 @@ ColumnLayout {
|
||||
text: i18n("Edit")
|
||||
display: QQC2.AbstractButton.IconOnly
|
||||
|
||||
Component {
|
||||
id: imageEditorPage
|
||||
ImageEditorPage {
|
||||
imagePath: root.attachmentPath
|
||||
}
|
||||
}
|
||||
// Component {
|
||||
// id: imageEditorPage
|
||||
// ImageEditorPage {
|
||||
// imagePath: root.attachmentPath
|
||||
// }
|
||||
// }
|
||||
|
||||
onClicked: {
|
||||
let imageEditor = applicationWindow().pageStack.pushDialogLayer(imageEditorPage);
|
||||
imageEditor.newPathChanged.connect(function (newPath) {
|
||||
applicationWindow().pageStack.layers.pop();
|
||||
root.attachmentPath = newPath;
|
||||
});
|
||||
}
|
||||
// onClicked: {
|
||||
// let imageEditor = applicationWindow().pageStack.pushDialogLayer(imageEditorPage);
|
||||
// imageEditor.newPathChanged.connect(function (newPath) {
|
||||
// applicationWindow().pageStack.layers.pop();
|
||||
// root.attachmentPath = newPath;
|
||||
// });
|
||||
// }
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.visible: hovered
|
||||
}
|
||||
|
||||
@@ -70,6 +70,11 @@ Loader {
|
||||
*/
|
||||
property list<Kirigami.Action> actions
|
||||
|
||||
/**
|
||||
* @brief Whether the web search menu should be shown or not.
|
||||
*/
|
||||
property bool enableWebSearch: true
|
||||
|
||||
/**
|
||||
* Some common actions shared between menus
|
||||
*/
|
||||
@@ -82,16 +87,23 @@ Loader {
|
||||
|
||||
component RemoveMessageAction: Kirigami.Action {
|
||||
visible: author.isLocalMember || currentRoom.canSendState("redact")
|
||||
text: i18n("Remove")
|
||||
text: i18nc("@action:button", "Remove")
|
||||
icon.name: "edit-delete-remove"
|
||||
icon.color: "red"
|
||||
onTriggered: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'RemoveSheet'), {
|
||||
room: currentRoom,
|
||||
eventId: eventId
|
||||
}, {
|
||||
title: i18nc("@title", "Remove Message"),
|
||||
width: Kirigami.Units.gridUnit * 25
|
||||
})
|
||||
onTriggered: {
|
||||
let dialog = applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
|
||||
title: i18nc("@title:dialog", "Remove Message"),
|
||||
placeholder: i18nc("@info:placeholder", "Reason for removing this message"),
|
||||
actionText: i18nc("@action:button 'Remove' as in 'Remove this message'", "Remove"),
|
||||
icon: "delete"
|
||||
}, {
|
||||
title: i18nc("@title:dialog", "Remove Message"),
|
||||
width: Kirigami.Units.gridUnit * 25
|
||||
});
|
||||
dialog.accepted.connect(reason => {
|
||||
currentRoom.redactEvent(root.eventId, reason);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
component ReplyMessageAction: Kirigami.Action {
|
||||
@@ -108,13 +120,28 @@ Loader {
|
||||
text: i18nc("@action:button 'Report' as in 'Report this event to the administrators'", "Report")
|
||||
icon.name: "dialog-warning-symbolic"
|
||||
visible: !author.isLocalMember
|
||||
onTriggered: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReportSheet'), {
|
||||
room: currentRoom,
|
||||
eventId: eventId
|
||||
}, {
|
||||
title: i18nc("@title", "Report Message"),
|
||||
width: Kirigami.Units.gridUnit * 25
|
||||
})
|
||||
onTriggered: {
|
||||
let dialog = applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
|
||||
title: i18nc("@title:dialog", "Report Message"),
|
||||
placeholder: i18nc("@info:placeholder", "Reason for reporting this message"),
|
||||
icon: "dialog-warning-symbolic",
|
||||
actionText: i18nc("@action:button 'Report' as in 'Report this event to the administrators'", "Report")
|
||||
}, {
|
||||
title: i18nc("@title", "Report Message"),
|
||||
width: Kirigami.Units.gridUnit * 25
|
||||
});
|
||||
dialog.accepted.connect(reason => {
|
||||
currentRoom.reportEvent(root.eventId, reason);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
component ShowUserAction: Kirigami.Action {
|
||||
text: i18nc("@action:inmenu", "Show User")
|
||||
icon.name: "username-copy"
|
||||
onTriggered: {
|
||||
RoomManager.resolveResource(author.id)
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
@@ -128,6 +155,7 @@ Loader {
|
||||
id: menuItem
|
||||
visible: modelData.visible
|
||||
title: modelData.text
|
||||
icon: modelData.icon
|
||||
|
||||
Instantiator {
|
||||
model: modelData.children
|
||||
@@ -158,7 +186,8 @@ Loader {
|
||||
QQC2.Menu {
|
||||
id: webshortcutmenu
|
||||
title: i18n("Search for '%1'", webshortcutmodel.trunkatedSearchText)
|
||||
property bool isVisible: webshortcutmodel.enabled
|
||||
icon.name: "search-symbolic"
|
||||
property bool isVisible: webshortcutmodel.enabled && root.enableWebSearch
|
||||
Component.onCompleted: {
|
||||
webshortcutmenu.parent.visible = isVisible;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,9 @@ DelegateContextMenu {
|
||||
*/
|
||||
required property var progressInfo
|
||||
|
||||
// Web search isn't useful for images
|
||||
enableWebSearch: false
|
||||
|
||||
/**
|
||||
* @brief The main list of menu item actions.
|
||||
*
|
||||
@@ -67,15 +70,23 @@ DelegateContextMenu {
|
||||
text: i18n("Remove")
|
||||
icon.name: "edit-delete-remove"
|
||||
icon.color: "red"
|
||||
onTriggered: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'RemoveSheet'), {
|
||||
room: currentRoom,
|
||||
eventId: eventId
|
||||
}, {
|
||||
title: i18nc("@title", "Remove Message"),
|
||||
width: Kirigami.Units.gridUnit * 25
|
||||
})
|
||||
onTriggered: {
|
||||
let dialog = applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
|
||||
title: i18nc("@title:dialog", "Remove Message"),
|
||||
placeholder: i18nc("@info:placeholder", "Reason for removing this message"),
|
||||
actionText: i18nc("@action:button 'Remove' as in 'Remove this message'", "Remove"),
|
||||
icon: "delete"
|
||||
}, {
|
||||
title: i18nc("@title:dialog", "Remove Message"),
|
||||
width: Kirigami.Units.gridUnit * 25
|
||||
});
|
||||
dialog.accepted.connect(reason => {
|
||||
currentRoom.redactEvent(root.eventId, reason);
|
||||
});
|
||||
}
|
||||
},
|
||||
DelegateContextMenu.ReportMessageAction {},
|
||||
DelegateContextMenu.ShowUserAction {},
|
||||
DelegateContextMenu.ViewSourceAction {}
|
||||
]
|
||||
|
||||
|
||||
116
src/qml/FileDelegateContextMenuMobile.qml
Normal file
116
src/qml/FileDelegateContextMenuMobile.qml
Normal file
@@ -0,0 +1,116 @@
|
||||
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import Qt.labs.platform
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
import org.kde.neochat
|
||||
|
||||
/**
|
||||
* @brief The menu for media messages.
|
||||
*
|
||||
* This component just overloads the actions and nested actions of the base menu
|
||||
* to what is required for a media item.
|
||||
*
|
||||
* @sa DelegateContextMenu
|
||||
*/
|
||||
DelegateContextMenu {
|
||||
id: root
|
||||
|
||||
/**
|
||||
* @brief The MIME type of the media.
|
||||
*/
|
||||
property string mimeType
|
||||
|
||||
/**
|
||||
* @brief Progress info when downloading files.
|
||||
*
|
||||
* @sa Quotient::FileTransferInfo
|
||||
*/
|
||||
required property var progressInfo
|
||||
|
||||
// Web search isn't useful for images
|
||||
enableWebSearch: false
|
||||
|
||||
/**
|
||||
* @brief The main list of menu item actions.
|
||||
*
|
||||
* Each action will be instantiated as a single line in the menu.
|
||||
*/
|
||||
property list<Kirigami.Action> actions: [
|
||||
Kirigami.Action {
|
||||
text: i18n("Open Externally")
|
||||
icon.name: "document-open"
|
||||
onTriggered: {
|
||||
currentRoom.openEventMediaExternally(root.eventId);
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("Save As")
|
||||
icon.name: "document-save"
|
||||
onTriggered: {
|
||||
var dialog = saveAsDialog.createObject(QQC2.Overlay.overlay);
|
||||
dialog.open();
|
||||
dialog.currentFile = dialog.folder + "/" + currentRoom.fileNameToDownload(eventId);
|
||||
}
|
||||
},
|
||||
DelegateContextMenu.ReplyMessageAction {},
|
||||
Kirigami.Action {
|
||||
text: i18n("Copy")
|
||||
icon.name: "edit-copy"
|
||||
onTriggered: {
|
||||
currentRoom.copyEventMedia(root.eventId);
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
visible: author.id === currentRoom.localMember.id || currentRoom.canSendState("redact")
|
||||
text: i18n("Remove")
|
||||
icon.name: "edit-delete-remove"
|
||||
icon.color: "red"
|
||||
onTriggered: {
|
||||
let dialog = applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
|
||||
title: i18nc("@title:dialog", "Remove Message"),
|
||||
placeholder: i18nc("@info:placeholder", "Reason for removing this message"),
|
||||
actionText: i18nc("@action:button 'Remove' as in 'Remove this message'", "Remove"),
|
||||
icon: "delete"
|
||||
}, {
|
||||
title: i18nc("@title:dialog", "Remove Message"),
|
||||
width: Kirigami.Units.gridUnit * 25
|
||||
});
|
||||
dialog.accepted.connect(reason => {
|
||||
currentRoom.redactEvent(root.eventId, reason);
|
||||
});
|
||||
}
|
||||
},
|
||||
DelegateContextMenu.ReportMessageAction {},
|
||||
DelegateContextMenu.ShowUserAction {},
|
||||
DelegateContextMenu.ViewSourceAction {}
|
||||
]
|
||||
|
||||
/**
|
||||
* @brief The list of menu item actions that have sub-actions.
|
||||
*
|
||||
* Each action will be instantiated as a single line that opens a sub menu.
|
||||
*/
|
||||
property list<Kirigami.Action> nestedActions: [
|
||||
]
|
||||
|
||||
Component {
|
||||
id: saveAsDialog
|
||||
FileDialog {
|
||||
fileMode: FileDialog.SaveFile
|
||||
folder: NeoChatConfig.lastSaveDirectory.length > 0 ? NeoChatConfig.lastSaveDirectory : StandardPaths.writableLocation(StandardPaths.DownloadLocation)
|
||||
onAccepted: {
|
||||
if (!currentFile) {
|
||||
return;
|
||||
}
|
||||
NeoChatConfig.lastSaveDirectory = folder;
|
||||
NeoChatConfig.save();
|
||||
currentRoom.downloadFile(eventId, currentFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,9 +123,10 @@ QQC2.Control {
|
||||
text: i18n("Reply in Thread")
|
||||
icon.name: "dialog-messages"
|
||||
onTriggered: {
|
||||
root.currentRoom.mainCache.replyId = "";
|
||||
root.currentRoom.mainCache.threadId = root.delegate.isThreaded ? root.delegate.threadRoot : root.delegate.eventId;
|
||||
root.currentRoom.editCache.editId = "";
|
||||
root.currentRoom.threadCache.replyId = "";
|
||||
root.currentRoom.threadCache.threadId = root.delegate.isThreaded ? root.delegate.threadRoot : root.delegate.eventId;
|
||||
root.currentRoom.mainCache.clearRelations();
|
||||
root.currentRoom.editCache.clearRelations();
|
||||
root.focusChatBar();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
@@ -11,111 +13,77 @@ import org.kde.kirigamiaddons.labs.components as KirigamiComponents
|
||||
|
||||
import org.kde.neochat
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
SearchPage {
|
||||
id: root
|
||||
|
||||
property NeoChatRoom room
|
||||
|
||||
title: i18n("Invite a User")
|
||||
title: i18nc("@title:dialog", "Invite a User")
|
||||
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
icon.name: "dialog-close"
|
||||
text: i18nc("@action", "Cancel")
|
||||
onTriggered: root.closeDialog()
|
||||
}
|
||||
]
|
||||
header: RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Kirigami.Units.largeSpacing
|
||||
searchFieldPlaceholder: i18nc("@info:placeholder", "Find a user…")
|
||||
noResultPlaceholderMessage: i18nc("@info:placeholder", "No users found")
|
||||
|
||||
Kirigami.SearchField {
|
||||
id: identifierField
|
||||
property bool isUserId: text.match(/@(.+):(.+)/g)
|
||||
Layout.fillWidth: true
|
||||
headerTrailing: QQC2.Button {
|
||||
icon.name: "list-add"
|
||||
display: QQC2.Button.IconOnly
|
||||
enabled: root.model.searchText.match(/@(.+):(.+)/g) && !root.room.containsUser(root.model.searchText)
|
||||
|
||||
placeholderText: i18n("Find a user...")
|
||||
onAccepted: userDictListModel.search()
|
||||
}
|
||||
text: i18nc("@action:button", "Invite this User")
|
||||
|
||||
QQC2.Button {
|
||||
visible: identifierField.isUserId
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.text: root.room.containsUser(root.model.searchText) ? i18nc("@info:tooltip", "User is either already a member or has been invited") : text
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
|
||||
text: i18n("Add")
|
||||
highlighted: true
|
||||
|
||||
onClicked: {
|
||||
room.inviteToRoom(identifierField.text);
|
||||
}
|
||||
}
|
||||
onClicked: root.room.inviteToRoom(root.model.searchText);
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: userDictListView
|
||||
model: UserDirectoryListModel {
|
||||
id: userDictListModel
|
||||
|
||||
clip: true
|
||||
connection: root.room.connection
|
||||
}
|
||||
|
||||
model: UserDirectoryListModel {
|
||||
id: userDictListModel
|
||||
modelDelegate: Delegates.RoundedItemDelegate {
|
||||
id: delegate
|
||||
|
||||
connection: root.room.connection
|
||||
searchText: identifierField.text
|
||||
}
|
||||
required property string userId
|
||||
required property string displayName
|
||||
required property url avatarUrl
|
||||
|
||||
Kirigami.PlaceholderMessage {
|
||||
anchors.centerIn: parent
|
||||
text: displayName
|
||||
|
||||
visible: userDictListView.count < 1
|
||||
contentItem: RowLayout {
|
||||
KirigamiComponents.Avatar {
|
||||
Layout.preferredWidth: Kirigami.Units.iconSizes.medium
|
||||
Layout.preferredHeight: Kirigami.Units.iconSizes.medium
|
||||
source: delegate.avatarUrl
|
||||
name: delegate.displayName
|
||||
}
|
||||
|
||||
text: i18n("No users available")
|
||||
}
|
||||
Delegates.SubtitleContentItem {
|
||||
itemDelegate: delegate
|
||||
subtitle: delegate.userId
|
||||
labelItem.textFormat: Text.PlainText
|
||||
}
|
||||
|
||||
delegate: Delegates.RoundedItemDelegate {
|
||||
id: delegate
|
||||
QQC2.ToolButton {
|
||||
id: inviteButton
|
||||
|
||||
required property string userId
|
||||
required property string displayName
|
||||
required property url avatarUrl
|
||||
readonly property bool inRoom: root.room && root.room.containsUser(delegate.userId)
|
||||
|
||||
property bool inRoom: room && room.containsUser(userId)
|
||||
icon.name: "document-send"
|
||||
text: i18nc("@action:button", "Send invitation")
|
||||
opacity: inRoom ? 0.5 : 1
|
||||
enabled: !inRoom
|
||||
|
||||
text: displayName
|
||||
|
||||
contentItem: RowLayout {
|
||||
KirigamiComponents.Avatar {
|
||||
Layout.preferredWidth: Kirigami.Units.iconSizes.medium
|
||||
Layout.preferredHeight: Kirigami.Units.iconSizes.medium
|
||||
source: delegate.avatarUrl
|
||||
name: delegate.displayName
|
||||
onClicked: {
|
||||
inviteButton.enabled = false;
|
||||
root.room.inviteToRoom(delegate.userId);
|
||||
}
|
||||
|
||||
Delegates.SubtitleContentItem {
|
||||
itemDelegate: delegate
|
||||
subtitle: delegate.userId
|
||||
labelItem.textFormat: Text.PlainText
|
||||
}
|
||||
|
||||
QQC2.ToolButton {
|
||||
id: inviteButton
|
||||
icon.name: "document-send"
|
||||
text: i18n("Send invitation")
|
||||
checkable: true
|
||||
checked: inRoom
|
||||
opacity: inRoom ? 0.5 : 1
|
||||
|
||||
onToggled: {
|
||||
if (inRoom) {
|
||||
checked = true;
|
||||
} else {
|
||||
room.inviteToRoom(delegate.userId);
|
||||
applicationWindow().pageStack.layers.pop();
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.ToolTip.text: !inRoom ? text : i18n("User is either already a member or has been invited")
|
||||
QQC2.ToolTip.visible: inviteButton.hovered
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
}
|
||||
QQC2.ToolTip.text: !inRoom ? text : i18nc("@info:tooltip", "User is either already a member or has been invited")
|
||||
QQC2.ToolTip.visible: inviteButton.hovered
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,9 +30,36 @@ Components.AbstractMaximizeComponent {
|
||||
root.close();
|
||||
}
|
||||
enabled: !!root.location
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18nc("@action:intoolbar Re-center the map onto the set location", "Re-Center")
|
||||
icon.name: "snap-bounding-box-center-symbolic"
|
||||
onTriggered: mapView.map.fitViewportToMapItems([mapView.locationMapItem])
|
||||
enabled: root.location !== undefined
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18nc("@action:intoolbar Determine the device's location", "Locate")
|
||||
icon.name: "mark-location-symbolic"
|
||||
enabled: positionSource.valid
|
||||
onTriggered: positionSource.update()
|
||||
}
|
||||
]
|
||||
|
||||
PositionSource {
|
||||
id: positionSource
|
||||
|
||||
active: false
|
||||
|
||||
onPositionChanged: {
|
||||
const coord = position.coordinate;
|
||||
mapView.gpsMapItem.latitude = coord.latitude;
|
||||
mapView.gpsMapItem.longitude = coord.longitude;
|
||||
|
||||
mapView.map.addMapItem(mapView.gpsMapItem);
|
||||
mapView.map.fitViewportToMapItems([mapView.gpsMapItem])
|
||||
}
|
||||
}
|
||||
|
||||
content: MapView {
|
||||
id: mapView
|
||||
map.plugin: OsmLocationPlugin.plugin
|
||||
@@ -54,6 +81,15 @@ Components.AbstractMaximizeComponent {
|
||||
author: null
|
||||
}
|
||||
|
||||
readonly property LocationMapItem gpsMapItem: LocationMapItem {
|
||||
latitude: 0.0
|
||||
longitude: 0.0
|
||||
isLive: true
|
||||
heading: NaN
|
||||
asset: ""
|
||||
author: null
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: mapView.map
|
||||
function onCopyrightLinkActivated(link: string) {
|
||||
|
||||
@@ -27,7 +27,10 @@ Kirigami.Page {
|
||||
let c = LocationHelper.center(LocationHelper.unite(locationsModel.boundingBox, liveLocationsModel.boundingBox));
|
||||
return QtPositioning.coordinate(c.y, c.x);
|
||||
}
|
||||
map.zoomLevel: LocationHelper.zoomToFit(LocationHelper.unite(locationsModel.boundingBox, liveLocationsModel.boundingBox), mapView.width, mapView.height)
|
||||
map.zoomLevel: {
|
||||
const zoom = LocationHelper.zoomToFit(LocationHelper.unite(locationsModel.boundingBox, liveLocationsModel.boundingBox), mapView.width, mapView.height)
|
||||
return Math.min(Math.max(zoom, map.minimumZoomLevel), map.maximumZoomLevel);
|
||||
}
|
||||
|
||||
MapItemView {
|
||||
Component.onCompleted: mapView.map.addMapItemView(this)
|
||||
|
||||
@@ -6,6 +6,7 @@ import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.config as KConfig
|
||||
|
||||
import org.kde.neochat
|
||||
import org.kde.neochat.login
|
||||
@@ -68,36 +69,8 @@ Kirigami.ApplicationWindow {
|
||||
}
|
||||
}
|
||||
|
||||
// This timer allows to batch update the window size change to reduce
|
||||
// the io load and also work around the fact that x/y/width/height are
|
||||
// changed when loading the page and overwrite the saved geometry from
|
||||
// the previous session.
|
||||
Timer {
|
||||
id: saveWindowGeometryTimer
|
||||
interval: 1000
|
||||
onTriggered: WindowController.saveGeometry()
|
||||
}
|
||||
|
||||
Connections {
|
||||
id: saveWindowGeometryConnections
|
||||
enabled: false // Disable on startup to avoid writing wrong values if the window is hidden
|
||||
target: root
|
||||
|
||||
function onClosing() {
|
||||
WindowController.saveGeometry();
|
||||
}
|
||||
function onWidthChanged() {
|
||||
saveWindowGeometryTimer.restart();
|
||||
}
|
||||
function onHeightChanged() {
|
||||
saveWindowGeometryTimer.restart();
|
||||
}
|
||||
function onXChanged() {
|
||||
saveWindowGeometryTimer.restart();
|
||||
}
|
||||
function onYChanged() {
|
||||
saveWindowGeometryTimer.restart();
|
||||
}
|
||||
KConfig.WindowStateSaver {
|
||||
configGroupName: "Window"
|
||||
}
|
||||
|
||||
QuickSwitcher {
|
||||
@@ -198,11 +171,9 @@ Kirigami.ApplicationWindow {
|
||||
if (ShareHandler.text && root.connection) {
|
||||
root.handleShare()
|
||||
}
|
||||
if (NeoChatConfig.minimizeToSystemTrayOnStartup && !Kirigami.Settings.isMobile && Controller.supportSystemTray && NeoChatConfig.systemTray) {
|
||||
restoreWindowGeometryConnections.enabled = true; // To restore window size and position
|
||||
} else {
|
||||
const hasSystemTray = Controller.supportSystemTray && NeoChatConfig.systemTray;
|
||||
if (Kirigami.Settings.isMobile || !(hasSystemTray && NeoChatConfig.minimizeToSystemTrayOnStartup)) {
|
||||
visible = true;
|
||||
saveWindowGeometryConnections.enabled = true;
|
||||
}
|
||||
}
|
||||
Connections {
|
||||
@@ -276,22 +247,7 @@ Kirigami.ApplicationWindow {
|
||||
target: Controller
|
||||
|
||||
function onErrorOccured(error, detail) {
|
||||
showPassiveNotification(detail.length > 0 ? i18n("%1: %2", error, detail) : error);
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
id: restoreWindowGeometryConnections
|
||||
enabled: false
|
||||
target: root
|
||||
|
||||
function onVisibleChanged() {
|
||||
if (!visible) {
|
||||
return;
|
||||
}
|
||||
Controller.restoreWindowGeometry(root);
|
||||
restoreWindowGeometryConnections.enabled = false; // Only restore window geometry for the first time
|
||||
saveWindowGeometryConnections.enabled = true;
|
||||
showPassiveNotification(detail.length > 0 ? i18n("%1: %2", error, detail) : error, "short");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,13 +66,7 @@ DelegateContextMenu {
|
||||
onTriggered: Clipboard.saveText(root.selectedText.length > 0 ? root.selectedText : root.plainText)
|
||||
},
|
||||
DelegateContextMenu.ReportMessageAction {},
|
||||
Kirigami.Action {
|
||||
text: i18nc("@action:inmenu", "Show User")
|
||||
icon.name: "username-copy"
|
||||
onTriggered: {
|
||||
RoomManager.resolveResource(author.id)
|
||||
}
|
||||
},
|
||||
DelegateContextMenu.ShowUserAction {},
|
||||
DelegateContextMenu.ViewSourceAction {},
|
||||
Kirigami.Action {
|
||||
text: i18n("Copy Link")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
@@ -12,10 +12,11 @@ import org.kde.neochat
|
||||
Kirigami.Page {
|
||||
id: root
|
||||
|
||||
property NeoChatRoom room
|
||||
property string userId
|
||||
required property string placeholder
|
||||
required property string actionText
|
||||
required property string icon
|
||||
|
||||
title: i18n("Ban User")
|
||||
signal accepted(reason: string)
|
||||
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
@@ -24,7 +25,7 @@ Kirigami.Page {
|
||||
|
||||
QQC2.TextArea {
|
||||
id: reason
|
||||
placeholderText: i18n("Reason for banning this user")
|
||||
placeholderText: root.placeholder
|
||||
anchors.fill: parent
|
||||
wrapMode: TextEdit.Wrap
|
||||
|
||||
@@ -40,11 +41,11 @@ Kirigami.Page {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
QQC2.Button {
|
||||
text: i18nc("@action:button 'Ban' as in 'Ban this user'", "Ban")
|
||||
icon.name: "im-ban-user"
|
||||
text: root.actionText
|
||||
icon.name: root.icon
|
||||
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole
|
||||
onClicked: {
|
||||
root.room.ban(root.userId, reason.text);
|
||||
root.accepted(reason.text);
|
||||
root.closeDialog();
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,7 @@ Kirigami.Dialog {
|
||||
spacing: Kirigami.Units.largeSpacing * 4
|
||||
Avatar {
|
||||
source: root.connection.makeMediaUrl(SpaceHierarchyCache.recommendedSpaceAvatar)
|
||||
name: SpaceHierarchyCache.recommendedSpaceDisplayName
|
||||
}
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
import org.kde.neochat
|
||||
|
||||
Kirigami.Page {
|
||||
id: root
|
||||
|
||||
property NeoChatRoom room
|
||||
property string eventId
|
||||
|
||||
property string userId: ""
|
||||
|
||||
title: userId.length > 0 ? i18n("Remove Messages") : i18n("Remove Message")
|
||||
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
|
||||
QQC2.TextArea {
|
||||
id: reason
|
||||
placeholderText: userId.length > 0 ? i18n("Reason for removing this user's recent messages") : i18n("Reason for removing this message")
|
||||
anchors.fill: parent
|
||||
wrapMode: TextEdit.Wrap
|
||||
background: Rectangle {
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
}
|
||||
}
|
||||
|
||||
footer: QQC2.ToolBar {
|
||||
QQC2.DialogButtonBox {
|
||||
anchors.fill: parent
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
QQC2.Button {
|
||||
text: i18nc("@action:button 'Remove' as in 'Remove this message'", "Remove")
|
||||
icon.name: "delete"
|
||||
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole
|
||||
onClicked: {
|
||||
if (root.userId.length > 0) {
|
||||
root.room.deleteMessagesByUser(root.userId, reason.text);
|
||||
} else {
|
||||
root.room.redactEvent(root.eventId, reason.text);
|
||||
}
|
||||
root.closeDialog();
|
||||
}
|
||||
}
|
||||
QQC2.Button {
|
||||
text: i18nc("@action", "Cancel")
|
||||
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.RejectRole
|
||||
onClicked: root.closeDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
import org.kde.neochat
|
||||
|
||||
Kirigami.Page {
|
||||
id: root
|
||||
|
||||
property NeoChatRoom room
|
||||
property string eventId
|
||||
|
||||
title: i18n("Report Message")
|
||||
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
|
||||
QQC2.TextArea {
|
||||
id: reason
|
||||
placeholderText: i18n("Reason for reporting this message")
|
||||
anchors.fill: parent
|
||||
wrapMode: TextEdit.Wrap
|
||||
|
||||
background: Rectangle {
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
}
|
||||
}
|
||||
|
||||
footer: QQC2.ToolBar {
|
||||
QQC2.DialogButtonBox {
|
||||
anchors.fill: parent
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
QQC2.Button {
|
||||
text: i18nc("@action:button 'Report' as in 'Report this event to the administrators'", "Report")
|
||||
icon.name: "dialog-warning-symbolic"
|
||||
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole
|
||||
onClicked: {
|
||||
root.room.reportEvent(eventId, reason.text);
|
||||
root.closeDialog();
|
||||
}
|
||||
}
|
||||
QQC2.Button {
|
||||
text: i18nc("@action", "Cancel")
|
||||
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.RejectRole
|
||||
onClicked: root.closeDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,7 +145,7 @@ Delegates.RoundedItemDelegate {
|
||||
text: i18n("Configure room")
|
||||
display: QQC2.Button.IconOnly
|
||||
|
||||
icon.name: "configure"
|
||||
icon.name: "overflow-menu-symbolic"
|
||||
onClicked: createRoomListContextMenu()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,15 +281,27 @@ Kirigami.Page {
|
||||
}
|
||||
|
||||
function onShowFileMenu(eventId, author, messageComponentType, plainText, mimeType, progressInfo, isThread) {
|
||||
const contextMenu = fileDelegateContextMenu.createObject(root, {
|
||||
author: author,
|
||||
eventId: eventId,
|
||||
plainText: plainText,
|
||||
mimeType: mimeType,
|
||||
progressInfo: progressInfo,
|
||||
isThread: isThread
|
||||
});
|
||||
contextMenu.open();
|
||||
if (Kirigami.Settings.isMobile) {
|
||||
const contextMenu = fileDelegateContextMenuMobile.createObject(root, {
|
||||
author: author,
|
||||
eventId: eventId,
|
||||
plainText: plainText,
|
||||
mimeType: mimeType,
|
||||
progressInfo: progressInfo,
|
||||
isThread: isThread
|
||||
});
|
||||
contextMenu.open();
|
||||
} else {
|
||||
const contextMenu = fileDelegateContextMenu.createObject(root, {
|
||||
author: author,
|
||||
eventId: eventId,
|
||||
plainText: plainText,
|
||||
mimeType: mimeType,
|
||||
progressInfo: progressInfo,
|
||||
isThread: isThread
|
||||
});
|
||||
contextMenu.open();
|
||||
}
|
||||
}
|
||||
|
||||
function onShowMaximizedMedia(index) {
|
||||
@@ -327,6 +339,13 @@ Kirigami.Page {
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: fileDelegateContextMenuMobile
|
||||
FileDelegateContextMenuMobile {
|
||||
connection: root.connection
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: maximizeComponent
|
||||
NeochatMaximizeComponent {
|
||||
|
||||
@@ -119,10 +119,7 @@ QQC2.Control {
|
||||
activeFocusOnTab: true
|
||||
|
||||
checked: RoomManager.currentSpace.length === 0
|
||||
onSelected: {
|
||||
RoomManager.currentSpace = "";
|
||||
root.selectionChanged();
|
||||
}
|
||||
onSelected: RoomManager.currentSpace = ""
|
||||
}
|
||||
AvatarTabButton {
|
||||
id: directChatButton
|
||||
@@ -154,7 +151,7 @@ QQC2.Control {
|
||||
visible: true
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Button
|
||||
Kirigami.Theme.inherit: false
|
||||
color: root.connection.directChatsHaveHighlightNotifications ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.backgroundColor
|
||||
color: root.connection.directChatsHaveHighlightNotifications || root.connection.directChatInvites ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.backgroundColor
|
||||
radius: height / 2
|
||||
}
|
||||
|
||||
@@ -162,16 +159,20 @@ QQC2.Control {
|
||||
id: directChatNotificationCountTextMetrics
|
||||
text: directChatNotificationCountLabel.text
|
||||
}
|
||||
|
||||
Kirigami.Icon {
|
||||
anchors.fill: parent
|
||||
|
||||
source: "list-add-symbolic"
|
||||
visible: root.connection.directChatInvites && root.connection.directChatNotifications === 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
activeFocusOnTab: true
|
||||
|
||||
checked: RoomManager.currentSpace === "DM"
|
||||
onSelected: {
|
||||
RoomManager.currentSpace = "DM";
|
||||
root.selectionChanged();
|
||||
}
|
||||
onSelected: RoomManager.currentSpace = "DM"
|
||||
}
|
||||
|
||||
Repeater {
|
||||
|
||||
@@ -122,7 +122,18 @@ Kirigami.Dialog {
|
||||
text: i18n("Kick this user")
|
||||
icon.name: "im-kick-user"
|
||||
onTriggered: {
|
||||
root.room.kickMember(root.user.id);
|
||||
let dialog = (root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
|
||||
title: i18nc("@title:dialog", "Kick User"),
|
||||
placeholder: i18nc("@info:placeholder", "Reason for kicking this user"),
|
||||
actionText: i18nc("@action:button 'Kick' as in 'Kick this user from the room'", "Kick"),
|
||||
icon: "im-kick-user"
|
||||
}, {
|
||||
title: i18nc("@title:dialog", "Kick User"),
|
||||
width: Kirigami.Units.gridUnit * 25
|
||||
});
|
||||
dialog.accepted.connect(reason => {
|
||||
root.room.kickMember(root.user.id, reason);
|
||||
});
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
@@ -150,13 +161,18 @@ Kirigami.Dialog {
|
||||
icon.name: "im-ban-user"
|
||||
icon.color: Kirigami.Theme.negativeTextColor
|
||||
onTriggered: {
|
||||
(root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'BanSheet'), {
|
||||
room: root.room,
|
||||
userId: root.user.id
|
||||
let dialog = (root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
|
||||
title: i18nc("@title:dialog", "Ban User"),
|
||||
placeholder: i18nc("@info:placeholder", "Reason for banning this user"),
|
||||
actionText: i18nc("@action:button 'Ban' as in 'Ban this user'", "Ban"),
|
||||
icon: "im-ban-user"
|
||||
}, {
|
||||
title: i18nc("@title", "Ban User"),
|
||||
title: i18nc("@title:dialog", "Ban User"),
|
||||
width: Kirigami.Units.gridUnit * 25
|
||||
});
|
||||
dialog.accepted.connect(reason => {
|
||||
root.room.ban(root.user.id, reason);
|
||||
});
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
@@ -204,17 +220,22 @@ Kirigami.Dialog {
|
||||
visible: root.room && (root.user.id === root.connection.localUserId || room.canSendState("redact"))
|
||||
|
||||
action: Kirigami.Action {
|
||||
text: i18n("Remove recent messages by this user")
|
||||
text: i18nc("@action:button", "Remove recent messages by this user")
|
||||
icon.name: "delete"
|
||||
icon.color: Kirigami.Theme.negativeTextColor
|
||||
onTriggered: {
|
||||
applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'RemoveSheet'), {
|
||||
room: root.room,
|
||||
userId: root.user.id
|
||||
let dialog = applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
|
||||
title: i18nc("@title:dialog", "Remove Messages"),
|
||||
placeholder: i18nc("@info:placeholder", "Reason for removing this user's recent messages"),
|
||||
actionText: i18nc("@action:button 'Remove' as in 'Remove these messages'", "Remove"),
|
||||
icon: "delete"
|
||||
}, {
|
||||
title: i18nc("@title", "Remove Messages"),
|
||||
width: Kirigami.Units.gridUnit * 25
|
||||
});
|
||||
dialog.accepted.connect(reason => {
|
||||
root.room.deleteMessagesByUser(root.user.id, reason);
|
||||
});
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ RowLayout {
|
||||
|
||||
text: i18n("Edit this account")
|
||||
source: mediaId ? root.connection.makeMediaUrl("mxc://" + mediaId) : ""
|
||||
name: root.connection.localUser.displayName
|
||||
|
||||
activeFocusOnTab: true
|
||||
|
||||
|
||||
@@ -375,7 +375,7 @@ void RoomManager::knockRoom(NeoChatConnection *account, const QString &roomAlias
|
||||
account,
|
||||
&NeoChatConnection::newRoom,
|
||||
this,
|
||||
[this](Quotient::Room *room) {
|
||||
[](Quotient::Room *room) {
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Info, i18n("You requested to join '%1'", room -> name()));
|
||||
},
|
||||
Qt::SingleShotConnection);
|
||||
|
||||
@@ -217,12 +217,12 @@ FormCard.FormCardPage {
|
||||
visible: colorSchemeDelegate.visible
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: colorSchemeDelegate
|
||||
visible: item !== null
|
||||
sourceComponent: Qt.createComponent('org.kde.neochat.settings', 'ColorScheme')
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
// Loader {
|
||||
// id: colorSchemeDelegate
|
||||
// visible: item !== null
|
||||
// sourceComponent: Qt.createComponent('org.kde.neochat.settings', 'ColorScheme')
|
||||
// Layout.fillWidth: true
|
||||
// }
|
||||
}
|
||||
|
||||
FormCard.FormCard {
|
||||
|
||||
@@ -15,10 +15,6 @@ FormCard.FormComboBoxDelegate {
|
||||
textRole: "display"
|
||||
valueRole: "display"
|
||||
model: ColorSchemer.model
|
||||
Component.onCompleted: currentIndex = ColorSchemer.indexForScheme(NeoChatConfig.colorScheme)
|
||||
onCurrentValueChanged: {
|
||||
ColorSchemer.apply(currentIndex);
|
||||
NeoChatConfig.colorScheme = ColorSchemer.nameForIndex(currentIndex);
|
||||
NeoChatConfig.save();
|
||||
}
|
||||
Component.onCompleted: currentIndex = ColorSchemer.indexForCurrentScheme()
|
||||
onCurrentValueChanged: ColorSchemer.apply(currentIndex);
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ FormCard.FormCardPage {
|
||||
id: minimizeDelegate
|
||||
text: i18n("Minimize to system tray on startup")
|
||||
checked: NeoChatConfig.minimizeToSystemTrayOnStartup
|
||||
visible: Controller.supportSystemTray && !Kirigami.Settings.isMobile
|
||||
visible: Controller.supportSystemTray && !Kirigami.Settings.isMobile && NeoChatConfig.systemTray
|
||||
enabled: NeoChatConfig.systemTray && !NeoChatConfig.isMinimizeToSystemTrayOnStartupImmutable
|
||||
onToggled: {
|
||||
NeoChatConfig.minimizeToSystemTrayOnStartup = checked;
|
||||
@@ -56,6 +56,7 @@ FormCard.FormCardPage {
|
||||
FormCard.FormDelegateSeparator {
|
||||
above: minimizeDelegate
|
||||
below: automaticallyDelegate
|
||||
visible: minimizeDelegate.visible
|
||||
}
|
||||
|
||||
FormCard.FormCheckDelegate {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard as FormCard
|
||||
@@ -15,7 +16,7 @@ FormCard.FormCardPage {
|
||||
|
||||
required property NeoChatConnection connection
|
||||
|
||||
title: i18nc("@title", "Security")
|
||||
title: i18nc("@title", "Security & Safety")
|
||||
|
||||
header: KirigamiComponents.Banner {
|
||||
id: banner
|
||||
@@ -24,13 +25,39 @@ FormCard.FormCardPage {
|
||||
type: Kirigami.MessageType.Error
|
||||
}
|
||||
|
||||
FormCard.FormHeader {
|
||||
title: i18nc("@title:group", "Invitations")
|
||||
}
|
||||
FormCard.FormCard {
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
FormCard.FormButtonDelegate {
|
||||
id: ignoredUsersDelegate
|
||||
text: i18nc("@action:button", "Ignored Users")
|
||||
icon.name: "im-invisible-user"
|
||||
onClicked: root.QQC2.ApplicationWindow.window.pageStack.pushDialogLayer(ignoredUsersDialogComponent, {}, {
|
||||
title: i18nc("@title:window", "Ignored Users")
|
||||
});
|
||||
}
|
||||
FormCard.FormDelegateSeparator {
|
||||
above: ignoredUsersDelegate
|
||||
below: hideImagesDelegate
|
||||
}
|
||||
FormCard.FormCheckDelegate {
|
||||
id: hideImagesDelegate
|
||||
text: i18nc("@label:checkbox", "Hide images and videos by default")
|
||||
description: i18nc("@info", "When this option is enabled, images and videos are only shown after a button is clicked.")
|
||||
checked: NeoChatConfig.hideImages
|
||||
enabled: !NeoChatConfig.isHideImagesImmutable
|
||||
onToggled: {
|
||||
NeoChatConfig.hideImages = checked;
|
||||
NeoChatConfig.save();
|
||||
}
|
||||
}
|
||||
FormCard.FormDelegateSeparator {
|
||||
above: hideImagesDelegate
|
||||
below: rejectInvitationsDelegate
|
||||
}
|
||||
FormCard.FormCheckDelegate {
|
||||
id: rejectInvitationsDelegate
|
||||
text: i18nc("@option:check", "Reject invitations from unknown users")
|
||||
description: connection.canCheckMutualRooms ? i18n("If enabled, NeoChat will reject invitations from users you don't share a room with.") : i18n("Your server does not support this setting.")
|
||||
description: connection.canCheckMutualRooms ? i18nc("@info", "If enabled, NeoChat will reject invitations from users you don't share a room with.") : i18nc("@info", "Your server does not support this setting.")
|
||||
checked: NeoChatConfig.rejectUnknownInvites
|
||||
enabled: !NeoChatConfig.isRejectUnknownInvitesImmutable && connection.canCheckMutualRooms
|
||||
onToggled: {
|
||||
@@ -40,42 +67,15 @@ FormCard.FormCardPage {
|
||||
}
|
||||
}
|
||||
FormCard.FormHeader {
|
||||
title: i18nc("@title:group", "Ignored Users")
|
||||
}
|
||||
FormCard.FormCard {
|
||||
FormCard.FormButtonDelegate {
|
||||
text: i18nc("@action:button", "Manage ignored users")
|
||||
onClicked: root.QQC2.ApplicationWindow.window.pageStack.pushDialogLayer(ignoredUsersDialogComponent, {}, {
|
||||
title: i18nc("@title:window", "Ignored Users")
|
||||
});
|
||||
}
|
||||
}
|
||||
FormCard.FormHeader {
|
||||
title: i18nc("@title", "Keys")
|
||||
}
|
||||
FormCard.FormCard {
|
||||
FormCard.FormTextDelegate {
|
||||
text: connection.deviceKey
|
||||
description: i18n("Device key")
|
||||
}
|
||||
FormCard.FormTextDelegate {
|
||||
text: connection.encryptionKey
|
||||
description: i18n("Encryption key")
|
||||
}
|
||||
FormCard.FormTextDelegate {
|
||||
text: connection.deviceId
|
||||
description: i18n("Device id")
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormHeader {
|
||||
title: i18nc("@title", "Encryption")
|
||||
visible: Controller.csSupported
|
||||
title: i18nc("@title", "Encryption Keys")
|
||||
}
|
||||
FormCard.FormCard {
|
||||
visible: Controller.csSupported
|
||||
FormCard.FormButtonDelegate {
|
||||
text: i18nc("@action:button", "Import Encryption Keys")
|
||||
id: importKeysDelegate
|
||||
text: i18nc("@action:button", "Import Keys")
|
||||
description: i18nc("@info", "Import encryption keys from a backup.")
|
||||
icon.name: "document-import"
|
||||
onClicked: {
|
||||
let dialog = root.QQC2.ApplicationWindow.window.pageStack.pushDialogLayer(Qt.createComponent("org.kde.neochat.settings", "ImportKeysDialog"), {
|
||||
@@ -91,8 +91,14 @@ FormCard.FormCardPage {
|
||||
banner.visible = false;
|
||||
}
|
||||
}
|
||||
FormCard.FormDelegateSeparator {
|
||||
above: importKeysDelegate
|
||||
below: exportKeysDelegate
|
||||
}
|
||||
FormCard.FormButtonDelegate {
|
||||
text: i18nc("@action:button", "Export Encryption Keys")
|
||||
id: exportKeysDelegate
|
||||
text: i18nc("@action:button", "Export Keys")
|
||||
description: i18nc("@info", "Export this device's encryption keys.")
|
||||
icon.name: "document-export"
|
||||
onClicked: {
|
||||
root.QQC2.ApplicationWindow.window.pageStack.pushDialogLayer(Qt.createComponent("org.kde.neochat.settings", "ExportKeysDialog"), {
|
||||
|
||||
@@ -44,10 +44,11 @@ KirigamiSettings.ConfigurationView {
|
||||
connection: root.connection
|
||||
};
|
||||
}
|
||||
visible: root.connection !== null
|
||||
},
|
||||
KirigamiSettings.ConfigurationModule {
|
||||
moduleId: "security"
|
||||
text: i18n("Security")
|
||||
text: i18nc("@title", "Security & Safety")
|
||||
icon.name: "preferences-security"
|
||||
page: () => Qt.createComponent("org.kde.neochat.settings", "NeoChatSecurityPage")
|
||||
initialProperties: () => {
|
||||
@@ -55,12 +56,14 @@ KirigamiSettings.ConfigurationView {
|
||||
connection: root.connection
|
||||
};
|
||||
}
|
||||
visible: root.connection !== null
|
||||
},
|
||||
KirigamiSettings.ConfigurationModule {
|
||||
moduleId: "accounts"
|
||||
text: i18n("Accounts")
|
||||
icon.name: "preferences-system-users"
|
||||
page: () => Qt.createComponent("org.kde.neochat.settings", "AccountsPage")
|
||||
visible: root.connection !== null
|
||||
},
|
||||
KirigamiSettings.ConfigurationModule {
|
||||
moduleId: "emoticons"
|
||||
@@ -72,6 +75,7 @@ KirigamiSettings.ConfigurationView {
|
||||
connection: root.connection
|
||||
};
|
||||
}
|
||||
visible: root.connection !== null
|
||||
},
|
||||
KirigamiSettings.SpellcheckingConfigurationModule {},
|
||||
KirigamiSettings.ConfigurationModule {
|
||||
@@ -90,6 +94,7 @@ KirigamiSettings.ConfigurationView {
|
||||
connection: root.connection
|
||||
};
|
||||
}
|
||||
visible: root.connection !== null
|
||||
},
|
||||
KirigamiSettings.ConfigurationModule {
|
||||
moduleId: "aboutNeochat"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user