Compare commits
1 Commits
work/nvrwh
...
work/redst
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb63ac5642 |
@@ -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.5")
|
||||
set(KF_MIN_VERSION "6.6")
|
||||
set(QT_MIN_VERSION "6.5")
|
||||
|
||||
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
|
||||
|
||||
@@ -46,7 +46,6 @@ private Q_SLOTS:
|
||||
void sendCustomEmojiCode_data();
|
||||
void sendCustomEmojiCode();
|
||||
|
||||
void receiveSpacelessSelfClosingTag();
|
||||
void receiveStripReply();
|
||||
void receivePlainTextIn();
|
||||
|
||||
@@ -253,19 +252,6 @@ 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(
|
||||
@@ -463,9 +449,6 @@ 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);
|
||||
|
||||
@@ -479,9 +462,6 @@ 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()
|
||||
@@ -556,7 +536,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<br/>"), {}},
|
||||
MessageComponent{MessageComponentType::Text, QStringLiteral("Ah, you mean something like"), {}},
|
||||
MessageComponent{
|
||||
MessageComponentType::Code,
|
||||
QStringLiteral(
|
||||
|
||||
@@ -17,6 +17,7 @@ class WindowControllerTest : public QObject
|
||||
|
||||
private Q_SLOTS:
|
||||
void nullWindow();
|
||||
void geometry();
|
||||
void showAndRaise();
|
||||
void toggle();
|
||||
|
||||
@@ -29,10 +30,32 @@ 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,7 +429,6 @@
|
||||
<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"/>
|
||||
|
||||
799
po/ar/neochat.po
799
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
840
po/az/neochat.po
840
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
771
po/ca/neochat.po
771
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
783
po/cs/neochat.po
783
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
790
po/da/neochat.po
790
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
837
po/de/neochat.po
837
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
834
po/el/neochat.po
834
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
823
po/eo/neochat.po
823
po/eo/neochat.po
File diff suppressed because it is too large
Load Diff
774
po/es/neochat.po
774
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
772
po/eu/neochat.po
772
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
824
po/fi/neochat.po
824
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
780
po/fr/neochat.po
780
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
770
po/gl/neochat.po
770
po/gl/neochat.po
File diff suppressed because it is too large
Load Diff
813
po/hu/neochat.po
813
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
790
po/ia/neochat.po
790
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
834
po/id/neochat.po
834
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
814
po/ie/neochat.po
814
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
829
po/it/neochat.po
829
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
696
po/ja/neochat.po
696
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
772
po/ka/neochat.po
772
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
822
po/ko/neochat.po
822
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
696
po/lt/neochat.po
696
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
814
po/lv/neochat.po
814
po/lv/neochat.po
File diff suppressed because it is too large
Load Diff
776
po/nl/neochat.po
776
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
790
po/nn/neochat.po
790
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
841
po/pa/neochat.po
841
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
774
po/pl/neochat.po
774
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
832
po/pt/neochat.po
832
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
825
po/ru/neochat.po
825
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
841
po/sk/neochat.po
841
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
773
po/sl/neochat.po
773
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
774
po/sv/neochat.po
774
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
913
po/ta/neochat.po
913
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
771
po/tr/neochat.po
771
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
778
po/uk/neochat.po
778
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,7 +242,6 @@ 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/ConfirmEncryptionDialog.qml
|
||||
qml/RoomSearchPage.qml
|
||||
@@ -307,7 +306,7 @@ add_subdirectory(devtools)
|
||||
add_subdirectory(login)
|
||||
add_subdirectory(chatbar)
|
||||
|
||||
if(NOT ANDROID AND NOT WIN32)
|
||||
if(UNIX)
|
||||
qt_target_qml_sources(neochat QML_FILES qml/ShareAction.qml)
|
||||
else()
|
||||
set_source_files_properties(qml/ShareActionStub.qml PROPERTIES
|
||||
|
||||
@@ -28,8 +28,7 @@ void ColorSchemer::apply(int idx)
|
||||
|
||||
int ColorSchemer::indexForCurrentScheme()
|
||||
{
|
||||
return -1;
|
||||
// return c->indexForSchemeId(c->activeSchemeId()).row();
|
||||
return c->indexForSchemeId(c->activeSchemeId()).row();
|
||||
}
|
||||
|
||||
#include "moc_colorschemer.cpp"
|
||||
|
||||
@@ -154,8 +154,7 @@ 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->prevContent() && roomMemberEvent->prevContent()->membership == roomMemberEvent->membership()
|
||||
&& !NeoChatConfig::self()->showRename()) {
|
||||
} else if (roomMemberEvent->isRename() && !roomMemberEvent->isJoin() && !roomMemberEvent->isLeave() && !NeoChatConfig::self()->showRename()) {
|
||||
return true;
|
||||
} else if (roomMemberEvent->isAvatarUpdate() && !roomMemberEvent->isJoin() && !roomMemberEvent->isLeave()
|
||||
&& !NeoChatConfig::self()->showAvatarUpdate()) {
|
||||
|
||||
@@ -122,7 +122,7 @@ Kirigami.Page {
|
||||
|
||||
QQC2.ToolButton {
|
||||
text: i18nc("@action:button", "Log out of this account")
|
||||
icon.name: "im-kick-user"
|
||||
icon.name: "edit-delete-remove"
|
||||
onClicked: Controller.removeConnection(modelData)
|
||||
display: QQC2.Button.IconOnly
|
||||
QQC2.ToolTip.text: text
|
||||
|
||||
@@ -304,6 +304,7 @@ int main(int argc, char *argv[])
|
||||
QWindow *window = windowFromEngine(&engine);
|
||||
|
||||
WindowController::instance().setWindow(window);
|
||||
WindowController::instance().restoreGeometry();
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
@@ -578,22 +578,21 @@ MessageComponent MessageContentModel::linkPreviewComponent(const QUrl &link)
|
||||
}
|
||||
if (linkPreviewer->loaded()) {
|
||||
return MessageComponent{MessageComponentType::LinkPreview, QString(), {{"link"_ls, link}}};
|
||||
} else {
|
||||
connect(linkPreviewer, &LinkPreviewer::loadedChanged, [this, link]() {
|
||||
const auto linkPreviewer = dynamic_cast<NeoChatConnection *>(m_room->connection())->previewerForLink(link);
|
||||
if (linkPreviewer != nullptr && linkPreviewer->loaded()) {
|
||||
for (auto &component : m_components) {
|
||||
if (component.attributes["link"_ls].toUrl() == link) {
|
||||
// HACK: Because DelegateChooser can't switch the delegate on dataChanged it has to think there is a new delegate.
|
||||
beginResetModel();
|
||||
component.type = MessageComponentType::LinkPreview;
|
||||
endResetModel();
|
||||
}
|
||||
}
|
||||
connect(linkPreviewer, &LinkPreviewer::loadedChanged, this, [this, link]() {
|
||||
const auto linkPreviewer = dynamic_cast<NeoChatConnection *>(m_room->connection())->previewerForLink(link);
|
||||
if (linkPreviewer != nullptr && linkPreviewer->loaded()) {
|
||||
for (auto &component : m_components) {
|
||||
if (component.attributes["link"_ls].toUrl() == link) {
|
||||
// HACK: Because DelegateChooser can't switch the delegate on dataChanged it has to think there is a new delegate.
|
||||
beginResetModel();
|
||||
component.type = MessageComponentType::LinkPreview;
|
||||
endResetModel();
|
||||
}
|
||||
}
|
||||
});
|
||||
return MessageComponent{MessageComponentType::LinkPreviewLoad, QString(), {{"link"_ls, link}}};
|
||||
}
|
||||
}
|
||||
});
|
||||
return MessageComponent{MessageComponentType::LinkPreviewLoad, QString(), {{"link"_ls, link}}};
|
||||
}
|
||||
|
||||
QList<MessageComponent> MessageContentModel::addLinkPreviews(QList<MessageComponent> inputComponents)
|
||||
|
||||
@@ -84,21 +84,19 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_currentRoom) {
|
||||
// HACK: Reset the model to a null room first to make sure QML dismantles
|
||||
// last room's objects before the room is actually changed
|
||||
beginResetModel();
|
||||
m_currentRoom->disconnect(this);
|
||||
m_currentRoom = nullptr;
|
||||
endResetModel();
|
||||
// HACK: Reset the model to a null room first to make sure QML dismantles
|
||||
// last room's objects before the room is actually changed
|
||||
beginResetModel();
|
||||
m_currentRoom->disconnect(this);
|
||||
m_currentRoom = nullptr;
|
||||
endResetModel();
|
||||
|
||||
// Don't clear the member objects until the model has been fully reset and all
|
||||
// refs cleared.
|
||||
m_memberObjects.clear();
|
||||
m_contentModels.clear();
|
||||
m_reactionModels.clear();
|
||||
m_readMarkerModels.clear();
|
||||
}
|
||||
// Don't clear the member objects until the model has been fully reset and all
|
||||
// refs cleared.
|
||||
m_memberObjects.clear();
|
||||
m_contentModels.clear();
|
||||
m_reactionModels.clear();
|
||||
m_readMarkerModels.clear();
|
||||
|
||||
beginResetModel();
|
||||
m_currentRoom = room;
|
||||
|
||||
@@ -2,40 +2,20 @@
|
||||
// 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 = {
|
||||
UsersDefaultKey,
|
||||
StateDefaultKey,
|
||||
EventsDefaultKey,
|
||||
};
|
||||
|
||||
static const QStringList basicPermissions = {
|
||||
InviteKey,
|
||||
KickKey,
|
||||
BanKey,
|
||||
RedactKey,
|
||||
};
|
||||
|
||||
static const QStringList knownPermissions = {
|
||||
QStringLiteral("users_default"),
|
||||
QStringLiteral("state_default"),
|
||||
QStringLiteral("events_default"),
|
||||
QStringLiteral("invite"),
|
||||
QStringLiteral("kick"),
|
||||
QStringLiteral("ban"),
|
||||
QStringLiteral("redact"),
|
||||
QStringLiteral("m.reaction"),
|
||||
QStringLiteral("m.room.redaction"),
|
||||
QStringLiteral("m.room.power_levels"),
|
||||
@@ -53,14 +33,14 @@ static const QStringList knownPermissions = {
|
||||
};
|
||||
|
||||
// Alternate name text for default permissions.
|
||||
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")},
|
||||
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")},
|
||||
{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")},
|
||||
@@ -78,10 +58,10 @@ static const QHash<QString, KLazyLocalizedString> permissionNames = {
|
||||
};
|
||||
|
||||
// Subtitles for the default values.
|
||||
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")},
|
||||
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")},
|
||||
};
|
||||
|
||||
// Permissions that should use the event default.
|
||||
@@ -90,7 +70,6 @@ static const QStringList eventPermissions = {
|
||||
QStringLiteral("m.reaction"),
|
||||
QStringLiteral("m.room.redaction"),
|
||||
};
|
||||
};
|
||||
|
||||
PermissionsModel::PermissionsModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
@@ -130,8 +109,6 @@ 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)) {
|
||||
@@ -154,17 +131,17 @@ QVariant PermissionsModel::data(const QModelIndex &index, int role) const
|
||||
|
||||
const auto permission = m_permissions.value(index.row());
|
||||
if (role == NameRole) {
|
||||
if (permissionNames.keys().contains(permission)) {
|
||||
return permissionNames.value(permission).toString();
|
||||
if (defaultPermissionNames.keys().contains(permission)) {
|
||||
return defaultPermissionNames.value(permission).toString();
|
||||
}
|
||||
return permission;
|
||||
}
|
||||
if (role == SubtitleRole) {
|
||||
if (knownPermissions.contains(permission) && permissionNames.keys().contains(permission)) {
|
||||
if (permission.startsWith(QLatin1String("m.")) && defaultPermissionNames.keys().contains(permission)) {
|
||||
return permission;
|
||||
}
|
||||
if (permissionSubtitles.contains(permission)) {
|
||||
return permissionSubtitles.value(permission).toString();
|
||||
if (defaultSubtitles.contains(permission)) {
|
||||
return defaultSubtitles.value(permission).toString();
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
@@ -189,10 +166,11 @@ QVariant PermissionsModel::data(const QModelIndex &index, int role) const
|
||||
return QString();
|
||||
}
|
||||
if (role == IsDefaultValueRole) {
|
||||
return defaultPermissions.contains(permission);
|
||||
return permission.contains(QLatin1String("default"));
|
||||
}
|
||||
if (role == IsBasicPermissionRole) {
|
||||
return basicPermissions.contains(permission);
|
||||
return permission == QStringLiteral("invite") || permission == QStringLiteral("kick") || permission == QStringLiteral("ban")
|
||||
|| permission == QStringLiteral("redact");
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -223,19 +201,19 @@ std::optional<int> PermissionsModel::powerLevel(const QString &permission) const
|
||||
}
|
||||
|
||||
if (const auto currentPowerLevelEvent = m_room->currentState().get<Quotient::RoomPowerLevelsEvent>()) {
|
||||
if (permission == BanKey) {
|
||||
if (permission == QStringLiteral("ban")) {
|
||||
return currentPowerLevelEvent->ban();
|
||||
} else if (permission == KickKey) {
|
||||
} else if (permission == QStringLiteral("kick")) {
|
||||
return currentPowerLevelEvent->kick();
|
||||
} else if (permission == InviteKey) {
|
||||
} else if (permission == QStringLiteral("invite")) {
|
||||
return currentPowerLevelEvent->invite();
|
||||
} else if (permission == RedactKey) {
|
||||
} else if (permission == QStringLiteral("redact")) {
|
||||
return currentPowerLevelEvent->redact();
|
||||
} else if (permission == UsersDefaultKey) {
|
||||
} else if (permission == QStringLiteral("users_default")) {
|
||||
return currentPowerLevelEvent->usersDefault();
|
||||
} else if (permission == StateDefaultKey) {
|
||||
} else if (permission == QStringLiteral("state_default")) {
|
||||
return currentPowerLevelEvent->stateDefault();
|
||||
} else if (permission == EventsDefaultKey) {
|
||||
} else if (permission == QStringLiteral("events_default")) {
|
||||
return currentPowerLevelEvent->eventsDefault();
|
||||
} else if (eventPermissions.contains(permission)) {
|
||||
return currentPowerLevelEvent->powerLevelForEvent(permission);
|
||||
@@ -263,9 +241,6 @@ 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,10 +2,15 @@
|
||||
// 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>
|
||||
|
||||
@@ -149,6 +154,30 @@ 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();
|
||||
@@ -162,7 +191,7 @@ QString ReactionModel::reactionText(QString text) const
|
||||
.arg(m_room->connection()->makeMediaUrl(QUrl(text)).toString(), QString::number(size));
|
||||
}
|
||||
|
||||
return Utils::isEmoji(text) ? QStringLiteral("<span style=\"font-family: 'emoji';\">") + text + QStringLiteral("</span>") : text;
|
||||
return isEmoji(text) ? QStringLiteral("<span style=\"font-family: 'emoji';\">") + text + QStringLiteral("</span>") : text;
|
||||
}
|
||||
|
||||
#include "moc_reactionmodel.cpp"
|
||||
|
||||
@@ -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, showNotification] {
|
||||
connect(job, &BaseJob::result, this, [this, job, roomMemberEvent, showNotification] {
|
||||
QJsonObject replyData = job->jsonData();
|
||||
if (replyData.contains(QStringLiteral("joined"))) {
|
||||
const bool inAnyOfOurRooms = !replyData[QStringLiteral("joined")].toArray().isEmpty();
|
||||
|
||||
@@ -86,7 +86,7 @@ QQC2.Menu {
|
||||
}
|
||||
QQC2.MenuItem {
|
||||
text: i18n("Logout")
|
||||
icon.name: "im-kick-user"
|
||||
icon.name: "list-remove-user"
|
||||
onTriggered: confirmLogoutDialogComponent.createObject(QQC2.ApplicationWindow.window.overlay).open()
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ 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
|
||||
@@ -69,8 +68,36 @@ Kirigami.ApplicationWindow {
|
||||
}
|
||||
}
|
||||
|
||||
KConfig.WindowStateSaver {
|
||||
configGroupName: "Window"
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
QuickSwitcher {
|
||||
@@ -171,9 +198,11 @@ Kirigami.ApplicationWindow {
|
||||
if (ShareHandler.text && root.connection) {
|
||||
root.handleShare()
|
||||
}
|
||||
const hasSystemTray = Controller.supportSystemTray && NeoChatConfig.systemTray;
|
||||
if (Kirigami.Settings.isMobile || !(hasSystemTray && NeoChatConfig.minimizeToSystemTrayOnStartup)) {
|
||||
if (NeoChatConfig.minimizeToSystemTrayOnStartup && !Kirigami.Settings.isMobile && Controller.supportSystemTray && NeoChatConfig.systemTray) {
|
||||
restoreWindowGeometryConnections.enabled = true; // To restore window size and position
|
||||
} else {
|
||||
visible = true;
|
||||
saveWindowGeometryConnections.enabled = true;
|
||||
}
|
||||
}
|
||||
Connections {
|
||||
@@ -247,7 +276,22 @@ Kirigami.ApplicationWindow {
|
||||
target: Controller
|
||||
|
||||
function onErrorOccured(error, detail) {
|
||||
showPassiveNotification(detail.length > 0 ? i18n("%1: %2", error, detail) : error, "short");
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -281,27 +281,15 @@ Kirigami.Page {
|
||||
}
|
||||
|
||||
function onShowFileMenu(eventId, author, messageComponentType, plainText, mimeType, progressInfo, isThread) {
|
||||
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();
|
||||
}
|
||||
const contextMenu = fileDelegateContextMenu.createObject(root, {
|
||||
author: author,
|
||||
eventId: eventId,
|
||||
plainText: plainText,
|
||||
mimeType: mimeType,
|
||||
progressInfo: progressInfo,
|
||||
isThread: isThread
|
||||
});
|
||||
contextMenu.open();
|
||||
}
|
||||
|
||||
function onShowMaximizedMedia(index) {
|
||||
@@ -339,13 +327,6 @@ Kirigami.Page {
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: fileDelegateContextMenuMobile
|
||||
FileDelegateContextMenuMobile {
|
||||
connection: root.connection
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: maximizeComponent
|
||||
NeochatMaximizeComponent {
|
||||
|
||||
@@ -119,7 +119,10 @@ QQC2.Control {
|
||||
activeFocusOnTab: true
|
||||
|
||||
checked: RoomManager.currentSpace.length === 0
|
||||
onSelected: RoomManager.currentSpace = ""
|
||||
onSelected: {
|
||||
RoomManager.currentSpace = "";
|
||||
root.selectionChanged();
|
||||
}
|
||||
}
|
||||
AvatarTabButton {
|
||||
id: directChatButton
|
||||
@@ -172,7 +175,10 @@ QQC2.Control {
|
||||
activeFocusOnTab: true
|
||||
|
||||
checked: RoomManager.currentSpace === "DM"
|
||||
onSelected: RoomManager.currentSpace = "DM"
|
||||
onSelected: {
|
||||
RoomManager.currentSpace = "DM";
|
||||
root.selectionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
|
||||
@@ -122,18 +122,7 @@ Kirigami.Dialog {
|
||||
text: i18n("Kick this user")
|
||||
icon.name: "im-kick-user"
|
||||
onTriggered: {
|
||||
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.room.kickMember(root.user.id);
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,7 +375,7 @@ void RoomManager::knockRoom(NeoChatConnection *account, const QString &roomAlias
|
||||
account,
|
||||
&NeoChatConnection::newRoom,
|
||||
this,
|
||||
[](Quotient::Room *room) {
|
||||
[this](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 {
|
||||
|
||||
@@ -459,7 +459,7 @@ QString TextHandler::cleanAttributes(const QString &tag, const QString &tagStrin
|
||||
nextAttributeIndex += 1;
|
||||
|
||||
while (nextAttributeIndex < tagString.length()) {
|
||||
nextSpaceIndex = tagString.indexOf(TextRegex::endAttributeType, nextAttributeIndex);
|
||||
nextSpaceIndex = tagString.indexOf(TextRegex::endTagType, nextAttributeIndex);
|
||||
if (nextSpaceIndex == -1) {
|
||||
nextSpaceIndex = tagString.length();
|
||||
}
|
||||
@@ -505,7 +505,7 @@ QVariantMap TextHandler::getAttributes(const QString &tag, const QString &tagStr
|
||||
nextAttributeIndex += 1;
|
||||
|
||||
while (nextAttributeIndex < tagString.length()) {
|
||||
nextSpaceIndex = tagString.indexOf(TextRegex::endAttributeType, nextAttributeIndex);
|
||||
nextSpaceIndex = tagString.indexOf(TextRegex::endTagType, nextAttributeIndex);
|
||||
if (nextSpaceIndex == -1) {
|
||||
nextSpaceIndex = tagString.length();
|
||||
}
|
||||
@@ -626,7 +626,7 @@ QString TextHandler::linkifyUrls(QString stringIn)
|
||||
int skip = 0;
|
||||
if (match.captured(0).size() > 0) {
|
||||
if (stringIn.left(index).count(QStringLiteral("<code>")) == stringIn.left(index).count(QStringLiteral("</code>"))) {
|
||||
auto replacement = QStringLiteral("<a href=\"https://matrix.to/#/%1\">%1</a>").arg(match.captured(1));
|
||||
auto replacement = QStringLiteral("<a href=\"https://matrix.to/#/%1\">%1</a>").arg(match.captured(2));
|
||||
stringIn = stringIn.replace(index, match.captured(0).size(), replacement);
|
||||
} else {
|
||||
skip = match.captured().length();
|
||||
|
||||
@@ -25,6 +25,16 @@ TextEdit {
|
||||
*/
|
||||
property bool isReply: false
|
||||
|
||||
/**
|
||||
* @brief Regex for detecting a message with a single emoji.
|
||||
*/
|
||||
readonly property var isEmojiRegex: /^(<span style='.*'>)?(\u00a9|\u00ae|[\u20D0-\u2fff]|[\u3190-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])+(<\/span>)?$/
|
||||
|
||||
/**
|
||||
* @brief Whether the message is an emoji
|
||||
*/
|
||||
readonly property var isEmoji: isEmojiRegex.test(display)
|
||||
|
||||
/**
|
||||
* @brief Regex for detecting a message with a spoiler.
|
||||
*/
|
||||
@@ -103,8 +113,8 @@ a{
|
||||
selectedTextColor: Kirigami.Theme.highlightedTextColor
|
||||
selectionColor: Kirigami.Theme.highlightColor
|
||||
font {
|
||||
pointSize: !root.isReply && QmlUtils.isEmoji(display) ? Kirigami.Theme.defaultFont.pointSize * 4 : Kirigami.Theme.defaultFont.pointSize
|
||||
family: QmlUtils.isEmoji(display) ? 'emoji' : Kirigami.Theme.defaultFont.family
|
||||
pointSize: !root.isReply && root.isEmoji ? Kirigami.Theme.defaultFont.pointSize * 4 : Kirigami.Theme.defaultFont.pointSize
|
||||
family: root.isEmoji ? 'emoji' : Kirigami.Theme.defaultFont.family
|
||||
}
|
||||
selectByMouse: !Kirigami.Settings.isMobile
|
||||
readOnly: true
|
||||
|
||||
@@ -3,24 +3,12 @@
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
#ifdef HAVE_ICU
|
||||
#include <QTextBoundaryFinder>
|
||||
#include <QTextCharFormat>
|
||||
#include <unicode/uchar.h>
|
||||
#include <unicode/urename.h>
|
||||
#endif
|
||||
|
||||
#include <Quotient/connection.h>
|
||||
|
||||
#include <QJsonDocument>
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
bool QmlUtils::isEmoji(const QString &text)
|
||||
{
|
||||
return Utils::isEmoji(text);
|
||||
}
|
||||
|
||||
bool QmlUtils::isValidJson(const QByteArray &json)
|
||||
{
|
||||
return !QJsonDocument::fromJson(json).isNull();
|
||||
@@ -38,28 +26,4 @@ QColor QmlUtils::getUserColor(qreal hueF)
|
||||
return QColor::fromHslF(hueF, 1, -0.7 * lightness + 0.9, 1);
|
||||
}
|
||||
|
||||
bool Utils::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
|
||||
}
|
||||
|
||||
#include "moc_utils.cpp"
|
||||
|
||||
@@ -30,7 +30,6 @@ public:
|
||||
return _instance;
|
||||
}
|
||||
|
||||
Q_INVOKABLE bool isEmoji(const QString &text);
|
||||
Q_INVOKABLE bool isValidJson(const QByteArray &json);
|
||||
Q_INVOKABLE QString escapeString(const QString &string);
|
||||
Q_INVOKABLE QColor getUserColor(qreal hueF);
|
||||
@@ -54,14 +53,11 @@ inline QColor getUserColor(qreal hueF)
|
||||
// https://github.com/quotient-im/libQuotient/wiki/User-color-coding-standard-draft-proposal
|
||||
return QColor::fromHslF(hueF, 1, -0.7 * lightness + 0.9, 1);
|
||||
}
|
||||
|
||||
bool isEmoji(const QString &text);
|
||||
}
|
||||
|
||||
namespace TextRegex
|
||||
{
|
||||
static const QRegularExpression endTagType{QStringLiteral("[> /]")};
|
||||
static const QRegularExpression endAttributeType{QStringLiteral("[> ]")};
|
||||
static const QRegularExpression endTagType{QStringLiteral("(>| )")};
|
||||
static const QRegularExpression attributeData{QStringLiteral("['\"](.*?)['\"]")};
|
||||
static const QRegularExpression removeReply{QStringLiteral("> <.*?>.*?\\n\\n"), QRegularExpression::DotMatchesEverythingOption};
|
||||
static const QRegularExpression removeRichReply{QStringLiteral("<mx-reply>.*?</mx-reply>"), QRegularExpression::DotMatchesEverythingOption};
|
||||
@@ -80,6 +76,6 @@ static const QRegularExpression
|
||||
QRegularExpression::CaseInsensitiveOption | QRegularExpression::UseUnicodePropertiesOption);
|
||||
static const QRegularExpression emailAddress(QStringLiteral(R"(<a.*?<\/a>(*SKIP)(*F)|\b(mailto:)?((\w|\.|-)+@(\w|\.|-)+\.\w+\b))"),
|
||||
QRegularExpression::CaseInsensitiveOption | QRegularExpression::UseUnicodePropertiesOption);
|
||||
static const QRegularExpression mxId(QStringLiteral(R"((?<=^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"),
|
||||
static const QRegularExpression mxId(QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"),
|
||||
QRegularExpression::CaseInsensitiveOption | QRegularExpression::UseUnicodePropertiesOption);
|
||||
}
|
||||
|
||||
@@ -40,6 +40,26 @@ QWindow *WindowController::window() const
|
||||
return m_window;
|
||||
}
|
||||
|
||||
void WindowController::restoreGeometry()
|
||||
{
|
||||
const auto stateConfig = KSharedConfig::openStateConfig();
|
||||
const KConfigGroup windowGroup = stateConfig->group(QStringLiteral("Window"));
|
||||
|
||||
KWindowConfig::restoreWindowSize(m_window, windowGroup);
|
||||
KWindowConfig::restoreWindowPosition(m_window, windowGroup);
|
||||
}
|
||||
|
||||
void WindowController::saveGeometry()
|
||||
{
|
||||
const auto stateConfig = KSharedConfig::openStateConfig();
|
||||
KConfigGroup windowGroup = stateConfig->group(QStringLiteral("Window"));
|
||||
|
||||
KWindowConfig::saveWindowPosition(m_window, windowGroup);
|
||||
KWindowConfig::saveWindowSize(m_window, windowGroup);
|
||||
|
||||
stateConfig->sync();
|
||||
}
|
||||
|
||||
void WindowController::showAndRaiseWindow(const QString &startupId)
|
||||
{
|
||||
if (m_window == nullptr) {
|
||||
@@ -47,6 +67,7 @@ void WindowController::showAndRaiseWindow(const QString &startupId)
|
||||
}
|
||||
if (!m_window->isVisible()) {
|
||||
m_window->show();
|
||||
restoreGeometry();
|
||||
}
|
||||
|
||||
#ifdef HAVE_WINDOWSYSTEM
|
||||
|
||||
@@ -41,6 +41,16 @@ public:
|
||||
*/
|
||||
QWindow *window() const;
|
||||
|
||||
/**
|
||||
* @brief Restore any saved window geometry if available.
|
||||
*/
|
||||
void restoreGeometry();
|
||||
|
||||
/**
|
||||
* @brief Save the current window geometry.
|
||||
*/
|
||||
Q_INVOKABLE void saveGeometry();
|
||||
|
||||
/**
|
||||
* @brief Show the window and raise to the top.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user