Compare commits

..

119 Commits

Author SHA1 Message Date
l10n daemon script
c5a4b2a50a GIT_SILENT Sync po/docbooks with svn 2025-08-07 21:08:05 +00:00
l10n daemon script
17fdad3afd GIT_SILENT made messages (after extraction) 2025-08-07 20:25:41 +00:00
l10n daemon script
64bc2691cb GIT_SILENT Sync po/docbooks with svn 2025-08-07 03:23:41 +00:00
l10n daemon script
c7df34a9c8 SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2025-08-07 03:19:05 +00:00
l10n daemon script
1525c74b10 GIT_SILENT made messages (after extraction) 2025-08-07 02:40:41 +00:00
l10n daemon script
58b85622c5 GIT_SILENT Sync po/docbooks with svn 2025-08-06 03:35:41 +00:00
l10n daemon script
796470d0e0 GIT_SILENT Sync po/docbooks with svn 2025-08-05 03:55:46 +00:00
l10n daemon script
3a43d99575 GIT_SILENT Sync po/docbooks with svn 2025-08-04 03:40:01 +00:00
l10n daemon script
a62798ef1e GIT_SILENT Sync po/docbooks with svn 2025-08-03 03:19:30 +00:00
l10n daemon script
349d0c5f5f GIT_SILENT Sync po/docbooks with svn 2025-08-02 03:15:57 +00:00
Tobias Fella
c917fc0166 Fix account switching on logout
(cherry picked from commit 501f14fead)
2025-08-01 12:28:35 +02:00
Tobias Fella
a4f2d8fca1 Fix loading
(cherry picked from commit d14466451d)
2025-08-01 12:13:38 +02:00
Tobias Fella
fd18f88adf Fix common crash during login
(cherry picked from commit 7742c6d4b0)
2025-08-01 11:28:43 +02:00
l10n daemon script
546694b08e GIT_SILENT Sync po/docbooks with svn 2025-08-01 03:27:30 +00:00
Heiko Becker
699026fc2f GIT_SILENT Update Appstream for new release 2025-08-01 00:54:04 +02:00
Heiko Becker
c61c2d7437 GIT_SILENT Upgrade release service version to 25.08.0. 2025-07-31 23:43:12 +02:00
l10n daemon script
56babbc1c5 GIT_SILENT Sync po/docbooks with svn 2025-07-31 03:14:23 +00:00
Joshua Goins
74b3e703c1 Replace duplicate beginResetModel with endResetModel
The initialize method was calling beginResetModel twice without
a corresponding endResetModel call. This could cause model state
inconsistencies.


(cherry picked from commit 4e0b295f66)

Co-authored-by: Wang Yu <wangyu@uniontech.com>
2025-07-30 21:13:01 -04:00
l10n daemon script
f620221113 GIT_SILENT Sync po/docbooks with svn 2025-07-30 03:15:18 +00:00
Tobias Fella
896c001430 Fix test
(cherry picked from commit 24e43d063a)
2025-07-29 17:04:21 +02:00
l10n daemon script
defee77c96 GIT_SILENT Sync po/docbooks with svn 2025-07-29 03:17:38 +00:00
l10n daemon script
4328ab8e89 GIT_SILENT Sync po/docbooks with svn 2025-07-28 03:13:37 +00:00
Tobias Fella
54b081abba Prepare for new RoomId format
See MSC4291

(cherry picked from commit edf5d55da4)
2025-07-27 21:54:13 +02:00
l10n daemon script
29686608e1 GIT_SILENT Sync po/docbooks with svn 2025-07-25 03:20:27 +00:00
Heiko Becker
b720ecf29d GIT_SILENT Upgrade release service version to 25.07.90. 2025-07-23 22:24:21 +02:00
l10n daemon script
fc859d679a GIT_SILENT Sync po/docbooks with svn 2025-07-23 03:18:09 +00:00
l10n daemon script
3595ad9293 GIT_SILENT Sync po/docbooks with svn 2025-07-22 03:21:18 +00:00
l10n daemon script
73f8ebc54e GIT_SILENT Sync po/docbooks with svn 2025-07-21 03:18:12 +00:00
l10n daemon script
19cf534acd GIT_SILENT Sync po/docbooks with svn 2025-07-17 03:14:25 +00:00
l10n daemon script
9b86088e26 GIT_SILENT Sync po/docbooks with svn 2025-07-16 04:06:03 +00:00
l10n daemon script
a93117fcd6 GIT_SILENT Sync po/docbooks with svn 2025-07-15 03:24:36 +00:00
l10n daemon script
ee20c90498 GIT_SILENT Sync po/docbooks with svn 2025-07-14 03:37:26 +00:00
l10n daemon script
860a2267d5 GIT_SILENT Sync po/docbooks with svn 2025-07-13 03:15:50 +00:00
l10n daemon script
cb9b2648ca GIT_SILENT Sync po/docbooks with svn 2025-07-12 03:21:34 +00:00
l10n daemon script
1e798b6c15 GIT_SILENT Sync po/docbooks with svn 2025-07-11 03:17:16 +00:00
l10n daemon script
124ffba5e0 GIT_SILENT Sync po/docbooks with svn 2025-07-10 03:16:20 +00:00
l10n daemon script
3aaaa610df GIT_SILENT Sync po/docbooks with svn 2025-07-09 03:22:43 +00:00
l10n daemon script
18e883834c GIT_SILENT Sync po/docbooks with svn 2025-07-08 03:24:01 +00:00
l10n daemon script
6acbd2dffd GIT_SILENT Sync po/docbooks with svn 2025-07-07 03:14:00 +00:00
l10n daemon script
dc5c27aa2d GIT_SILENT Sync po/docbooks with svn 2025-07-06 03:22:47 +00:00
Albert Astals Cid
26774bbe56 GIT_SILENT Upgrade release service version to 25.07.80. 2025-07-05 11:30:42 +02:00
l10n daemon script
87213903b1 GIT_SILENT Sync po/docbooks with svn 2025-07-05 01:39:27 +00:00
James Graham
3d9d211d25 Allow the condition for when messages are automatically marked as read to be configurable.
Title this adds a number of options for when messages should be automatically marked as read for the user to choose from.

![image](/uploads/cef95f8c6c77bfdcabb7a8a309bc1fd2/image.png){width=480 height=262}
2025-07-04 14:36:36 +01:00
James Graham
d5d291396d MessageContentModel improvements
Remove the hack for DelegateChooser and minimise the amount of updates when data changes
2025-07-04 10:28:50 +01:00
l10n daemon script
d7cd38b5e5 GIT_SILENT Sync po/docbooks with svn 2025-07-04 01:39:05 +00:00
l10n daemon script
1a25cfc0e3 GIT_SILENT Sync po/docbooks with svn 2025-07-03 07:51:46 +00:00
l10n daemon script
8d9669e033 GIT_SILENT Sync po/docbooks with svn 2025-07-02 11:07:52 +00:00
l10n daemon script
690d2e2b12 GIT_SILENT Sync po/docbooks with svn 2025-07-02 08:01:01 +00:00
l10n daemon script
2208b1b2b3 GIT_SILENT Sync po/docbooks with svn 2025-07-02 01:40:33 +00:00
Joshua Goins
67da272ccd Add feature flag for the new calls feature
This will be experimental for some time, so we need a way to turn it
on/off for future stable builds.
2025-07-01 14:52:46 -04:00
Joshua Goins
171930fb1d Show a message when failing to download a file
And optionally show an error message, if libQuotient gets one. This
helps in certain rare situations where you just *Cannot* download
something. Before, NeoChat would just reset the image or video without
telling you anything but now there's a nice big error message telling
you what's wrong.
2025-07-01 06:53:59 -04:00
l10n daemon script
2bd59fa16f GIT_SILENT Sync po/docbooks with svn 2025-07-01 01:40:14 +00:00
Joshua Goins
d0d0384bdb Improve the look of loading videos
The video thumbnail is no longer hidden while loading. This makes it
look less buggy (like NeoChat forgot what the thumbnail was) while
loading a large video. To help with contrast, a slight background tint
is added.
2025-06-30 17:21:40 -04:00
James Graham
680573de61 Make sure that thread replies aren't hidden when threads are off 2025-06-30 19:34:27 +01:00
Heiko Becker
ba7f866cf5 GIT_SILENT Update Appstream for new release
(cherry picked from commit 7e87d415ae)
2025-06-30 18:57:23 +02:00
Tobias Fella
57a95ae972 Show nicer text for call member events 2025-06-30 17:17:47 +02:00
Marco Martin
de97275a38 Use new Kirigami builtin column resize handle
Use the new Kirigami.ColumnView.interactiveResizeEnabled attached property
which allows to have a built in resize handle for ColumnView pages
removing the need for a custom handle with resize logic

depends from https://invent.kde.org/frameworks/kirigami/-/merge_requests/1795
2025-06-30 11:25:28 +02:00
l10n daemon script
d08181f56d GIT_SILENT Sync po/docbooks with svn 2025-06-30 01:41:18 +00:00
James Graham
f6e8491bf1 Split message content into its own module
This is laying some groundwork for the rich text chatbar.
2025-06-29 12:43:48 +01:00
l10n daemon script
a1447ebd6f GIT_SILENT Sync po/docbooks with svn 2025-06-29 01:44:03 +00:00
l10n daemon script
3b67e4deaf GIT_SILENT Sync po/docbooks with svn 2025-06-28 01:39:59 +00:00
James Graham
e55afd0402 Move ImageEditorPage to Chatbar module 2025-06-27 16:25:57 +01:00
James Graham
7c0710d445 ActionToolbar is no longer needed now there is only 1 button 2025-06-27 16:08:30 +01:00
James Graham
61f9cd41f7 Initial Account
Restore the functionality where clicking on "edit this account" from the AccountMenu opens setting to the account rather than pushing as its own window
2025-06-27 16:08:14 +01:00
l10n daemon script
6c7a7a7be5 GIT_SILENT Sync po/docbooks with svn 2025-06-27 01:41:18 +00:00
James Graham
63125d97c3 Fix hiding down button on timeline when at the bottom 2025-06-26 14:38:16 +01:00
James Graham
cb4e7d4943 Fix room list header when collapsed 2025-06-26 14:34:18 +01:00
l10n daemon script
b9d5ed699e GIT_SILENT Sync po/docbooks with svn 2025-06-26 01:41:04 +00:00
l10n daemon script
75fe5c8970 GIT_SILENT Sync po/docbooks with svn 2025-06-25 01:42:54 +00:00
Aleix Pol
91da2d01b7 UserInfo: Move the switch user action to the menu
It doesn't clutter the main UI.
It allows us to see the keyboard shortcut.
2025-06-23 23:19:05 +00:00
l10n daemon script
4bade72ce4 GIT_SILENT Sync po/docbooks with svn 2025-06-23 01:39:47 +00:00
l10n daemon script
9c05e2feed GIT_SILENT Sync po/docbooks with svn 2025-06-22 01:45:29 +00:00
l10n daemon script
ba94098411 GIT_SILENT Sync po/docbooks with svn 2025-06-21 01:41:18 +00:00
James Graham
43c691160e Update the RoomTreeSection to use standard Kirigami.ListSectionHeader 2025-06-20 07:42:43 +01:00
James Graham
e2daa091e8 Switch section delegate for standard Kirigami.ListSectionHeader
Switch section delegate for standard Kirigami.ListSectionHeader, SectionDelegate is no longer required so removed.
2025-06-20 07:42:14 +01:00
l10n daemon script
f026414b1a GIT_SILENT Sync po/docbooks with svn 2025-06-20 01:39:04 +00:00
l10n daemon script
fd640a4bd0 GIT_SILENT Sync po/docbooks with svn 2025-06-19 01:39:06 +00:00
l10n daemon script
dd433d7f99 GIT_SILENT Sync po/docbooks with svn 2025-06-18 01:39:30 +00:00
James Graham
3183e00acc Fix Use After Free MessageDelegateBase
Make sure that a `MessageDelegateBase` is not used after free by a `MessageObjectIncubator` callback by tracking them and cleaning them up on deletion of a `MessageDelegateBase`
2025-06-17 17:19:38 +01:00
James Graham
1860de12ea RoomPage cleanup
This does some further cleanup of RoomPage, mostly removing all the vestigial bits from when we could have multiple windows. But also stuff is moved to TimelineView where possible.

The loading placeholder is removed as TimelineModel already has this built in.

TimelineView now gets room from it's model. This is to ensure we're always using the same room as it which may not be true momentarily when RoomManager.currentRoom changes as the model does it's own reset sequence. This will prevent some race conditions in future (and which I already hit creating other MRs)
2025-06-17 16:41:38 +01:00
l10n daemon script
277f1ab252 GIT_SILENT Sync po/docbooks with svn 2025-06-17 01:40:15 +00:00
Thiago Sueto
324b332fa6 Add confirmation prompt to reset all configuration
Resetting all configuration is destructive, it should require a confirmation prompt instead of triggering immediately.
2025-06-16 18:00:06 +00:00
James Graham
33d29f6b02 Fix jump to last message button trigger, the list view is no longer root. 2025-06-16 18:04:25 +01:00
l10n daemon script
419aed6375 GIT_SILENT Sync po/docbooks with svn 2025-06-16 01:39:23 +00:00
Aleix Pol
7bb26ac3be main: Add a replace option
Much like many other unique KDE components, add --replace so the running
instance can be replaced by the new one. Useful for development.
2025-06-15 23:07:40 +02:00
Aleix Pol
7654333ec1 ImageComponent: Can't make a qreal undefined
Using -1 because MediaSizeHelper is already handling negative numbers as
such.
2025-06-15 23:07:40 +02:00
Aleix Pol
d3a2da391d flatpak: Use the Qt 6.9 runtime
It's all the fresh!!
2025-06-15 23:07:40 +02:00
Aleix Pol
f19f59c37f EmojiPicker: Fix delegate for stickers
There's no avatarUrl in ImagePacksModel, it's just url.
2025-06-15 23:07:40 +02:00
Aleix Pol
94e53e14a3 UserInfo: Use "Account" naming consistently
We use "Account" in the settings so we better use the same concept
across the UI.
2025-06-15 23:06:15 +02:00
Manuel Alcaraz Zambrano
d2b3788872 Remove QCoro from Flatpak
QCoro is already available on the runtime.
2025-06-15 12:07:24 +02:00
l10n daemon script
8095062db2 GIT_SILENT Sync po/docbooks with svn 2025-06-15 01:40:14 +00:00
Tobias Fella
89beaa9316 Remove stray warning 2025-06-15 00:29:34 +02:00
Carl Schwan
dfaffd043e Cleanup craft config
Don't build master version of some modules
2025-06-14 12:13:31 +00:00
Tobias Fella
a8c200222f Don't parent PollHandler to room
PollHandlers are stored in a QCache, which takes over ownership of the object. At the same time, PollHandler currently relies on its parent being the room.
Somehow, this didn't explode entirely, but only leads to minor problems like crashes on shutdown.
2025-06-14 12:13:16 +00:00
Tobias Fella
598a1c28ac Add button for hiding images and videos 2025-06-14 12:12:01 +00:00
Tobias Fella
b972703f34 Refactor and fix opening file-based content
The relevant fix is checking whether the the event is file-*based* (i.e., image, video, audio, file), not whether it *is* a file
2025-06-14 12:07:56 +00:00
Tobias Fella
d30fdc67c6 Remove file logging
This hasn't proven to be as useful as i had hoped:
- My arcane logic for determining logging categories is apparently broken
- It won't work with the logging by the new crypto sdk
- I never actually ended up looking at my own logs, or anyone else's
- It seems to cause crashes
2025-06-14 11:57:23 +00:00
James Graham
6a5a2e6144 Refactor TimelineView
Refactor TimelineView to make it more reliable and prepare for read marker choice. This is done by creating signalling from the mode when reset which can be used to move the scrollbar to the newest meassage.

Some of the spaghetti is also removed so there is no need for ChatBar and TimelineView to talk directly.

The code to mark messages as read if they are all visible after 10s has been removed infour of just marking as read on entry if all are visible. This is temporary until a follow up providing user options is finished (although it will be one of the options)
2025-06-14 12:16:39 +01:00
Kai Uwe Broulik
235143528c MaximizeComponent: Fix download location
No idea where "saveFolder" was supposed to be coming from but this
makes it use the last location or default to downloads, consistent
with the other save dialogs.
2025-06-14 10:08:12 +02:00
l10n daemon script
33ca2b8d09 GIT_SILENT Sync po/docbooks with svn 2025-06-14 01:40:03 +00:00
Aleix Pol
50176cfafb ImageComponent: Do not set undefined to int
An int property cannot be undefined, make it var.
2025-06-13 15:46:30 +00:00
Aleix Pol
a67fb36a84 SpaceHierarchyDelegate: Fix opening rooms
RoomManager is in org.kde.neochat and needs importing
2025-06-13 15:46:30 +00:00
Aleix Pol
aea9984187 DevicesModel: Do not connect until the value is initialised
When the DevicesModel is open, we get a nullptr instance. I guess it's
obvious that it's null because it hasn't been set yet. Move the connect
to the setter.
2025-06-13 15:46:30 +00:00
Freya Lupen
7a6c234b40 macOS: Set app bundle properties
This fixes the app's name being blank in the system.
2025-06-13 08:25:45 +00:00
Freya Lupen
4cdaa513d3 Use Breeze style on macOS, as on other platforms 2025-06-13 08:25:45 +00:00
l10n daemon script
aaf655ea5b GIT_SILENT Sync po/docbooks with svn 2025-06-13 01:39:47 +00:00
Tobias Fella
207c824ec7 Use QML safe member for invitations
Has the sideeffect of making sure that the avatar colors are the same between the invitation view and the sidebar
2025-06-12 12:49:01 +00:00
Tobias Fella
29abe0bacb Use ImageComponent for replies
This automatically gives us the image hiding safety feature

BUG: 503442
2025-06-12 09:04:52 +00:00
Tobias Fella
d14eda2ca0 Hide avatar of inviting user when images are hidden by default
While this reuses the setting for a slightly different purpose, in practice, these safety feature really belong together and it makes sense to have them both under a single option.
In the future, we might want to rephrase the options description
2025-06-11 22:42:49 +02:00
l10n daemon script
0425cf41d0 GIT_SILENT Sync po/docbooks with svn 2025-06-11 01:45:08 +00:00
l10n daemon script
4ff014f288 GIT_SILENT Sync po/docbooks with svn 2025-06-09 01:42:48 +00:00
l10n daemon script
2e24500866 GIT_SILENT Sync po/docbooks with svn 2025-06-08 01:41:02 +00:00
Joshua Goins
f13a256150 Refactor receiveRichPlainUrl text handler test to be data-based
I was originally going to add another test case to this, turns out I
didn't need to. Might as well ship this anyway, as it will make it
trivial to add more in the future.
2025-06-07 07:30:28 -04:00
l10n daemon script
c958e8cba9 GIT_SILENT Sync po/docbooks with svn 2025-06-06 01:38:09 +00:00
Joshua Goins
7668da68d3 Allow accepting reason dialogs with Ctrl+Enter
The original suggestion was the Enter key, but this won't work well as
reasons can take multiple lines. I also made sure the reason control was
focused by default, and that the "Cancel" button has an icon.

BUG: 500990
2025-06-05 12:03:37 +00:00
Heiko Becker
c478a0c0fc GIT_SILENT Update Appstream for new release
(cherry picked from commit 11ebe5316f)
2025-06-02 23:46:47 +02:00
l10n daemon script
fe60a08817 GIT_SILENT Sync po/docbooks with svn 2025-06-02 01:39:15 +00:00
James Graham
dec5369a8f Actually position the view at the end
It turns out that for whatever reason ListView.posiitionViewAtEnd() ignores any whitespace. This means that when we use the function it goes to the last delegate. This is no good as we have some padding at the bottom to make space for the typing indicator.

So the fix for this is stupid and involves adding a "spacer" delegate to the timeline beginning model which is completely invisible but qml see as a delegate so we can both leave the space and properly position the view at the end.

BUG: 501075
2025-06-01 10:37:30 +01:00
Joshua Goins
5ec0b9393e Fix showPassiveNotification call in Account Editor
I hate this thing, I think the standalone account editor was fine, but
it didn't work when I went through account settings. Now that should be
fixed.
2025-06-01 04:51:18 +02:00
223 changed files with 36198 additions and 32571 deletions

View File

@@ -2,7 +2,5 @@
; SPDX-License-Identifier: CC0-1.0
[BlueprintSettings]
kde/frameworks/extra-cmake-modules.version=master
kde/unreleased/kirigami-addons.version=master
kde/applications/neochat.packageAppx=True
libs/qt.qtMajorVersion=6

View File

@@ -2,7 +2,7 @@
"id": "org.kde.neochat",
"branch": "master",
"runtime": "org.kde.Platform",
"runtime-version": "6.8",
"runtime-version": "6.9",
"sdk": "org.kde.Sdk",
"command": "neochat",
"tags": [
@@ -149,27 +149,6 @@
],
"builddir": true
},
{
"name": "qcoro",
"buildsystem": "cmake-ninja",
"config-opts": [
"-DQCORO_BUILD_EXAMPLES=OFF",
"-DBUILD_TESTING=OFF"
],
"sources": [
{
"type": "archive",
"url": "https://github.com/danvratil/qcoro/archive/refs/tags/v0.11.0.tar.gz",
"sha256": "9942c5b4c533192f6c5954dc6d10178b3829075e6a621b67df73f0a4b74d8297",
"x-checker-data": {
"type": "anitya",
"project-id": 236236,
"stable-only": true,
"url-template": "https://github.com/danvratil/qcoro/archive/refs/tags/v$version.tar.gz"
}
}
]
},
{
"name": "kunifiedpush",
"buildsystem": "cmake-ninja",

View File

@@ -8,8 +8,8 @@ cmake_minimum_required(VERSION 3.16)
# KDE Applications version, managed by release script.
set(RELEASE_SERVICE_VERSION_MAJOR "25")
set(RELEASE_SERVICE_VERSION_MINOR "07")
set(RELEASE_SERVICE_VERSION_MICRO "70")
set(RELEASE_SERVICE_VERSION_MINOR "08")
set(RELEASE_SERVICE_VERSION_MICRO "0")
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})

View File

@@ -130,7 +130,8 @@ void EventHandlerTest::timeString()
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)).toLocalTime().time(), QLocale::ShortFormat));
QCOMPARE(EventHandler::timeString(room, event, true),
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)).toLocalTime().date(), QLocale::ShortFormat));
QCOMPARE(EventHandler::timeString(room, event, u"hh:mm"_s), QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)).toString(u"hh:mm"_s));
QCOMPARE(EventHandler::timeString(room, event, u"hh:mm"_s),
QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::LocalTime)).toString(u"hh:mm"_s));
const auto txID = room->postJson("m.room.message"_L1, event->fullJson());
QCOMPARE(room->pendingEvents().size(), 1);

View File

@@ -61,6 +61,7 @@ private Q_SLOTS:
void receiveRichStrikethrough();
void receiveRichtextIn();
void receiveRichMxcUrl();
void receiveRichPlainUrl_data();
void receiveRichPlainUrl();
void receiveRichEdited_data();
void receiveRichEdited();
@@ -450,6 +451,32 @@ void TextHandlerTest::receiveRichMxcUrl()
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText, room, room->messageEvents().at(0).get()), testOutputString);
}
void TextHandlerTest::receiveRichPlainUrl_data()
{
QTest::addColumn<QString>("input");
QTest::addColumn<QString>("output");
// This is an actual link that caused trouble which is why it's so long. Keeping
// so we can confirm consistent behaviour for complex urls.
QTest::addRow("link 1")
<< u"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im <a href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im\">Link already rich</a>"_s
<< u"<a href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im\">https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im</a> <a href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im\">Link already rich</a>"_s;
// Another real case. The linkification wasn't handling it when a single link
// contains what looks like and email. It was broken into 3 but needs to
// be just single link.
QTest::addRow("link 2")
<< u"https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/"_s
<< u"<a href=\"https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/\">https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/</a>"_s;
QTest::addRow("email") << uR"(email@example.com <a href="mailto:email@example.com">Link already rich</a>)"_s
<< uR"(<a href="mailto:email@example.com">email@example.com</a> <a href="mailto:email@example.com">Link already rich</a>)"_s;
QTest::addRow("mxid")
<< u"@user:kde.org <a href=\"https://matrix.to/#/@user:kde.org\">Link already rich</a>"_s
<< u"<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>"_s;
QTest::addRow("mxid with prefix") << u"a @user:kde.org b"_s << u"a <b><a href=\"https://matrix.to/#/@user:kde.org\">@user:kde.org</a></b> b"_s;
}
/**
* For when your rich input string has a plain text url left in.
*
@@ -458,46 +485,13 @@ void TextHandlerTest::receiveRichMxcUrl()
*/
void TextHandlerTest::receiveRichPlainUrl()
{
// This is an actual link that caused trouble which is why it's so long. Keeping
// so we can confirm consistent behaviour for complex urls.
const QString testInputStringLink1 =
u"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im <a href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im\">Link already rich</a>"_s;
const QString testOutputStringLink1 =
u"<a href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im\">https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im</a> <a href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im\">Link already rich</a>"_s;
// Another real case. The linkification wasn't handling it when a single link
// contains what looks like and email. It was been broken into 3 but needs to
// be just single link.
const QString testInputStringLink2 = u"https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/"_s;
const QString testOutputStringLink2 =
u"<a href=\"https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/\">https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/</a>"_s;
QString testInputStringEmail = uR"(email@example.com <a href="mailto:email@example.com">Link already rich</a>)"_s;
QString testOutputStringEmail = uR"(<a href="mailto:email@example.com">email@example.com</a> <a href="mailto:email@example.com">Link already rich</a>)"_s;
QString testInputStringMxId = u"@user:kde.org <a href=\"https://matrix.to/#/@user:kde.org\">Link already rich</a>"_s;
QString testOutputStringMxId =
u"<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>"_s;
QString testInputStringMxIdWithPrefix = u"a @user:kde.org b"_s;
QString testOutputStringMxIdWithPrefix = u"a <b><a href=\"https://matrix.to/#/@user:kde.org\">@user:kde.org</a></b> b"_s;
QFETCH(QString, input);
QFETCH(QString, output);
TextHandler testTextHandler;
testTextHandler.setData(testInputStringLink1);
testTextHandler.setData(input);
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringLink1);
testTextHandler.setData(testInputStringLink2);
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringLink2);
testTextHandler.setData(testInputStringEmail);
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringEmail);
testTextHandler.setData(testInputStringMxId);
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringMxId);
testTextHandler.setData(testInputStringMxIdWithPrefix);
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringMxIdWithPrefix);
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), output);
}
void TextHandlerTest::receiveRichEdited_data()

View File

@@ -57,7 +57,7 @@ void TimelineMessageModelTest::switchEmptyRoom()
auto firstRoom = new TestUtils::TestRoom(connection, u"#firstRoom:kde.org"_s);
auto secondRoom = new TestUtils::TestRoom(connection, u"#secondRoom:kde.org"_s);
QSignalSpy spy(model, SIGNAL(roomChanged()));
QSignalSpy spy(model, SIGNAL(roomChanged(NeoChatRoom *, NeoChatRoom *)));
QCOMPARE(model->room(), nullptr);
model->setRoom(firstRoom);
@@ -77,7 +77,7 @@ void TimelineMessageModelTest::switchSyncedRoom()
auto firstRoom = new TestUtils::TestRoom(connection, u"#firstRoom:kde.org"_s, u"test-messageventmodel-sync.json"_s);
auto secondRoom = new TestUtils::TestRoom(connection, u"#secondRoom:kde.org"_s, u"test-messageventmodel-sync.json"_s);
QSignalSpy spy(model, SIGNAL(roomChanged()));
QSignalSpy spy(model, SIGNAL(roomChanged(NeoChatRoom *, NeoChatRoom *)));
QCOMPARE(model->room(), nullptr);
model->setRoom(firstRoom);
@@ -208,7 +208,7 @@ void TimelineMessageModelTest::idToRow()
auto room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s, u"test-min-sync.json"_s);
model->setRoom(room);
QCOMPARE(model->eventIdToRow(u"$153456789:example.org"_s), 0);
QCOMPARE(model->indexforEventId(u"$153456789:example.org"_s).row(), 0);
}
void TimelineMessageModelTest::cleanup()

View File

@@ -75,6 +75,7 @@
<summary xml:lang="nl">Chat op Matrix</summary>
<summary xml:lang="nn">Prat med via Matrix</summary>
<summary xml:lang="pl">Rozmawiaj na Matriksie</summary>
<summary xml:lang="pt-BR">Bate-papo na Matrix</summary>
<summary xml:lang="ru">Общение в Matrix</summary>
<summary xml:lang="sa">Matrix इत्यत्र गपशपं कुर्वन्तु</summary>
<summary xml:lang="sl">Klepet na Matrixu</summary>
@@ -109,6 +110,7 @@
<p xml:lang="nl">NeoChat is een chat-toepassing die u het volledige voordeel van het Matrix-netwerk laat genieten. Het levert u op een veilige manier tekstberichten, video's en geluidsbestanden naar uw familie, collega's en vrienden te verzenden.</p>
<p xml:lang="nn">NeoChat er ein prateapp som lèt deg bruka all funksjonalitet i Matrix-nettverket. Du kan utveksla tekst, lyd og videoar med vennar, familie og kollegaar på ein trygg måte.</p>
<p xml:lang="pl">NoeChat to aplikacja do rozmów, która umożliwia wykorzystanie wszystkich możliwości Matriksa. Umożliwia wysyłanie wiadomości tekstowych, filmów i dźwięków w bezpieczny sposób do twojej rodziny, kolegów i przyjaciół.</p>
<p xml:lang="pt-BR">O NeoChat é um aplicativo de bate-papo que permite que você aproveite ao máximo a rede Matrix. Ele oferece uma maneira segura de enviar mensagens de texto, vídeos e arquivos de áudio para sua família, colegas e amigos.</p>
<p xml:lang="ru">NeoChat — приложение для общения, предоставляющее все преимущества сети Matrix. С его помощью можно безопасно отправлять текстовые сообщения, видеозаписи и звуковые файлы родственникам, коллегам и друзьям.</p>
<p xml:lang="sa">NeoChat इति एकं गपशप-अनुप्रयोगं यत् भवान् Matrix-जालस्य पूर्णं लाभं ग्रहीतुं शक्नोति । एतत् भवन्तं भवतः परिवाराय, सहकारिभ्यः, मित्रेभ्यः च पाठसन्देशान्, भिडियो, श्रव्यसञ्चिकाः च प्रेषयितुं सुरक्षितं मार्गं प्रदाति ।</p>
<p xml:lang="sl">NeoChat je aplikacija za klepet, ki vam omogoča, da v celoti izkoristite omrežje Matrix. Zagotavlja vam varen način za pošiljanje besedilnih sporočil, videoposnetkov in zvočnih datotek vaši družini, sodelavcem in prijateljem.</p>
@@ -142,6 +144,7 @@
<p xml:lang="nn">NeoChat har som mål å støtta all funksjonalitet i Matrix-spesifikasjonen. Førebels er alt i den gjeldande stabile spesifikasjonen støtta, med unntak av VoIP, trådar og nokre delar av ende-til-kryptering. Det finst òg andre småting som ikkje er støtta, sidan Matrix-spesifikasjon er i stadig endring, men målet er altså støtte for alt.</p>
<p xml:lang="pl">NeoChat w zamyśle ma być pełnowartościową aplikacją wg wytycznych Matriksa. Z tego powodu, wszystko, co jest obecnie w stabilnych wytycznych z pominięciem VoIP, wątków i niektórych części szyfrowania Użytkownik-do-Użytkownika są obecnie obsługiwane. Pominięto też kilka mniejszych rzeczy ze względu na ciągły rozwój wytycznych Matriksa, lecz celem nadal jest zapewnienie obsługi wszystkich wytycznych.</p>
<p xml:lang="pt">O NeoChat pretende ser uma aplicação completa para a especificação do Matrix. Como tal, tudo o que existe na especificação estável actual, com as notáveis excepções do VoIP, tópicos e alguns aspectos da Encriptação Ponto-a-Ponto, são suportados. Existem mais algumas omissões, devido ao facto que a norma do Matrix está em constante evolução, mas o objectivo continua a ser oferecer o suporte eventual para a norma por inteiro.</p>
<p xml:lang="pt-BR">O NeoChat pretende ser um aplicativo completo para a especificação Matrix. Dessa forma, tudo na especificação estável atual, com as notáveis exceções de VoIP, tópicos e alguns aspectos da criptografia de ponta a ponta, é suportado. Há algumas outras pequenas omissões devido ao fato de a especificação Matrix estar em constante evolução, mas o objetivo continua sendo fornecer suporte eventual para toda a especificação.</p>
<p xml:lang="ru">Целью создания NeoChat является полноценная реализация программы для спецификации Matrix. Как следствие, реализовано всё в текущей стабильной спецификации (за исключением голосовой интернет-связи, потоков и некоторых аспектов сквозного шифрования). Есть также несколько других незначительных пробелов, обусловленных постоянными изменениями спецификации Matrix. Тем не менее, стоит задача в итоге предоставить полную поддержку спецификации.</p>
<p xml:lang="sa">NeoChat इत्यस्य उद्देश्यं Matrix विनिर्देशस्य कृते पूर्णतया विशेषतायुक्तः अनुप्रयोगः भवितुम् अस्ति । यथा तथा वर्तमानस्थिरविनिर्देशे सर्वं VoIP इत्यस्य उल्लेखनीयअपवादैः सह, थ्रेड्स तथा च End-to-End Encryption इत्यस्य केचन पक्षाः समर्थिताः सन्ति । अन्ये कतिचन लघु लोपाः सन्ति यतोहि Matrix spec निरन्तरं विकसितः अस्ति परन्तु उद्देश्यं सम्पूर्ण spec कृते अन्ततः समर्थनं प्रदातुं अवशिष्टम् अस्ति</p>
<p xml:lang="sl">Neochat cilja, da bi bila popolna aplikacija po specifikaciji Matrixa. Kot takšna vsebuje vse v trenutni stabilni specifikaciji z pomembnimi izjemami pri VoIP, nitih in nekaterih vidikov šifriranja od konca do konca. Obstaja nekaj drugih manjših opustitev zaradi dejstva, da se specifikacija Matrix nenehno razvija, vendar cilj ostaja zagotoviti morebitno podporo celotni specifikaciji.</p>
@@ -175,6 +178,7 @@
<p xml:lang="nn">På grunn av måten Matrix-spesifikasjonen vert utvikla på, støttar NeoChat òg nokre uferdige funksjonar:</p>
<p xml:lang="pl">Ze względu na sposób rozwoju Matriksa, NeoChat obsługuje także kilka niestabilnych możliwości. Obecnie są to:</p>
<p xml:lang="pt">Devido à natureza do desenvolvimento da especificação do Matrix, o NeoChat também suporta diversas funcionalidades instáveis. De momento são:</p>
<p xml:lang="pt-BR">Devido à natureza do desenvolvimento da especificação Matrix, o NeoChat também suporta diversos recursos instáveis. Atualmente, são eles:</p>
<p xml:lang="ru">В силу природы разработки спецификации Matrix в NeoChat тоже предусмотрена поддержка многочисленных нестабильных возможностей. В текущей версии это следующие возможности:</p>
<p xml:lang="sa">Matrix विनिर्देशविकासस्य प्रकृतेः कारणात् NeoChat अपि अनेकानाम् अस्थिरविशेषतानां समर्थनं करोति । सम्प्रति एते सन्ति :</p>
<p xml:lang="sl">Zaradi narave razvoja specifikacije Matrixa NeoChat podpira tudi številne nestabilne zmožnosti. Trenutno so to:</p>
@@ -187,8 +191,8 @@
<ul>
<li>Polls - MSC3381</li>
<li xml:lang="ar">التصويت - MSC3381</li>
<li xml:lang="ca">Enquestes - MSC3381</li>
<li xml:lang="ca-valencia">Enquestes - MSC3381</li>
<li xml:lang="ca">Votacions - MSC3381</li>
<li xml:lang="ca-valencia">Votacions - MSC3381</li>
<li xml:lang="el">Δημοσκοπήσεις - MSC3381</li>
<li xml:lang="en-GB">Polls - MSC3381</li>
<li xml:lang="eo">Enketoj - MSC3381</li>
@@ -209,6 +213,7 @@
<li xml:lang="nn">Avstemmingar  MSC3381</li>
<li xml:lang="pl">Ankiety - MSC3381</li>
<li xml:lang="pt">Inquéritos - MSC3381</li>
<li xml:lang="pt-BR">Enquetes - MSC3381</li>
<li xml:lang="ru">Голосования — MSC3381</li>
<li xml:lang="sa">मतदान - MSC3381</li>
<li xml:lang="sl">Polls - MSC3381</li>
@@ -242,6 +247,7 @@
<li xml:lang="nn">Klistremerke-pakkar  MSC2545</li>
<li xml:lang="pl">Paczki naklejek - MSC2545</li>
<li xml:lang="pt">Pacotes de Autocolantes - MSC2545</li>
<li xml:lang="pt-BR">Pacotes de Stickers - MSC2545</li>
<li xml:lang="ru">Наборы стикеров — MSC2545</li>
<li xml:lang="sa">स्टिकर पैक - MSC2545</li>
<li xml:lang="sl">Sticker Packs - MSC2545</li>
@@ -275,6 +281,7 @@
<li xml:lang="nn">Posisjonshendingar  MSC3488</li>
<li xml:lang="pl">Wydarzenia w miejscach - MSC3488</li>
<li xml:lang="pt">Eventos com Localizações - MSC3488</li>
<li xml:lang="pt-BR">Localização de eventos - MSC3488</li>
<li xml:lang="ru">События местоположения — MSC3488</li>
<li xml:lang="sa">स्थान घटनाएँ - MSC3488</li>
<li xml:lang="sl">Location Events - MSC3488</li>
@@ -344,6 +351,7 @@
<caption xml:lang="nn">Hovudvising med romliste, pratevindauge og rominformasjon</caption>
<caption xml:lang="pl">Główny widok z wykazem pokojów, rozmowami i szczegółami pokojów</caption>
<caption xml:lang="pt">A área principal com a lista de salas e com informações sobre a conversa e a sala</caption>
<caption xml:lang="pt-BR">Visão principal com lista de salas, bate-papo e informações sobre as salas</caption>
<caption xml:lang="ru">Главное окно со списком комнат, чатом и информацией о комнате</caption>
<caption xml:lang="sa">कक्षसूची, गपशपः, कक्षसूचना च सह मुख्यदृश्यम्</caption>
<caption xml:lang="sl">Glavni pogled s seznamom sob, klepetom in informacijami o sobah</caption>
@@ -380,6 +388,7 @@
<caption xml:lang="nl">Ontdek nieuwe gemeenschappen met Matrix-ruimten</caption>
<caption xml:lang="nn">Oppdag nye fellesskap med Matrix Spaces</caption>
<caption xml:lang="pl">Odkrywaj nowe społeczności w Przestrzeniach Matriksa</caption>
<caption xml:lang="pt-BR">Descubra novas comunidades com os Espaços Matrix</caption>
<caption xml:lang="ru">Поиск новых сообществ с помощью Matrix Spaces</caption>
<caption xml:lang="sa">Matrix Spaces इत्यनेन सह नूतनानां समुदायानाम् अन्वेषणं कुर्वन्तु</caption>
<caption xml:lang="sl">Odkrijte nove skupnosti z Matrix Spaces</caption>
@@ -424,6 +433,7 @@
<caption xml:lang="nn">Hovudvising med romliste, pratevindauge og rominformasjon</caption>
<caption xml:lang="pl">Główny widok z wykazem pokojów, rozmowami i szczegółami pokojów</caption>
<caption xml:lang="pt">A área principal com a lista de salas e com informações sobre a conversa e a sala</caption>
<caption xml:lang="pt-BR">Visão principal com lista de salas, bate-papo e informações sobre as salas</caption>
<caption xml:lang="ru">Главное окно со списком комнат, чатом и информацией о комнате</caption>
<caption xml:lang="sa">कक्षसूची, गपशपः, कक्षसूचना च सह मुख्यदृश्यम्</caption>
<caption xml:lang="sl">Glavni pogled s seznamom sob, klepetom in informacijami o sobah</caption>
@@ -462,6 +472,7 @@
<caption xml:lang="nn">Innloggingsbilete</caption>
<caption xml:lang="pl">Ekran logowania</caption>
<caption xml:lang="pt">Ecrã de autenticação</caption>
<caption xml:lang="pt-BR">Tela de login</caption>
<caption xml:lang="ru">Окно входа</caption>
<caption xml:lang="sa">लॉगिन् स्क्रीन</caption>
<caption xml:lang="sl">Prijavni zaslon</caption>
@@ -477,6 +488,9 @@
<content_attribute id="social-chat">intense</content_attribute>
</content_rating>
<releases>
<release version="25.08.0" date="2025-08-14"/>
<release version="25.04.3" date="2025-07-03"/>
<release version="25.04.2" date="2025-06-05"/>
<release version="25.04.1" date="2025-05-08"/>
<release version="25.04.0" date="2025-04-17"/>
<release version="24.12.3" date="2025-03-06"/>

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

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

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

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

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

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

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

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

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

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

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,122 @@
<?xml version="1.0" ?>
<!DOCTYPE refentry PUBLIC "-//KDE//DTD DocBook XML V4.5-Based Variant V1.1//EN" "dtd/kdedbx45.dtd" [
<!ENTITY % Russian "INCLUDE">
]>
<!--
SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
SPDX-License-Identifier: CC-BY-SA-4.0
-->
<refentry lang="&language;">
<refentryinfo>
<title
>Руководство пользователя NeoChat</title>
<author
><firstname
>Carl</firstname
><surname
>Schwan</surname
> <contrib
>man-страница NeoChat.</contrib
> <email
>carl@carlschwan.eu</email
></author>
<date
>2022-11-01</date>
<releaseinfo
>22.09</releaseinfo>
<productname
>NeoChat</productname>
</refentryinfo>
<refmeta>
<refentrytitle>
<command
>neochat</command>
</refentrytitle>
<manvolnum
>1</manvolnum>
</refmeta>
<refnamediv>
<refname
>neochat</refname>
<refpurpose
>Клиент для взаимодействия с протоколом обмена сообщениями Matrix</refpurpose>
</refnamediv>
<!-- body begins here -->
<refsynopsisdiv id='synopsis'>
<cmdsynopsis
><command
>neochat</command
> <arg choice="opt"
><replaceable
>URI</replaceable
></arg
> </cmdsynopsis>
</refsynopsisdiv>
<refsect1 id="description">
<title
>Описание</title>
<para
><command
>neochat</command
> — приложение для настольных и мобильных устройств, позволяющее общаться в чатах с помощью протокола Matrix. </para>
</refsect1>
<refsect1 id="options"
><title
>Параметры</title>
<variablelist>
<varlistentry>
<term
><option
>URI</option
></term>
<listitem>
<para
>URI-адрес пользователя или комнаты в Matrix, например: matrix:u/user:example.org и matrix:r/root:example.org. NeoChat попытается открыть указанную комнату или беседу. </para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1 id="bug">
<title
>Отчёты об ошибках</title>
<para
>Сообщать об ошибках и отправлять предложения по улучшению можно по адресу <ulink url="https://bugs.kde.org/enter_bug.cgi?product=NeoChat&amp;component=General"
>https://bugs.kde.org/enter_bug.cgi?product=NeoChat&amp;component=General</ulink
></para>
</refsect1>
<refsect1>
<title
>Смотрите также</title>
<simplelist>
<member
>Список наиболее часто задаваемых вопросов о Matrix <ulink url="https://matrix.org/faq/"
>https://matrix.org/faq/</ulink
> </member>
<member
>kf5options(7)</member>
<member
>qt5options(7)</member>
</simplelist>
</refsect1>
<refsect1 id="copyright"
><title
>Авторские права</title>
<para
>Авторские права &copy; Tobias Fella, 20202022 </para>
<para
>Авторские права &copy; Carl Schwan, 20202022 </para>
<para
>Лицензия: стандартная общественная лицензия GNU версии 3 или любой более поздней версии &lt;<ulink url="https://www.gnu.org/licenses/gpl-3.0.html"
>https://www.gnu.org/licenses/gpl-3.0.html</ulink
>&gt;</para>
</refsect1>
</refentry>

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

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

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -9,6 +9,7 @@ add_subdirectory(libneochat)
add_subdirectory(login)
add_subdirectory(rooms)
add_subdirectory(roominfo)
add_subdirectory(messagecontent)
add_subdirectory(timeline)
add_subdirectory(spaces)
add_subdirectory(chatbar)

View File

@@ -20,8 +20,6 @@ add_library(neochat STATIC
windowcontroller.h
models/serverlistmodel.cpp
models/serverlistmodel.h
logger.cpp
logger.h
models/notificationsmodel.cpp
models/notificationsmodel.h
proxycontroller.cpp
@@ -61,9 +59,7 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
qml/RoomPage.qml
qml/ManualRoomDialog.qml
qml/ExplorerDelegate.qml
qml/ImageEditorPage.qml
qml/NeochatMaximizeComponent.qml
qml/TypingPane.qml
qml/QuickSwitcher.qml
qml/AttachmentPane.qml
qml/QuickFormatBar.qml
@@ -108,12 +104,11 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
DEPENDENCIES
QtCore
QtQuick
org.kde.prison
org.kde.prison.scanner
IMPORTS
org.kde.neochat.libneochat
org.kde.neochat.rooms
org.kde.neochat.roominfo
org.kde.neochat.messagecontent
org.kde.neochat.timeline
org.kde.neochat.spaces
org.kde.neochat.settings
@@ -182,7 +177,7 @@ else()
endif()
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models)
target_link_libraries(neochat PRIVATE Loginplugin Roomsplugin RoomInfoplugin Timelineplugin Spacesplugin Chatbarplugin Settingsplugin Devtoolsplugin)
target_link_libraries(neochat PRIVATE Loginplugin Roomsplugin RoomInfoplugin MessageContentplugin Timelineplugin Spacesplugin Chatbarplugin Settingsplugin Devtoolsplugin)
target_link_libraries(neochat PUBLIC
LibNeoChat
Timeline
@@ -207,6 +202,7 @@ target_link_libraries(neochat PUBLIC
QuotientQt6
Login
Rooms
MessageContent
Spaces
)
@@ -361,3 +357,10 @@ install(TARGETS neochat-app ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
install(FILES plasma-runner-neochat.desktop DESTINATION ${KDE_INSTALL_DATAROOTDIR}/krunner/dbusplugins)
endif()
if (APPLE)
set_target_properties(neochat-app PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.neochat")
set_target_properties(neochat-app PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "NeoChat")
set_target_properties(neochat-app PROPERTIES MACOSX_BUNDLE_SHORT_VERSION_STRING ${RELEASE_SERVICE_VERSION})
set_target_properties(neochat-app PROPERTIES MACOSX_BUNDLE_BUNDLE_VERSION ${RELEASE_SERVICE_VERSION})
endif ()

View File

@@ -407,4 +407,9 @@ void Controller::markImageShown(const QString &eventId)
m_shownImages.append(eventId);
}
void Controller::markImageHidden(const QString &eventId)
{
m_shownImages.removeAll(eventId);
}
#include "moc_controller.cpp"

View File

@@ -99,6 +99,7 @@ public:
Q_INVOKABLE bool isImageShown(const QString &eventId);
Q_INVOKABLE void markImageShown(const QString &eventId);
Q_INVOKABLE void markImageHidden(const QString &eventId);
private:
explicit Controller(QObject *parent = nullptr);

View File

@@ -1,223 +0,0 @@
// SPDX-FileCopyrightText: 1997 Matthias Kalle Dalheimer <kalle@kde.org>
// SPDX-FileCopyrightText: 2002 Holger Freyther <freyther@kde.org>
// SPDX-FileCopyrightText: 2008 Volker Krause <vkrause@kde.org>
// SPDX-FileCopyrightText: 2023 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "logger.h"
#include <QDateTime>
#include <QDir>
#include <QFileInfo>
#include <QLoggingCategory>
#include <QMutex>
#include <QStandardPaths>
using namespace Qt::StringLiterals;
static QLoggingCategory::CategoryFilter oldCategoryFilter = nullptr;
static QtMessageHandler oldHandler = nullptr;
static bool e2eeDebugEnabled = false;
class FileDebugStream : public QIODevice
{
Q_OBJECT
public:
FileDebugStream()
: mType(QtCriticalMsg)
{
open(WriteOnly);
}
bool isSequential() const override
{
return true;
}
qint64 readData(char *, qint64) override
{
return 0;
}
qint64 readLineData(char *, qint64) override
{
return 0;
}
qint64 writeData(const char *data, qint64 len) override
{
if (!mFileName.isEmpty()) {
QFile outputFile(mFileName);
outputFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Unbuffered);
outputFile.write(data, len);
outputFile.putChar('\n');
outputFile.close();
}
return len;
}
void setFileName(const QString &fileName)
{
mFileName = fileName;
}
void setType(QtMsgType type)
{
mType = type;
}
private:
QString mFileName;
QtMsgType mType;
};
class DebugPrivate
{
public:
DebugPrivate()
: origHandler(nullptr)
{
}
~DebugPrivate()
{
qInstallMessageHandler(origHandler);
file.close();
}
void log(QtMsgType type, const QMessageLogContext &context, const QString &message)
{
QMutexLocker locker(&mutex);
QByteArray buf;
QTextStream str(&buf);
str << QDateTime::currentDateTime().toString(Qt::ISODate) << u" ["_s;
switch (type) {
case QtDebugMsg:
str << u"DEBUG"_s;
break;
case QtInfoMsg:
str << u"INFO "_s;
break;
case QtWarningMsg:
str << u"WARN "_s;
break;
case QtFatalMsg:
str << u"FATAL"_s;
break;
case QtCriticalMsg:
str << u"CRITICAL"_s;
break;
}
str << u"] "_s << context.category << u": "_s;
if (context.file && *context.file && context.line) {
str << context.file << u":"_s << context.line << u": "_s;
}
if (context.function && *context.function) {
str << context.function << u": "_s;
}
str << message << u"\n"_s;
str.flush();
file.write(buf.constData(), buf.size());
file.flush();
if (oldHandler && (!context.category || (strcmp(context.category, "quotient.e2ee") != 0 || e2eeDebugEnabled))) {
oldHandler(type, context, message);
}
}
void setName(const QString &appName)
{
name = appName;
if (file.isOpen()) {
file.close();
}
const auto &filePath = u"%1%2%3"_s.arg(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), QDir::separator(), appName);
QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QDir::separator());
auto entryList = dir.entryList({appName + u".*"_s});
std::sort(entryList.begin(), entryList.end(), [](const auto &left, const auto &right) {
auto leftIndex = left.split(u"."_s).last().toInt();
auto rightIndex = right.split(u"."_s).last().toInt();
return leftIndex > rightIndex;
});
for (const auto &entry : entryList) {
bool ok = false;
const auto index = entry.split(u"."_s).last().toInt(&ok);
if (!ok) {
continue;
}
QFileInfo info(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QDir::separator() + entry);
if (info.exists()) {
QFile file(info.absoluteFilePath());
if (index > 50) {
file.remove();
continue;
}
const auto &newName = u"%1.%2"_s.arg(filePath, QString::number(index + 1));
const auto success = file.copy(newName);
if (success) {
file.remove();
} else {
qFatal("Cannot rename log file '%s' to '%s': %s",
qUtf8Printable(file.fileName()),
qUtf8Printable(newName),
qUtf8Printable(file.errorString()));
}
}
}
QFileInfo finfo(filePath);
if (!finfo.absoluteDir().exists()) {
QDir().mkpath(finfo.absolutePath());
}
file.setFileName(filePath + u".0"_s);
file.open(QIODevice::WriteOnly | QIODevice::Unbuffered);
}
void setOrigHandler(QtMessageHandler origHandler_)
{
origHandler = origHandler_;
}
QMutex mutex;
QFile file;
QString name;
QtMessageHandler origHandler;
QByteArray loggingCategory;
};
Q_GLOBAL_STATIC(DebugPrivate, sInstance)
void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message)
{
switch (type) {
case QtDebugMsg:
case QtInfoMsg:
case QtWarningMsg:
case QtCriticalMsg:
sInstance()->log(type, context, message);
break;
case QtFatalMsg:
sInstance()->log(QtInfoMsg, context, message);
}
}
void filter(QLoggingCategory *category)
{
if (qstrcmp(category->categoryName(), "quotient.e2ee") == 0) {
category->setEnabled(QtDebugMsg, true);
} else if (oldCategoryFilter) {
oldCategoryFilter(category);
}
}
void initLogging()
{
e2eeDebugEnabled = QLoggingCategory("quotient.e2ee", QtInfoMsg).isEnabled(QtDebugMsg);
oldCategoryFilter = QLoggingCategory::installFilter(filter);
oldHandler = qInstallMessageHandler(messageHandler);
sInstance->setOrigHandler(oldHandler);
sInstance->setName(u"neochat.log"_s);
}
#include "logger.moc"

View File

@@ -1,9 +0,0 @@
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
/**
* Initlalize logging to file and enables some additional categories, which will only be logged to the file
*/
void initLogging();

View File

@@ -49,7 +49,6 @@
#include "blurhashimageprovider.h"
#include "colorschemer.h"
#include "controller.h"
#include "logger.h"
#include "login.h"
#include "registration.h"
#include "roommanager.h"
@@ -138,6 +137,11 @@ int main(int argc, char *argv[])
font.setHintingPreference(QFont::PreferNoHinting);
app.setFont(font);
#endif
#ifdef Q_OS_MACOS
QApplication::setStyle(u"breeze"_s);
#endif
KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));
QGuiApplication::setOrganizationName("KDE"_L1);
@@ -177,8 +181,6 @@ int main(int argc, char *argv[])
KCrash::initialize();
#endif
initLogging();
Connection::setEncryptionDefault(true);
Connection::setDirectChatEncryptionDefault(true);
@@ -195,6 +197,9 @@ int main(int argc, char *argv[])
parser.addPositionalArgument(u"urls"_s, i18n("Supports matrix: url scheme"));
parser.addOption(QCommandLineOption("ignore-ssl-errors"_L1, i18n("Ignore all SSL Errors, e.g., unsigned certificates.")));
QCommandLineOption replaceOption({QStringLiteral("replace")}, i18nc("command line description", "Replace an existing instance"));
parser.addOption(replaceOption);
QCommandLineOption testOption("test"_L1, i18n("Only used for autotests"));
testOption.setFlags(QCommandLineOption::HiddenFromHelp);
parser.addOption(testOption);
@@ -231,7 +236,7 @@ int main(int argc, char *argv[])
#endif
#ifdef HAVE_KDBUSADDONS
KDBusService service(KDBusService::Unique);
KDBusService service(KDBusService::Unique | (parser.isSet(replaceOption) ? KDBusService::Replace : KDBusService::StartupOption(0)));
#endif
const auto accountManager = std::make_unique<AccountManager>(parser.isSet("test"_L1));
@@ -239,13 +244,6 @@ int main(int argc, char *argv[])
LoginHelper::instance().setAccountManager(accountManager.get());
Registration::instance().setAccountManager(accountManager.get());
Q_IMPORT_QML_PLUGIN(org_kde_neochat_settingsPlugin)
Q_IMPORT_QML_PLUGIN(org_kde_neochat_roomsPlugin)
Q_IMPORT_QML_PLUGIN(org_kde_neochat_timelinePlugin)
Q_IMPORT_QML_PLUGIN(org_kde_neochat_devtoolsPlugin)
Q_IMPORT_QML_PLUGIN(org_kde_neochat_loginPlugin)
Q_IMPORT_QML_PLUGIN(org_kde_neochat_chatbarPlugin)
qml_register_types_org_kde_neochat();
qmlRegisterUncreatableMetaObject(Quotient::staticMetaObject, "Quotient", 1, 0, "JoinRule", u"Access to JoinRule enum only"_s);

View File

@@ -176,7 +176,7 @@ void ServerListModel::initialize()
true,
false,
});
beginResetModel();
endResetModel();
}
#include "moc_serverlistmodel.cpp"

View File

@@ -78,6 +78,12 @@
<label>Use a compact room list layout</label>
<default>false</default>
</entry>
<entry name="MarkReadCondition" type="Enum">
<label>The sort order for the rooms in the list.</label>
<choices name="::TimelineMarkReadCondition::Condition">
</choices>
<default>2</default>
</entry>
<entry name="ShowStateEvent" type="bool">
<label>Show state events in the timeline</label>
<default>true</default>
@@ -205,6 +211,10 @@
<label>Enable add phone numbers as 3PIDs</label>
<default>false</default>
</entry>
<entry name="Calls" type="bool">
<label>Enable audio and video calling</label>
<default>false</default>
</entry>
</group>
<group name="Security">
<entry name="RejectUnknownInvites" type="bool">

View File

@@ -36,14 +36,18 @@ KirigamiComponents.ConvergentContextMenu {
}
}
Kirigami.Action {
text: i18nc("@action:inmenu", "Switch Account")
icon.name: "system-switch-user"
shortcut: "Ctrl+U"
onTriggered: accountSwitchDialog.createObject(QQC2.Overlay.overlay, {
connection: root.connection
}).open();
}
QQC2.Action {
text: i18n("Edit This Account")
icon.name: "document-edit"
onTriggered: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.settings', 'AccountEditorPage'), {
connection: root.connection
}, {
title: i18n("Account editor")
})
onTriggered: NeoChatSettingsView.openWithInitialProperties("accounts", {initialAccount: root.connection});
}
QQC2.Action {

View File

@@ -16,7 +16,7 @@ ColumnLayout {
id: root
required property NeoChatRoom currentRoom
readonly property var invitingMember: currentRoom.member(currentRoom.invitingUserId)
readonly property var invitingMember: currentRoom.qmlSafeMember(currentRoom.invitingUserId)
readonly property string inviteTimestamp: root.currentRoom.inviteTimestamp.toLocaleString(Qt.locale(), Locale.ShortFormat)
spacing: Kirigami.Units.smallSpacing
@@ -33,7 +33,7 @@ ColumnLayout {
Layout.fillWidth: true
name: root.invitingMember.displayName
source: root.invitingMember.avatarUrl
source: NeoChatConfig.hideImages ? undefined : root.invitingMember.avatarUrl
color: root.invitingMember.color
}

View File

@@ -41,6 +41,7 @@ Kirigami.ApplicationWindow {
showExisting: true
onConnectionChosen: root.load()
}
columnView.columnResizeMode: pageStack.wideMode ? Kirigami.ColumnView.DynamicColumns : Kirigami.ColumnView.SingleColumn
globalToolBar.canContainHandles: true
globalToolBar {
style: Kirigami.ApplicationHeaderStyle.ToolBar

View File

@@ -139,7 +139,7 @@ Components.AlbumMaximizeComponent {
id: saveAsDialog
Dialogs.FileDialog {
fileMode: Dialogs.FileDialog.SaveFile
currentFolder: root.saveFolder
currentFolder: NeoChatConfig.lastSaveDirectory.length > 0 ? NeoChatConfig.lastSaveDirectory : Core.StandardPaths.writableLocation(Core.StandardPaths.DownloadLocation)
onAccepted: {
NeoChatConfig.lastSaveDirectory = currentFolder;
NeoChatConfig.save();

View File

@@ -28,6 +28,14 @@ Kirigami.Page {
placeholderText: root.placeholder
anchors.fill: parent
wrapMode: TextEdit.Wrap
focus: true
Keys.onReturnPressed: event => {
if (event.modifiers & Qt.ControlModifier) {
root.accepted(reason.text);
root.closeDialog();
}
}
background: Rectangle {
color: Kirigami.Theme.backgroundColor
@@ -50,6 +58,7 @@ Kirigami.Page {
}
}
QQC2.Button {
icon.name: "dialog-cancel-symbolic"
text: i18nc("@action", "Cancel")
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.RejectRole
onClicked: root.closeDialog()

View File

@@ -11,15 +11,14 @@ import org.kde.kirigami as Kirigami
import org.kde.kitemmodels
import org.kde.neochat
import org.kde.neochat.chatbar
Kirigami.Page {
id: root
/// Not readonly because of the separate window view.
property NeoChatRoom currentRoom: RoomManager.currentRoom
required property NeoChatConnection connection
/**
* @brief The NeoChatRoom the delegate is being displayed in.
*/
readonly property NeoChatRoom currentRoom: RoomManager.currentRoom
/**
* @brief The TimelineModel to use.
@@ -59,11 +58,6 @@ Kirigami.Page {
*/
property MediaMessageFilterModel mediaMessageFilterModel: RoomManager.mediaMessageFilterModel
property bool loading: !root.currentRoom || (root.currentRoom.timelineSize === 0 && !root.currentRoom.allHistoryLoaded)
/// Disable cancel shortcut. Used by the separate window since it provides its own cancel implementation.
property bool disableCancelShortcut: false
title: root.currentRoom ? root.currentRoom.displayName : ""
focus: true
padding: 0
@@ -86,9 +80,9 @@ Kirigami.Page {
}
Connections {
target: root.connection
target: root.currentRoom.connection
function onIsOnlineChanged() {
if (!root.connection.isOnline) {
if (!root.currentRoom.connection.isOnline) {
banner.text = i18n("NeoChat is offline. Please check your network connection.");
banner.visible = true;
banner.type = Kirigami.MessageType.Error;
@@ -109,18 +103,15 @@ Kirigami.Page {
Loader {
id: timelineViewLoader
anchors.fill: parent
active: root.currentRoom && !root.currentRoom.isInvite && !root.loading && !root.currentRoom.isSpace
active: root.currentRoom && !root.currentRoom.isInvite && !root.currentRoom.isSpace
// We need the loader to be active but invisible while the room is loading messages so signals in TimelineView work.
visible: !root.loading
sourceComponent: TimelineView {
id: timelineView
currentRoom: root.currentRoom
page: root
timelineModel: root.timelineModel
messageFilterModel: root.messageFilterModel
onFocusChatBar: {
if (chatBarLoader.item) {
chatBarLoader.item.forceActiveFocus();
}
}
compactLayout: NeoChatConfig.compactLayout
fileDropEnabled: !Controller.isFlatpak
markReadCondition: NeoChatConfig.markReadCondition
}
}
@@ -152,14 +143,6 @@ Kirigami.Page {
}
}
Loader {
active: root.loading && !invitationLoader.active && RoomManager.currentRoom && !spaceLoader.active
anchors.centerIn: parent
sourceComponent: Kirigami.LoadingPlaceholder {
anchors.centerIn: parent
}
}
background: Rectangle {
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
@@ -174,12 +157,7 @@ Kirigami.Page {
id: chatBar
width: parent.width
currentRoom: root.currentRoom
connection: root.connection
onMessageSent: {
if (!timelineViewLoader.item.atYEnd) {
timelineViewLoader.item.goToLastMessage();
}
}
connection: root.currentRoom.connection
}
}
@@ -196,21 +174,8 @@ Kirigami.Page {
}
}
Shortcut {
sequence: StandardKey.Cancel
onActivated: {
if (!timelineViewLoader.item.atYEnd || !root.currentRoom.partiallyReadStats.empty()) {
timelineViewLoader.item.goToLastMessage();
root.currentRoom.markAllMessagesAsRead();
} else {
applicationWindow().pageStack.get(0).forceActiveFocus();
}
}
enabled: !root.disableCancelShortcut
}
Connections {
target: root.connection
target: root.currentRoom.connection
function onJoinedRoom(room, invited) {
if (root.currentRoom.id === invited.id) {
RoomManager.resolveResource(room.id);
@@ -296,7 +261,7 @@ Kirigami.Page {
id: messageDelegateContextMenu
MessageDelegateContextMenu {
room: root.currentRoom
connection: root.connection
connection: root.currentRoom.connection
}
}
@@ -304,7 +269,7 @@ Kirigami.Page {
id: fileDelegateContextMenu
FileDelegateContextMenu {
room: root.currentRoom
connection: root.connection
connection: root.currentRoom.connection
}
}

View File

@@ -80,7 +80,6 @@ Kirigami.Dialog {
text: root.user.id
elide: Qt.ElideRight
elideWidth: root.availableWidth - avatar.width - qrButton.width - detailRow.spacing * 2 - detailRow.Layout.leftMargin - detailRow.Layout.rightMargin
onElideWidthChanged: console.warn(root.availableWidth, avatar.width, qrButton.width, elideWidth, elidedText)
}
}

View File

@@ -59,9 +59,9 @@ RoomManager::RoomManager(QObject *parent)
m_directChatsConfig = m_config->group(u"DirectChatsActive"_s);
connect(this, &RoomManager::currentRoomChanged, this, [this]() {
m_userListModel->setRoom(m_currentRoom);
m_timelineModel->setRoom(m_currentRoom);
m_sortFilterRoomTreeModel->setCurrentRoom(m_currentRoom);
m_userListModel->setRoom(m_currentRoom);
});
connect(&Controller::instance(), &Controller::activeConnectionChanged, this, [this](NeoChatConnection *connection) {
@@ -96,6 +96,7 @@ RoomManager::RoomManager(QObject *parent)
m_messageFilterModel->invalidate();
}
});
connect(m_timelineModel->timelineMessageModel(), &MessageModel::modelResetComplete, this, &RoomManager::activateUserModel);
MessageFilterModel::setShowAllEvents(NeoChatConfig::self()->showAllEvents());
connect(NeoChatConfig::self(), &NeoChatConfig::ShowAllEventsChanged, this, [this] {
MessageFilterModel::setShowAllEvents(NeoChatConfig::self()->showAllEvents());
@@ -392,7 +393,9 @@ void RoomManager::joinRoom(Quotient::Connection *account, const QString &roomAli
// If no one gives us a homeserver suggestion, try the server specified in the alias/id.
// Otherwise joining a remote room not on our homeserver will fail.
if (vias.empty()) {
// This is a hack and we're not supposed to do it. With room ids not containing the server going forward, it won't work anymore for new room versions.
// FIXME: Let's keep it around anyway for now, remove it at some point, though
if (vias.empty() && roomAliasOrId.contains(':'_L1)) {
vias.append(roomAliasOrId.mid(roomAliasOrId.lastIndexOf(':'_L1) + 1));
}

View File

@@ -15,4 +15,5 @@ ecm_add_qml_module(Chatbar GENERATE_PLUGIN_SOURCE
EmojiPicker.qml
EmojiDialog.qml
EmojiTonesPicker.qml
ImageEditorPage.qml
)

View File

@@ -160,11 +160,6 @@ QQC2.Control {
}
]
/**
* @brief A message has been sent from the chat bar.
*/
signal messageSent
spacing: 0
Kirigami.Theme.colorSet: Kirigami.Theme.View
@@ -436,7 +431,6 @@ QQC2.Control {
repeatTimer.stop();
root.currentRoom.markAllMessagesAsRead();
textField.clear();
messageSent();
}
function formatText(format, selectionStart, selectionEnd) {

View File

@@ -207,7 +207,7 @@ ColumnLayout {
padding: Kirigami.Units.largeSpacing
contentItem: Image {
source: model.avatarUrl
source: model.url
fillMode: Image.PreserveAspectFit
sourceSize.width: width
sourceSize.height: height

View File

@@ -36,4 +36,13 @@ FormCard.FormCard {
NeoChatConfig.save();
}
}
FormCard.FormCheckDelegate {
text: i18nc("@option:check Enable the matrix feature for audio and video calling", "Calls")
checked: NeoChatConfig.calls
onToggled: {
NeoChatConfig.calls = checked;
NeoChatConfig.save();
}
}
}

View File

@@ -28,6 +28,7 @@ target_sources(LibNeoChat PRIVATE
enums/pushrule.h
enums/roomsortparameter.cpp
enums/roomsortorder.h
enums/timelinemarkreadcondition.h
events/imagepackevent.cpp
events/pollevent.cpp
jobs/neochatgetcommonroomsjob.cpp
@@ -48,11 +49,6 @@ target_sources(LibNeoChat PRIVATE
ecm_add_qml_module(LibNeoChat GENERATE_PLUGIN_SOURCE
URI org.kde.neochat.libneochat
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat/libneochat
DEPENDENCIES
QtCore
QtQuick
org.kde.prison
org.kde.prison.scanner
QML_FILES
qml/GroupChatDrawerHeader.qml
qml/LocationMapItem.qml

View File

@@ -173,7 +173,7 @@ void AccountManager::addConnection(NeoChatConnection *connection)
});
connect(connection, &NeoChatConnection::loggedOut, this, [this, connection] {
// Only set the connection if the account being logged out is currently active
if (m_accountRegistry->accounts().count() > 1 && connection == activeConnection()) {
if (m_accountRegistry->accounts().count() == 1 && connection == activeConnection()) {
setActiveConnection(dynamic_cast<NeoChatConnection *>(m_accountRegistry->accounts()[0]));
} else {
setActiveConnection(nullptr);

View File

@@ -0,0 +1,32 @@
// SPDX-FileCopyrightText: 2025 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <QObject>
#include <QQmlEngine>
/**
* @class TimelineMarkReadCondition
*
* This class is designed to define the TimelineMarkReadCondition enumeration.
*/
class TimelineMarkReadCondition : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
public:
/**
* @brief The condition for marking messages as read.
*/
enum Condition {
Never = 0, /**< Messages should never be marked automatically. */
Entry, /**< Messages should be marked automatically on entry to the room. */
EntryVisible, /**< Messages should be marked automatically on entry to the room if all messages are visible. */
Exit, /**< Messages should be marked automatically on exiting the room. */
ExitVisible, /**< Messages should be marked automatically on exiting the room if all messages are visible. */
};
Q_ENUM(Condition);
};

View File

@@ -435,6 +435,13 @@ QString EventHandler::getBody(const NeoChatRoom *room, const Quotient::RoomEvent
return i18nc("[User] configured <name> widget", "configured %1 widget", e.contentJson()["name"_L1].toString());
},
[prettyPrint](const StateEvent &e) {
if (e.matrixType() == "org.matrix.msc3401.call.member"_L1) {
if (e.contentJson().isEmpty()) {
return i18nc("[User] left a [voice/video] call", "left a call");
} else {
return i18nc("[User] joined a [voice/video] call", "joined a call");
}
}
return e.stateKey().isEmpty() ? i18n("updated %1 state", e.matrixType())
: i18n("updated %1 state for %2", e.matrixType(), prettyPrint ? e.stateKey().toHtmlEscaped() : e.stateKey());
},
@@ -634,7 +641,14 @@ QString EventHandler::genericBody(const NeoChatRoom *room, const Quotient::RoomE
}
return i18n("%1 configured a widget", senderString);
},
[senderString](const StateEvent &) {
[senderString](const StateEvent &e) {
if (e.matrixType() == "org.matrix.msc3401.call.member"_L1) {
if (e.contentJson().isEmpty()) {
return i18nc("[User] left a [voice/video] call", "%1 left a call", senderString);
} else {
return i18nc("[User] joined a [voice/video] call", "%1 joined a call", senderString);
}
}
return i18n("%1 updated the state", senderString);
},
[senderString](const PollStartEvent &) {

View File

@@ -10,7 +10,7 @@ struct MessageComponent {
QString content;
QVariantMap attributes;
int operator==(const MessageComponent &right) const
bool operator==(const MessageComponent &right) const
{
return type == right.type && content == right.content && attributes == right.attributes;
}

View File

@@ -31,13 +31,7 @@ auto leaveRoomLambda = [](const QString &text, NeoChatRoom *room, ChatBarCache *
Q_EMIT room->showMessage(MessageType::Information, i18n("Leaving this room."));
room->forget();
} else {
QRegularExpression roomRegex(uR"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"_s);
auto regexMatch = roomRegex.match(text);
if (!regexMatch.hasMatch()) {
Q_EMIT room->showMessage(MessageType::Error,
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
return QString();
}
// FIXME: re-add sanity check for roomId/alias
auto leaving = dynamic_cast<NeoChatRoom *>(room->connection()->room(text));
if (!leaving) {
leaving = dynamic_cast<NeoChatRoom *>(room->connection()->roomByAlias(text));
@@ -217,13 +211,7 @@ QList<ActionsModel::Action> actions{
Action{
u"join"_s,
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
QRegularExpression roomRegex(uR"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"_s);
auto regexMatch = roomRegex.match(text);
if (!regexMatch.hasMatch()) {
Q_EMIT room->showMessage(MessageType::Error,
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
return QString();
}
// FIXME: re-add sanity check for roomId/alias
auto targetRoom = text.startsWith(QLatin1Char('!')) ? room->connection()->room(text) : room->connection()->roomByAlias(text);
if (targetRoom) {
ActionsModel::instance().resolveResource(targetRoom->id());
@@ -242,25 +230,18 @@ QList<ActionsModel::Action> actions{
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
auto parts = text.split(u" "_s);
QString roomName = parts[0];
QRegularExpression roomRegex(uR"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"_s);
auto regexMatch = roomRegex.match(roomName);
if (!regexMatch.hasMatch()) {
Q_EMIT room->showMessage(MessageType::Error,
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
return QString();
}
auto targetRoom = text.startsWith(QLatin1Char('!')) ? room->connection()->room(text) : room->connection()->roomByAlias(text);
if (targetRoom) {
// FIXME: re-add sanity check for roomId/alias
if (const auto targetRoom = text.startsWith(QLatin1Char('!')) ? room->connection()->room(text) : room->connection()->roomByAlias(text)) {
ActionsModel::instance().resolveResource(targetRoom->id());
return QString();
}
Q_EMIT room->showMessage(MessageType::Information, i18nc("Knocking room <roomname>.", "Knocking room %1.", text));
auto connection = dynamic_cast<NeoChatConnection *>(room->connection());
const auto knownServer = roomName.mid(roomName.indexOf(":"_L1) + 1);
const auto knownServer = roomName.contains(":"_L1) ? QStringList{roomName.mid(roomName.indexOf(":"_L1) + 1)} : QStringList();
if (parts.length() >= 2) {
ActionsModel::instance().knockRoom(connection, roomName, parts[1], QStringList{knownServer});
ActionsModel::instance().knockRoom(connection, roomName, parts[1], knownServer);
} else {
ActionsModel::instance().knockRoom(connection, roomName, QString(), QStringList{knownServer});
ActionsModel::instance().knockRoom(connection, roomName, QString(), knownServer);
}
return QString();
},
@@ -271,13 +252,7 @@ QList<ActionsModel::Action> actions{
Action{
u"j"_s,
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
QRegularExpression roomRegex(uR"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"_s);
auto regexMatch = roomRegex.match(text);
if (!regexMatch.hasMatch()) {
Q_EMIT room->showMessage(MessageType::Error,
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
return QString();
}
// FIXME: re-add sanity check for roomId/alias
if (room->connection()->room(text) || room->connection()->roomByAlias(text)) {
Q_EMIT room->showMessage(MessageType::Information, i18nc("You are already in room <roomname>.", "You are already in room %1.", text));
return QString();

View File

@@ -1214,34 +1214,38 @@ QByteArray NeoChatRoom::getEventJsonSource(const QString &eventId)
void NeoChatRoom::openEventMediaExternally(const QString &eventId)
{
const auto evtIt = findInTimeline(eventId);
if (evtIt != messageEvents().rend() && is<RoomMessageEvent>(**evtIt)) {
const auto event = evtIt->viewAs<RoomMessageEvent>();
if (event->has<EventContent::FileContent>()) {
const auto transferInfo = cachedFileTransferInfo(event);
if (transferInfo.completed()) {
if (evtIt == messageEvents().rend()) {
return;
}
// TODO: Also allow stickers here, once that's fixed in libQuotient
if (!is<RoomMessageEvent>(**evtIt) || !evtIt->viewAs<RoomMessageEvent>()->has<EventContent::FileContentBase>()) {
return;
}
const auto transferInfo = cachedFileTransferInfo(evtIt->viewAs<RoomEvent>());
if (transferInfo.completed()) {
UrlHelper helper;
helper.openUrl(transferInfo.localPath);
return;
}
downloadFile(eventId,
QUrl(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + u'/'
+ evtIt->event()->id().replace(u':', u'_').replace(u'/', u'_').replace(u'+', u'_') + fileNameToDownload(eventId)));
connect(
this,
&Room::fileTransferCompleted,
this,
[this, eventId](QString id, QUrl localFile, FileSourceInfo fileMetadata) {
Q_UNUSED(localFile);
Q_UNUSED(fileMetadata);
if (id == eventId) {
auto transferInfo = fileTransferInfo(eventId);
UrlHelper helper;
helper.openUrl(transferInfo.localPath);
} else {
downloadFile(eventId,
QUrl(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + u'/'
+ event->id().replace(u':', u'_').replace(u'/', u'_').replace(u'+', u'_') + fileNameToDownload(eventId)));
connect(
this,
&Room::fileTransferCompleted,
this,
[this, eventId](QString id, QUrl localFile, FileSourceInfo fileMetadata) {
Q_UNUSED(localFile);
Q_UNUSED(fileMetadata);
if (id == eventId) {
auto transferInfo = fileTransferInfo(eventId);
UrlHelper helper;
helper.openUrl(transferInfo.localPath);
}
},
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection));
}
}
}
},
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection));
}
void NeoChatRoom::copyEventMedia(const QString &eventId)

View File

@@ -557,7 +557,7 @@ public:
* responsibility of the caller to ensure that they only ask for objects
* for real senders.
*/
NeochatRoomMember *qmlSafeMember(const QString &memberId);
Q_INVOKABLE NeochatRoomMember *qmlSafeMember(const QString &memberId);
/**
* @brief Pin a message in the room.

View File

@@ -85,6 +85,15 @@ void LoginHelper::init()
account.sync();
m_accountManager->addConnection(m_connection);
m_accountManager->setActiveConnection(m_connection);
disconnect(m_connection, nullptr, this, nullptr);
connect(
m_connection.get(),
&NeoChatConnection::syncDone,
this,
[this]() {
Q_EMIT loaded();
},
Qt::SingleShotConnection);
m_connection = nullptr;
});
connect(m_connection, &NeoChatConnection::networkError, this, [this](QString error, const QString &, int, int) {
@@ -105,15 +114,6 @@ void LoginHelper::init()
connect(m_connection, &NeoChatConnection::resolveError, this, [this](QString error) {
Q_EMIT m_connection->errorOccured(i18n("Network Error: %1", std::move(error)));
});
connect(
m_connection.get(),
&NeoChatConnection::syncDone,
this,
[this]() {
Q_EMIT loaded();
},
Qt::SingleShotConnection);
}
void LoginHelper::setHomeserverReachable(bool reachable)

View File

@@ -0,0 +1,107 @@
# SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
# SPDX-License-Identifier: BSD-2-Clause
qt_add_library(MessageContent STATIC)
ecm_add_qml_module(MessageContent GENERATE_PLUGIN_SOURCE
URI org.kde.neochat.messagecontent
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat/messagecontent
QML_FILES
BaseMessageComponentChooser.qml
MessageComponentChooser.qml
ReplyMessageComponentChooser.qml
AuthorComponent.qml
AudioComponent.qml
ChatBarComponent.qml
CodeComponent.qml
EncryptedComponent.qml
FetchButtonComponent.qml
FileComponent.qml
ImageComponent.qml
ItineraryComponent.qml
ItineraryReservationComponent.qml
JourneySectionStopDelegateLineSegment.qml
TransportIcon.qml
FoodReservationComponent.qml
TrainReservationComponent.qml
FlightReservationComponent.qml
HotelReservationComponent.qml
LinkPreviewComponent.qml
LinkPreviewLoadComponent.qml
LiveLocationComponent.qml
LoadComponent.qml
LocationComponent.qml
MimeComponent.qml
PdfPreviewComponent.qml
PollComponent.qml
QuoteComponent.qml
ReactionComponent.qml
ReplyAuthorComponent.qml
ReplyButtonComponent.qml
ReplyComponent.qml
StateComponent.qml
TextComponent.qml
ThreadBodyComponent.qml
VideoComponent.qml
SOURCES
contentprovider.cpp
mediasizehelper.cpp
pollhandler.cpp
models/itinerarymodel.cpp
models/linemodel.cpp
models/messagecontentmodel.cpp
models/pollanswermodel.cpp
models/reactionmodel.cpp
models/threadmodel.cpp
RESOURCES
images/bike.svg
images/bus.svg
images/cablecar.svg
images/car.svg
images/coach.svg
images/couchettecar.svg
images/elevator.svg
images/escalator.svg
images/ferry.svg
images/flight.svg
images/foodestablishment.svg
images/funicular.svg
images/longdistancetrain.svg
images/rapidtransit.svg
images/seat.svg
images/shuttle.svg
images/sleepingcar.svg
images/stairs.svg
images/subway.svg
images/taxi.svg
images/train.svg
images/tramway.svg
images/transfer.svg
images/wait.svg
images/walk.svg
DEPENDENCIES
QtQuick
)
configure_file(config-neochat.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-neochat.h)
ecm_qt_declare_logging_category(MessageContent
HEADER "messagemodel_logging.h"
IDENTIFIER "Message"
CATEGORY_NAME "org.kde.neochat.messagemodel"
DESCRIPTION "Neochat: messagemodel"
DEFAULT_SEVERITY Info
EXPORT NEOCHAT
)
target_include_directories(MessageContent PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models)
target_link_libraries(MessageContent PRIVATE
Qt::Core
Qt::Quick
Qt::QuickControls2
KF6::Kirigami
LibNeoChat
)
if(NOT ANDROID)
target_link_libraries(MessageContent PUBLIC KF6::SyntaxHighlighting)
endif()

View File

@@ -46,9 +46,32 @@ Item {
*/
required property var fileTransferInfo
/**
* The maximum height of the image. Can be left undefined most of the times. Passed to MediaSizeHelper::contentMaxHeight.
*/
property var contentMaxHeight: undefined
implicitWidth: mediaSizeHelper.currentSize.width
implicitHeight: mediaSizeHelper.currentSize.height
QQC2.Button {
anchors.right: parent.right
anchors.top: parent.top
visible: !_private.hideImage
icon.name: "view-hidden"
text: i18nc("@action:button", "Hide Image")
display: QQC2.Button.IconOnly
z: 10
onClicked: {
_private.hideImage = true;
Controller.markImageHidden(root.eventId)
}
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
Loader {
id: imageLoader
@@ -98,7 +121,7 @@ Item {
Rectangle {
anchors.fill: parent
visible: _private.imageItem.status !== Image.Ready
visible: _private.imageItem.status !== Image.Ready || _private.hideImage
color: "#BB000000"
@@ -164,6 +187,7 @@ Item {
MediaSizeHelper {
id: mediaSizeHelper
contentMaxWidth: root.Message.maxContentWidth
contentMaxHeight: root.contentMaxHeight ?? -1
mediaWidth: root?.mediaInfo.isSticker ? 256 : (root?.mediaInfo.width ?? 0)
mediaHeight: root?.mediaInfo.isSticker ? 256 : (root?.mediaInfo.height ?? 0)
}

Some files were not shown because too many files have changed in this diff Show More