mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-12-03 15:48:48 +03:00
Compare commits
13 commits
4c33d10e19
...
d3051aa8ff
Author | SHA1 | Date | |
---|---|---|---|
|
d3051aa8ff | ||
|
1a00d91316 | ||
|
b7a5d714c6 | ||
|
2f967978f2 | ||
|
3b0df06629 | ||
|
27683bedc4 | ||
|
80a39cca17 | ||
|
5523460f4e | ||
|
65c6e96e24 | ||
|
3a3c3def7c | ||
|
da2d7861d7 | ||
|
db68281a28 | ||
|
d299f7af5c |
20 changed files with 714 additions and 597 deletions
|
@ -151,7 +151,7 @@ build-clazy:
|
|||
- apt-get -y install --no-install-suggests --no-install-recommends ca-certificates build-essential ninja-build cmake gcc make automake ccache liblmdb-dev
|
||||
libssl-dev libqt5multimedia5-plugins libqt5multimediagsttools5 libqt5multimediaquick5 libqt5svg5-dev
|
||||
qtmultimedia5-dev qtquickcontrols2-5-dev qttools5-dev qttools5-dev-tools qtdeclarative5-dev
|
||||
qml-module-qtmultimedia qml-module-qtquick-controls2 qml-module-qtquick-layouts qml-module-qt-labs-platform
|
||||
qml-module-qtmultimedia qml-module-qtquick-controls2 qml-module-qtquick-layouts
|
||||
qt5keychain-dev ccache libcurl4-openssl-dev libevent-dev libspdlog-dev git nlohmann-json3-dev libcmark-dev asciidoc time # libolm-dev
|
||||
# need recommended deps for wget
|
||||
- apt-get -y install wget
|
||||
|
@ -282,7 +282,7 @@ build-macos-as:
|
|||
- pipx ensurepath
|
||||
- . ~/.zshrc
|
||||
- mkdir $HOME/Qt
|
||||
- aqt install-qt --outputdir $HOME/qt mac desktop 6.6 clang_64 -m qtlocation qtimageformats qtmultimedia qtpositioning qtshadertools
|
||||
- aqt install-qt --outputdir $HOME/qt mac desktop 6.8 clang_64 -m qtlocation qtimageformats qtmultimedia qtpositioning qtshadertools
|
||||
script:
|
||||
- export QTPATH=($HOME/qt/6.*/macos/bin)
|
||||
- export PATH="$QTPATH:${PATH}"
|
||||
|
@ -394,7 +394,7 @@ build-flatpak:
|
|||
- apt-get -y install --no-install-suggests --no-install-recommends ca-certificates build-essential ninja-build cmake gcc make automake ccache liblmdb-dev
|
||||
libssl-dev libqt5multimedia5-plugins libqt5multimediagsttools5 libqt5multimediaquick5 libqt5svg5-dev
|
||||
qtmultimedia5-dev qtquickcontrols2-5-dev qttools5-dev qttools5-dev-tools qtdeclarative5-dev
|
||||
qml-module-qtmultimedia qml-module-qtquick-controls2 qml-module-qtquick-layouts qml-module-qt-labs-platform
|
||||
qml-module-qtmultimedia qml-module-qtquick-controls2 qml-module-qtquick-layouts
|
||||
qt5keychain-dev ccache libcurl4-openssl-dev libevent-dev libspdlog-dev nlohmann-json3-dev libcmark-dev asciidoc libre2-dev libgtest-dev libgl1-mesa-dev qml-module-qtquick-particles2
|
||||
|
||||
# Installing the packages needed to build AppImage
|
||||
|
|
|
@ -365,7 +365,7 @@ cmake --build build
|
|||
*Build requirements + qml modules needed at runtime (you may not need all of them, but the following seem to work according to reports):*
|
||||
```bash
|
||||
sudo apt install --no-install-recommends g++ cmake make zlib1g-dev libssl-dev libolm-dev liblmdb-dev libcmark-dev nlohmann-json3-dev libspdlog-dev libevent-dev libcurl4-openssl-dev libre2-dev libxcb-ewmh-dev asciidoc-base \
|
||||
qt{base,declarative,tools,multimedia,quickcontrols2-}5-dev libqt6svg5-dev qt6keychain-dev qml-module-qt{gstreamer,multimedia,quick-extras,-labs-settings,-labs-platform,graphicaleffects,quick-controls2,quick-particles2} \
|
||||
qt{base,declarative,tools,multimedia,quickcontrols2-}5-dev libqt6svg5-dev qt6keychain-dev qml-module-qt{gstreamer,multimedia,quick-extras,-labs-settings,graphicaleffects,quick-controls2,quick-particles2} \
|
||||
libgstreamer1.0-dev libgstreamer-plugins-{base,bad}1.0-dev qtgstreamer-plugins-qt6 libnice-dev ninja-build
|
||||
```
|
||||
lmdb++-dev is too old so bundled lmdbxx must be used.
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import "./components"
|
||||
import Qt.labs.platform 1.1 as Platform
|
||||
import QtQml
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
|
@ -101,17 +100,18 @@ Page {
|
|||
]
|
||||
|
||||
onClicked: Communities.setCurrentTagId(model.id)
|
||||
onPressAndHold: communityContextMenu.show(model.id, model.hidden, model.muted)
|
||||
onPressAndHold: communityContextMenu.show(communityItem, model.id, model.hidden, model.muted)
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
TapHandler {
|
||||
id: rth
|
||||
acceptedButtons: Qt.RightButton
|
||||
acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad
|
||||
gesturePolicy: TapHandler.ReleaseWithinBounds
|
||||
|
||||
onSingleTapped: communityContextMenu.show(model.id, model.hidden, model.muted)
|
||||
onSingleTapped: communityContextMenu.show(rth, model.id, model.hidden, model.muted)
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
|
@ -195,28 +195,28 @@ Page {
|
|||
}
|
||||
}
|
||||
|
||||
Platform.Menu {
|
||||
Menu {
|
||||
id: communityContextMenu
|
||||
|
||||
property bool hidden
|
||||
property bool muted
|
||||
property string tagId
|
||||
|
||||
function show(id_, hidden_, muted_) {
|
||||
function show(parent, id_, hidden_, muted_) {
|
||||
tagId = id_;
|
||||
hidden = hidden_;
|
||||
muted = muted_;
|
||||
open();
|
||||
popup(parent);
|
||||
}
|
||||
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
checkable: true
|
||||
checked: communityContextMenu.muted
|
||||
text: qsTr("Do not show notification counts for this community or tag.")
|
||||
|
||||
onTriggered: Communities.toggleTagMute(communityContextMenu.tagId)
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
checkable: true
|
||||
checked: communityContextMenu.hidden
|
||||
text: qsTr("Hide rooms with this tag or from this community by default.")
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
import "./ui"
|
||||
import "./dialogs"
|
||||
import Qt.labs.platform 1.1 as Platform
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.2
|
||||
|
@ -393,7 +392,7 @@ Item {
|
|||
}
|
||||
}
|
||||
}
|
||||
Platform.Menu {
|
||||
Menu {
|
||||
id: messageContextMenuC
|
||||
|
||||
property string eventId
|
||||
|
@ -421,9 +420,9 @@ Item {
|
|||
else
|
||||
link = "";
|
||||
if (showAt_)
|
||||
open(showAt_);
|
||||
popup(showAt_);
|
||||
else
|
||||
open();
|
||||
popup();
|
||||
}
|
||||
|
||||
Component {
|
||||
|
@ -448,7 +447,7 @@ Item {
|
|||
ReportMessage {}
|
||||
}
|
||||
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
enabled: visible
|
||||
text: qsTr("Go to &message")
|
||||
visible: filteredTimeline.filterByContent
|
||||
|
@ -458,21 +457,21 @@ Item {
|
|||
room.showEvent(messageContextMenuC.eventId);
|
||||
}
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
enabled: visible
|
||||
text: qsTr("&Copy")
|
||||
visible: messageContextMenuC.text
|
||||
|
||||
onTriggered: Clipboard.text = messageContextMenuC.text
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
enabled: visible
|
||||
text: qsTr("Copy &link location")
|
||||
visible: messageContextMenuC.link
|
||||
|
||||
onTriggered: Clipboard.text = messageContextMenuC.link
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
id: reactionOption
|
||||
|
||||
text: qsTr("Re&act")
|
||||
|
@ -483,39 +482,39 @@ Item {
|
|||
TimelineManager.focusMessageInput();
|
||||
})
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
text: qsTr("Repl&y")
|
||||
visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false
|
||||
|
||||
onTriggered: room.reply = (messageContextMenuC.eventId)
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
enabled: visible
|
||||
text: qsTr("&Edit")
|
||||
visible: messageContextMenuC.isEditable && (room ? room.permissions.canSend(MtxEvent.TextMessage) : false)
|
||||
|
||||
onTriggered: room.edit = (messageContextMenuC.eventId)
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
enabled: visible
|
||||
text: qsTr("&Thread")
|
||||
visible: (room ? room.permissions.canSend(MtxEvent.TextMessage) : false)
|
||||
|
||||
onTriggered: room.thread = (messageContextMenuC.threadId || messageContextMenuC.eventId)
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
enabled: visible
|
||||
text: visible && room.pinnedMessages.includes(messageContextMenuC.eventId) ? qsTr("Un&pin") : qsTr("&Pin")
|
||||
visible: (room ? room.permissions.canChange(MtxEvent.PinnedEvents) : false)
|
||||
|
||||
onTriggered: visible && room.pinnedMessages.includes(messageContextMenuC.eventId) ? room.unpin(messageContextMenuC.eventId) : room.pin(messageContextMenuC.eventId)
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
text: qsTr("&Read receipts")
|
||||
|
||||
onTriggered: room.showReadReceipts(messageContextMenuC.eventId)
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
text: qsTr("&Forward")
|
||||
visible: messageContextMenuC.eventType == MtxEvent.ImageMessage || messageContextMenuC.eventType == MtxEvent.VideoMessage || messageContextMenuC.eventType == MtxEvent.AudioMessage || messageContextMenuC.eventType == MtxEvent.FileMessage || messageContextMenuC.eventType == MtxEvent.Sticker || messageContextMenuC.eventType == MtxEvent.TextMessage || messageContextMenuC.eventType == MtxEvent.LocationMessage || messageContextMenuC.eventType == MtxEvent.EmoteMessage || messageContextMenuC.eventType == MtxEvent.NoticeMessage
|
||||
|
||||
|
@ -526,15 +525,15 @@ Item {
|
|||
timelineRoot.destroyOnClose(forwardMess);
|
||||
}
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
text: qsTr("&Mark as read")
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
text: qsTr("View raw message")
|
||||
|
||||
onTriggered: room.viewRawMessage(messageContextMenuC.eventId)
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
enabled: visible
|
||||
text: qsTr("View decrypted raw message")
|
||||
// TODO(Nico): Fix this still being iterated over, when using keyboard to select options
|
||||
|
@ -542,7 +541,7 @@ Item {
|
|||
|
||||
onTriggered: room.viewDecryptedRawMessage(messageContextMenuC.eventId)
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
text: qsTr("Remo&ve message")
|
||||
visible: (room ? room.permissions.canRedact() : false) || messageContextMenuC.isSender
|
||||
|
||||
|
@ -554,7 +553,7 @@ Item {
|
|||
timelineRoot.destroyOnClose(dialog);
|
||||
}
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
text: qsTr("Report message")
|
||||
enabled: visible
|
||||
onTriggered: function () {
|
||||
|
@ -564,21 +563,21 @@ Item {
|
|||
timelineRoot.destroyOnClose(dialog);
|
||||
}
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
enabled: visible
|
||||
text: qsTr("&Save as")
|
||||
visible: messageContextMenuC.eventType == MtxEvent.ImageMessage || messageContextMenuC.eventType == MtxEvent.VideoMessage || messageContextMenuC.eventType == MtxEvent.AudioMessage || messageContextMenuC.eventType == MtxEvent.FileMessage || messageContextMenuC.eventType == MtxEvent.Sticker
|
||||
|
||||
onTriggered: room.saveMedia(messageContextMenuC.eventId)
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
enabled: visible
|
||||
text: qsTr("&Open in external program")
|
||||
visible: messageContextMenuC.eventType == MtxEvent.ImageMessage || messageContextMenuC.eventType == MtxEvent.VideoMessage || messageContextMenuC.eventType == MtxEvent.AudioMessage || messageContextMenuC.eventType == MtxEvent.FileMessage || messageContextMenuC.eventType == MtxEvent.Sticker
|
||||
|
||||
onTriggered: room.openMedia(messageContextMenuC.eventId)
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
enabled: visible
|
||||
text: qsTr("Copy link to eve&nt")
|
||||
visible: messageContextMenuC.eventId
|
||||
|
@ -592,7 +591,7 @@ Item {
|
|||
ForwardCompleter {
|
||||
}
|
||||
}
|
||||
Platform.Menu {
|
||||
Menu {
|
||||
id: replyContextMenuC
|
||||
|
||||
property string eventId
|
||||
|
@ -606,21 +605,21 @@ Item {
|
|||
open();
|
||||
}
|
||||
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
enabled: visible
|
||||
text: qsTr("&Copy")
|
||||
visible: replyContextMenuC.text
|
||||
|
||||
onTriggered: Clipboard.text = replyContextMenuC.text
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
enabled: visible
|
||||
text: qsTr("Copy &link location")
|
||||
visible: replyContextMenuC.link
|
||||
|
||||
onTriggered: Clipboard.text = replyContextMenuC.link
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
enabled: visible
|
||||
text: qsTr("&Go to quoted message")
|
||||
visible: true
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
import "./components"
|
||||
import "./dialogs"
|
||||
import "./ui"
|
||||
import Qt.labs.platform 1.1 as Platform
|
||||
import QtQml
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
@ -43,6 +41,8 @@ Page {
|
|||
id: buttonRow
|
||||
|
||||
ImageButton {
|
||||
id: startChatButton
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Nheko.paddingMedium
|
||||
ToolTip.delay: Nheko.tooltipDelay
|
||||
|
@ -53,17 +53,17 @@ Page {
|
|||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/add-square-button.svg"
|
||||
|
||||
onClicked: roomJoinCreateMenu.open(parent)
|
||||
onClicked: roomJoinCreateMenu.popup(startChatButton)
|
||||
|
||||
Platform.Menu {
|
||||
Menu {
|
||||
id: roomJoinCreateMenu
|
||||
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
text: qsTr("Join a room")
|
||||
|
||||
onTriggered: Nheko.openJoinRoomDialog()
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
text: qsTr("Create a new room")
|
||||
|
||||
onTriggered: {
|
||||
|
@ -72,7 +72,7 @@ Page {
|
|||
timelineRoot.destroyOnClose(createRoom);
|
||||
}
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
text: qsTr("Start a direct chat")
|
||||
|
||||
onTriggered: {
|
||||
|
@ -81,7 +81,7 @@ Page {
|
|||
timelineRoot.destroyOnClose(createDirect);
|
||||
}
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
text: qsTr("Create a new community")
|
||||
|
||||
onTriggered: {
|
||||
|
@ -255,68 +255,72 @@ Page {
|
|||
Nheko.setStatusMessage(text);
|
||||
}
|
||||
}
|
||||
Platform.Menu {
|
||||
Menu {
|
||||
id: userInfoMenu
|
||||
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
text: qsTr("Profile settings")
|
||||
|
||||
onTriggered: userInfoPanel.openUserProfile()
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
text: qsTr("Set status message")
|
||||
|
||||
onTriggered: statusDialog.show()
|
||||
}
|
||||
Platform.MenuSeparator {
|
||||
MenuSeparator {
|
||||
}
|
||||
|
||||
Platform.MenuItemGroup {
|
||||
ButtonGroup {
|
||||
id: onlineStateGroup
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
text: qsTr("Automatic online status")
|
||||
group: onlineStateGroup
|
||||
ButtonGroup.group: onlineStateGroup
|
||||
checkable: true
|
||||
checked: Settings.presence == Settings.AutomaticPresence
|
||||
onTriggered: if (checked) Settings.presence = Settings.AutomaticPresence
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
text: qsTr("Online")
|
||||
group: onlineStateGroup
|
||||
ButtonGroup.group: onlineStateGroup
|
||||
checkable: true
|
||||
checked: Settings.presence == Settings.Online
|
||||
onTriggered: if (checked) Settings.presence = Settings.Online
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
text: qsTr("Unavailable")
|
||||
group: onlineStateGroup
|
||||
ButtonGroup.group: onlineStateGroup
|
||||
checkable: true
|
||||
checked: Settings.presence == Settings.Unavailable
|
||||
onTriggered: if (checked) Settings.presence = Settings.Unavailable
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
text: qsTr("Offline")
|
||||
group: onlineStateGroup
|
||||
ButtonGroup.group: onlineStateGroup
|
||||
checkable: true
|
||||
checked: Settings.presence == Settings.Offline
|
||||
onTriggered: if (checked) Settings.presence = Settings.Offline
|
||||
}
|
||||
}
|
||||
TapHandler {
|
||||
id: userTapHandler
|
||||
|
||||
acceptedButtons: Qt.LeftButton
|
||||
gesturePolicy: TapHandler.ReleaseWithinBounds
|
||||
margin: -Nheko.paddingSmall
|
||||
|
||||
onLongPressed: userInfoMenu.open()
|
||||
onLongPressed: userInfoMenu.popup(userTapHandler)
|
||||
onSingleTapped: userInfoPanel.openUserProfile()
|
||||
}
|
||||
TapHandler {
|
||||
id: userTapHandler2
|
||||
|
||||
acceptedButtons: Qt.RightButton
|
||||
gesturePolicy: TapHandler.ReleaseWithinBounds
|
||||
margin: -Nheko.paddingSmall
|
||||
|
||||
onSingleTapped: userInfoMenu.open()
|
||||
onSingleTapped: userInfoMenu.popup(userTapHandler2)
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
|
@ -525,7 +529,7 @@ Page {
|
|||
}
|
||||
onPressAndHold: {
|
||||
if (!isInvite)
|
||||
roomContextMenu.show(roomId, tags);
|
||||
roomContextMenu.show(roomItem, roomId, tags);
|
||||
}
|
||||
|
||||
Ripple {
|
||||
|
@ -538,13 +542,15 @@ Page {
|
|||
anchors.margins: 1
|
||||
|
||||
TapHandler {
|
||||
id: roomItemTh
|
||||
|
||||
acceptedButtons: Qt.RightButton
|
||||
acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad
|
||||
gesturePolicy: TapHandler.ReleaseWithinBounds
|
||||
|
||||
onSingleTapped: {
|
||||
if (!TimelineManager.isInvite)
|
||||
roomContextMenu.show(roomId, tags);
|
||||
roomContextMenu.show(roomItemTh, roomId, tags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -734,16 +740,16 @@ Page {
|
|||
roomid: roomContextMenu.roomid
|
||||
}
|
||||
}
|
||||
Platform.Menu {
|
||||
Menu {
|
||||
id: roomContextMenu
|
||||
|
||||
property string roomid
|
||||
property var tags
|
||||
|
||||
function show(roomid_, tags_) {
|
||||
function show(parent, roomid_, tags_) {
|
||||
roomid = roomid_;
|
||||
tags = tags_;
|
||||
open();
|
||||
popup(parent);
|
||||
}
|
||||
|
||||
InputDialog {
|
||||
|
@ -756,7 +762,7 @@ Page {
|
|||
Rooms.toggleTag(roomContextMenu.roomid, "u." + text, true);
|
||||
}
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
text: qsTr("Open separately")
|
||||
|
||||
onTriggered: {
|
||||
|
@ -768,27 +774,27 @@ Page {
|
|||
destroyOnClose(roomWindow);
|
||||
}
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
text: qsTr("Mark as read")
|
||||
|
||||
onTriggered: Rooms.getRoomById(roomContextMenu.roomid).markRoomAsRead()
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
text: qsTr("Room settings")
|
||||
|
||||
onTriggered: TimelineManager.openRoomSettings(roomContextMenu.roomid)
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
text: qsTr("Leave room")
|
||||
|
||||
onTriggered: TimelineManager.openLeaveRoomDialog(roomContextMenu.roomid)
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
text: qsTr("Copy room link")
|
||||
|
||||
onTriggered: Rooms.copyLink(roomContextMenu.roomid)
|
||||
}
|
||||
Platform.Menu {
|
||||
Menu {
|
||||
id: tagsMenu
|
||||
|
||||
title: qsTr("Tag room as:")
|
||||
|
@ -796,7 +802,7 @@ Page {
|
|||
Instantiator {
|
||||
model: Communities.tagsWithDefault
|
||||
|
||||
delegate: Platform.MenuItem {
|
||||
delegate: MenuItem {
|
||||
property string t: modelData
|
||||
|
||||
checkable: true
|
||||
|
@ -820,7 +826,7 @@ Page {
|
|||
onObjectAdded: (index, object) => tagsMenu.insertItem(index, object)
|
||||
onObjectRemoved: (index, object) => tagsMenu.removeItem(object)
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
text: qsTr("Create new tag...")
|
||||
|
||||
onTriggered: newTag.show()
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
import "./dialogs"
|
||||
import "./pages"
|
||||
import "./ui"
|
||||
import Qt.labs.platform 1.1 as Platform
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Window
|
||||
import QtQuick.Dialogs
|
||||
import im.nheko
|
||||
|
||||
Pane {
|
||||
|
@ -342,15 +342,13 @@ Pane {
|
|||
return UIA.submit3pidToken(t);
|
||||
}
|
||||
}
|
||||
Platform.MessageDialog {
|
||||
MessageDialog {
|
||||
id: uiaConfirmationLinkDialog
|
||||
|
||||
buttons: Platform.MessageDialog.Ok
|
||||
buttons: MessageDialog.Ok
|
||||
text: qsTr("Wait for the confirmation link to arrive, then continue.")
|
||||
|
||||
// Broken on macos, see https://bugreports.qt.io/browse/QTBUG-102078
|
||||
//onAccepted: UIA.continue3pidReceived()
|
||||
onOkClicked: UIA.continue3pidReceived()
|
||||
onAccepted: UIA.continue3pidReceived()
|
||||
}
|
||||
Connections {
|
||||
function onConfirm3pidToken() {
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import Qt.labs.platform 1.1 as Platform
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.2
|
||||
|
@ -235,28 +234,28 @@ Pane {
|
|||
image: ":/icons/icons/ui/options.svg"
|
||||
visible: !!room
|
||||
|
||||
onClicked: roomOptionsMenu.open(roomOptionsButton)
|
||||
onClicked: roomOptionsMenu.popup(roomOptionsButton)
|
||||
|
||||
Platform.Menu {
|
||||
Menu {
|
||||
id: roomOptionsMenu
|
||||
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
text: qsTr("Invite users")
|
||||
visible: room ? room.permissions.canInvite() : false
|
||||
|
||||
onTriggered: TimelineManager.openInviteUsers(roomId)
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
text: qsTr("Members")
|
||||
|
||||
onTriggered: TimelineManager.openRoomMembers(room)
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
text: qsTr("Leave room")
|
||||
|
||||
onTriggered: TimelineManager.openLeaveRoomDialog(roomId)
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
text: qsTr("Settings")
|
||||
|
||||
onTriggered: TimelineManager.openRoomSettings(roomId)
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import Qt.labs.platform 1.1 as Platform
|
||||
import im.nheko 1.0
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import im.nheko
|
||||
|
||||
Platform.Menu {
|
||||
Menu {
|
||||
id: spacesMenu
|
||||
|
||||
property string roomid
|
||||
|
@ -19,56 +19,61 @@ Platform.Menu {
|
|||
onAboutToShow: loadChildren = true
|
||||
//onAboutToHide: loadChildren = false
|
||||
|
||||
Platform.MenuItemGroup {
|
||||
ButtonGroup {
|
||||
id: modificationGroup
|
||||
visible: position != -1
|
||||
//visible: position != -1
|
||||
}
|
||||
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
text: qsTr("Official community for this room")
|
||||
group: modificationGroup
|
||||
ButtonGroup.group: modificationGroup
|
||||
visible: position != -1
|
||||
checkable: true
|
||||
checked: spacesMenu.position >= 0 && (modelData.childValid && modelData.parentValid && modelData.canonical)
|
||||
enabled: spacesMenu.position >= 0 && (modelData.canEditChild && modelData.canEditParent)
|
||||
onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, true, true, true)
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
text: qsTr("Affiliated community for this room")
|
||||
group: modificationGroup
|
||||
ButtonGroup.group: modificationGroup
|
||||
visible: position != -1
|
||||
checkable: true
|
||||
checked: spacesMenu.position >= 0 && (modelData.childValid && modelData.parentValid && !modelData.canonical)
|
||||
enabled: spacesMenu.position >= 0 && (modelData.canEditChild && modelData.canEditParent)
|
||||
onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, true, true, false)
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
text: qsTr("Listed only for community members")
|
||||
group: modificationGroup
|
||||
ButtonGroup.group: modificationGroup
|
||||
visible: position != -1
|
||||
checkable: true
|
||||
checked: spacesMenu.position >= 0 && (modelData.childValid && !modelData.parentValid)
|
||||
enabled: spacesMenu.position >= 0 && ((modelData.canEditChild || modelData.childValid) && (!modelData.parentValid || modelData.canEditParent))
|
||||
onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, false, true, false)
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
text: qsTr("Listed only for room members")
|
||||
group: modificationGroup
|
||||
ButtonGroup.group: modificationGroup
|
||||
visible: position != -1
|
||||
checkable: true
|
||||
checked: spacesMenu.position >= 0 && (!modelData.childValid && modelData.parentValid)
|
||||
enabled: spacesMenu.position >= 0 && ((modelData.canEditChild) && (modelData.parentValid || modelData.canEditParent))
|
||||
onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, true, false, false)
|
||||
}
|
||||
Platform.MenuItem {
|
||||
MenuItem {
|
||||
text: qsTr("Not related")
|
||||
group: modificationGroup
|
||||
ButtonGroup.group: modificationGroup
|
||||
visible: position != -1
|
||||
checkable: true
|
||||
checked: spacesMenu.position >= 0 && (!modelData.childValid && !modelData.parentValid)
|
||||
enabled: spacesMenu.position >= 0 && ((modelData.canEditChild || !modelData.childValid) && (!modelData.parentValid || modelData.canEditParent))
|
||||
onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, false, false, false)
|
||||
}
|
||||
|
||||
Platform.MenuSeparator {
|
||||
text: qsTr("Subcommunities")
|
||||
group: modificationGroup
|
||||
visible: modificationGroup.visible && inst.model != undefined
|
||||
MenuSeparator {
|
||||
//text: qsTr("Subcommunities")
|
||||
ButtonGroup.group: modificationGroup
|
||||
visible: position != -1 && inst.model != undefined
|
||||
}
|
||||
|
||||
Instantiator {
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
|
||||
import ".."
|
||||
import "../ui"
|
||||
import Qt.labs.platform 1.1 as Platform
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtQuick.Window 2.13
|
||||
import QtQuick.Dialogs
|
||||
import im.nheko 1.0
|
||||
|
||||
ApplicationWindow {
|
||||
|
@ -580,26 +580,23 @@ ApplicationWindow {
|
|||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
|
||||
Platform.MessageDialog {
|
||||
MessageDialog {
|
||||
id: confirmEncryptionDialog
|
||||
|
||||
title: qsTr("End-to-End Encryption")
|
||||
text: qsTr(`Encryption is currently experimental and things might break unexpectedly. <br>
|
||||
Please take note that it can't be disabled afterwards.`)
|
||||
modality: Qt.NonModal
|
||||
// Broken on macos, see https://bugreports.qt.io/browse/QTBUG-102078
|
||||
//onAccepted: {
|
||||
onOkClicked: {
|
||||
onAccepted: {
|
||||
if (roomSettings.isEncryptionEnabled)
|
||||
return ;
|
||||
|
||||
roomSettings.enableEncryption();
|
||||
}
|
||||
//onRejected: {
|
||||
onCancelClicked: {
|
||||
onRejected: {
|
||||
encryptionToggle.checked = false;
|
||||
}
|
||||
buttons: Platform.MessageDialog.Ok | Platform.MessageDialog.Cancel
|
||||
buttons: MessageDialog.Ok | MessageDialog.Cancel
|
||||
}
|
||||
|
||||
Label {
|
||||
|
|
787
src/Cache.cpp
787
src/Cache.cpp
File diff suppressed because it is too large
Load diff
169
src/Cache_p.h
169
src/Cache_p.h
|
@ -9,12 +9,6 @@
|
|||
#include <QDateTime>
|
||||
#include <QString>
|
||||
|
||||
#if __has_include(<lmdbxx/lmdb++.h>)
|
||||
#include <lmdbxx/lmdb++.h>
|
||||
#else
|
||||
#include <lmdb++.h>
|
||||
#endif
|
||||
|
||||
#include <mtx/events/collections.hpp>
|
||||
#include <mtx/responses/notifications.hpp>
|
||||
#include <mtx/responses/sync.hpp>
|
||||
|
@ -29,12 +23,20 @@ struct Messages;
|
|||
struct StateEvents;
|
||||
}
|
||||
|
||||
namespace lmdb {
|
||||
class txn;
|
||||
class dbi;
|
||||
}
|
||||
|
||||
struct CacheDb;
|
||||
|
||||
class Cache final : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Cache(const QString &userId, QObject *parent = nullptr);
|
||||
~Cache() noexcept;
|
||||
|
||||
std::string displayName(const std::string &room_id, const std::string &user_id);
|
||||
QString displayName(const QString &room_id, const QString &user_id);
|
||||
|
@ -97,19 +99,11 @@ public:
|
|||
//! Get a specific state event
|
||||
template<typename T>
|
||||
std::optional<mtx::events::StateEvent<T>>
|
||||
getStateEvent(const std::string &room_id, std::string_view state_key = "")
|
||||
{
|
||||
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
|
||||
return getStateEvent<T>(txn, room_id, state_key);
|
||||
}
|
||||
getStateEvent(const std::string &room_id, std::string_view state_key = "");
|
||||
template<typename T>
|
||||
std::vector<mtx::events::StateEvent<T>>
|
||||
getStateEventsWithType(const std::string &room_id,
|
||||
mtx::events::EventType type = mtx::events::state_content_to_type<T>)
|
||||
{
|
||||
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
|
||||
return getStateEventsWithType<T>(txn, room_id, type);
|
||||
}
|
||||
mtx::events::EventType type = mtx::events::state_content_to_type<T>);
|
||||
|
||||
//! retrieve a specific event from account data
|
||||
//! pass empty room_id for global account data
|
||||
|
@ -304,20 +298,6 @@ public:
|
|||
std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>,
|
||||
mtx::events::StateEvent<decltype(std::declval<T>().content)>>;
|
||||
|
||||
static int compare_state_key(const MDB_val *a, const MDB_val *b)
|
||||
{
|
||||
auto get_skey = [](const MDB_val *v) {
|
||||
auto temp = std::string_view(static_cast<const char *>(v->mv_data), v->mv_size);
|
||||
// allow only passing the state key, in which case no null char will be in it and we
|
||||
// return the whole string because rfind returns npos.
|
||||
// We search from the back, because state keys could include nullbytes, event ids can
|
||||
// not.
|
||||
return temp.substr(0, temp.rfind('\0'));
|
||||
};
|
||||
|
||||
return get_skey(a).compare(get_skey(b));
|
||||
}
|
||||
|
||||
signals:
|
||||
void newReadReceipts(const QString &room_id, const std::vector<QString> &event_ids);
|
||||
void roomReadStatus(const std::map<QString, bool> &status);
|
||||
|
@ -401,107 +381,44 @@ private:
|
|||
|
||||
//! Sends signals for the rooms that are removed.
|
||||
void
|
||||
removeLeftRooms(lmdb::txn &txn, const std::map<std::string, mtx::responses::LeftRoom> &rooms)
|
||||
{
|
||||
for (const auto &room : rooms) {
|
||||
removeRoom(txn, room.first);
|
||||
|
||||
// Clean up leftover invites.
|
||||
removeInvite(txn, room.first);
|
||||
}
|
||||
}
|
||||
removeLeftRooms(lmdb::txn &txn, const std::map<std::string, mtx::responses::LeftRoom> &rooms);
|
||||
|
||||
void updateSpaces(lmdb::txn &txn,
|
||||
const std::set<std::string> &spaces_with_updates,
|
||||
std::set<std::string> rooms_with_updates);
|
||||
|
||||
lmdb::dbi getEventsDb(lmdb::txn &txn, const std::string &room_id)
|
||||
{
|
||||
return lmdb::dbi::open(txn, std::string(room_id + "/events").c_str(), MDB_CREATE);
|
||||
}
|
||||
lmdb::dbi getEventsDb(lmdb::txn &txn, const std::string &room_id);
|
||||
|
||||
lmdb::dbi getEventOrderDb(lmdb::txn &txn, const std::string &room_id)
|
||||
{
|
||||
return lmdb::dbi::open(
|
||||
txn, std::string(room_id + "/event_order").c_str(), MDB_CREATE | MDB_INTEGERKEY);
|
||||
}
|
||||
lmdb::dbi getEventOrderDb(lmdb::txn &txn, const std::string &room_id);
|
||||
|
||||
// inverse of EventOrderDb
|
||||
lmdb::dbi getEventToOrderDb(lmdb::txn &txn, const std::string &room_id)
|
||||
{
|
||||
return lmdb::dbi::open(txn, std::string(room_id + "/event2order").c_str(), MDB_CREATE);
|
||||
}
|
||||
lmdb::dbi getEventToOrderDb(lmdb::txn &txn, const std::string &room_id);
|
||||
|
||||
lmdb::dbi getMessageToOrderDb(lmdb::txn &txn, const std::string &room_id)
|
||||
{
|
||||
return lmdb::dbi::open(txn, std::string(room_id + "/msg2order").c_str(), MDB_CREATE);
|
||||
}
|
||||
lmdb::dbi getMessageToOrderDb(lmdb::txn &txn, const std::string &room_id);
|
||||
|
||||
lmdb::dbi getOrderToMessageDb(lmdb::txn &txn, const std::string &room_id)
|
||||
{
|
||||
return lmdb::dbi::open(
|
||||
txn, std::string(room_id + "/order2msg").c_str(), MDB_CREATE | MDB_INTEGERKEY);
|
||||
}
|
||||
lmdb::dbi getOrderToMessageDb(lmdb::txn &txn, const std::string &room_id);
|
||||
|
||||
lmdb::dbi getPendingMessagesDb(lmdb::txn &txn, const std::string &room_id)
|
||||
{
|
||||
return lmdb::dbi::open(
|
||||
txn, std::string(room_id + "/pending").c_str(), MDB_CREATE | MDB_INTEGERKEY);
|
||||
}
|
||||
lmdb::dbi getPendingMessagesDb(lmdb::txn &txn, const std::string &room_id);
|
||||
|
||||
lmdb::dbi getRelationsDb(lmdb::txn &txn, const std::string &room_id)
|
||||
{
|
||||
return lmdb::dbi::open(
|
||||
txn, std::string(room_id + "/related").c_str(), MDB_CREATE | MDB_DUPSORT);
|
||||
}
|
||||
lmdb::dbi getRelationsDb(lmdb::txn &txn, const std::string &room_id);
|
||||
|
||||
lmdb::dbi getInviteStatesDb(lmdb::txn &txn, const std::string &room_id)
|
||||
{
|
||||
return lmdb::dbi::open(txn, std::string(room_id + "/invite_state").c_str(), MDB_CREATE);
|
||||
}
|
||||
lmdb::dbi getInviteStatesDb(lmdb::txn &txn, const std::string &room_id);
|
||||
|
||||
lmdb::dbi getInviteMembersDb(lmdb::txn &txn, const std::string &room_id)
|
||||
{
|
||||
return lmdb::dbi::open(txn, std::string(room_id + "/invite_members").c_str(), MDB_CREATE);
|
||||
}
|
||||
lmdb::dbi getInviteMembersDb(lmdb::txn &txn, const std::string &room_id);
|
||||
|
||||
lmdb::dbi getStatesDb(lmdb::txn &txn, const std::string &room_id)
|
||||
{
|
||||
return lmdb::dbi::open(txn, std::string(room_id + "/state").c_str(), MDB_CREATE);
|
||||
}
|
||||
lmdb::dbi getStatesDb(lmdb::txn &txn, const std::string &room_id);
|
||||
|
||||
lmdb::dbi getStatesKeyDb(lmdb::txn &txn, const std::string &room_id)
|
||||
{
|
||||
auto db = lmdb::dbi::open(
|
||||
txn, std::string(room_id + "/states_key").c_str(), MDB_CREATE | MDB_DUPSORT);
|
||||
lmdb::dbi_set_dupsort(txn, db, compare_state_key);
|
||||
return db;
|
||||
}
|
||||
lmdb::dbi getStatesKeyDb(lmdb::txn &txn, const std::string &room_id);
|
||||
|
||||
lmdb::dbi getAccountDataDb(lmdb::txn &txn, const std::string &room_id)
|
||||
{
|
||||
return lmdb::dbi::open(txn, std::string(room_id + "/account_data").c_str(), MDB_CREATE);
|
||||
}
|
||||
lmdb::dbi getAccountDataDb(lmdb::txn &txn, const std::string &room_id);
|
||||
|
||||
lmdb::dbi getMembersDb(lmdb::txn &txn, const std::string &room_id)
|
||||
{
|
||||
return lmdb::dbi::open(txn, std::string(room_id + "/members").c_str(), MDB_CREATE);
|
||||
}
|
||||
lmdb::dbi getMembersDb(lmdb::txn &txn, const std::string &room_id);
|
||||
|
||||
lmdb::dbi getUserKeysDb(lmdb::txn &txn) { return lmdb::dbi::open(txn, "user_key", MDB_CREATE); }
|
||||
lmdb::dbi getUserKeysDb(lmdb::txn &txn);
|
||||
|
||||
lmdb::dbi getVerificationDb(lmdb::txn &txn)
|
||||
{
|
||||
return lmdb::dbi::open(txn, "verified", MDB_CREATE);
|
||||
}
|
||||
lmdb::dbi getVerificationDb(lmdb::txn &txn);
|
||||
|
||||
QString getDisplayName(const mtx::events::StateEvent<mtx::events::state::Member> &event)
|
||||
{
|
||||
if (!event.content.display_name.empty())
|
||||
return QString::fromStdString(event.content.display_name);
|
||||
|
||||
return QString::fromStdString(event.state_key);
|
||||
}
|
||||
QString getDisplayName(const mtx::events::StateEvent<mtx::events::state::Member> &event);
|
||||
|
||||
std::optional<VerificationCache> verificationCache(const std::string &user_id, lmdb::txn &txn);
|
||||
VerificationStatus verificationStatus_(const std::string &user_id, lmdb::txn &txn);
|
||||
|
@ -509,27 +426,6 @@ private:
|
|||
|
||||
void setNextBatchToken(lmdb::txn &txn, const std::string &token);
|
||||
|
||||
lmdb::env env_;
|
||||
lmdb::dbi syncStateDb_;
|
||||
lmdb::dbi roomsDb_;
|
||||
lmdb::dbi spacesChildrenDb_, spacesParentsDb_;
|
||||
lmdb::dbi invitesDb_;
|
||||
lmdb::dbi readReceiptsDb_;
|
||||
lmdb::dbi notificationsDb_;
|
||||
lmdb::dbi presenceDb_;
|
||||
|
||||
lmdb::dbi devicesDb_;
|
||||
lmdb::dbi deviceKeysDb_;
|
||||
|
||||
lmdb::dbi inboundMegolmSessionDb_;
|
||||
lmdb::dbi outboundMegolmSessionDb_;
|
||||
lmdb::dbi megolmSessionDataDb_;
|
||||
lmdb::dbi olmSessionDb_;
|
||||
|
||||
lmdb::dbi encryptedRooms_;
|
||||
|
||||
lmdb::dbi eventExpiryBgJob_;
|
||||
|
||||
QString localUserId_;
|
||||
QString cacheDirectory_;
|
||||
|
||||
|
@ -538,6 +434,8 @@ private:
|
|||
VerificationStorage verification_storage;
|
||||
|
||||
bool databaseReady_ = false;
|
||||
|
||||
std::unique_ptr<CacheDb> db;
|
||||
};
|
||||
|
||||
namespace cache {
|
||||
|
@ -546,11 +444,12 @@ client();
|
|||
}
|
||||
|
||||
#define NHEKO_CACHE_GET_STATE_EVENT_FORWARD(Content) \
|
||||
extern template std::optional<mtx::events::StateEvent<Content>> Cache::getStateEvent( \
|
||||
lmdb::txn &txn, const std::string &room_id, std::string_view state_key); \
|
||||
extern template std::optional<mtx::events::StateEvent<Content>> Cache::getStateEvent<Content>( \
|
||||
const std::string &room_id, std::string_view state_key); \
|
||||
\
|
||||
extern template std::vector<mtx::events::StateEvent<Content>> Cache::getStateEventsWithType( \
|
||||
lmdb::txn &txn, const std::string &room_id, mtx::events::EventType type);
|
||||
extern template std::vector<mtx::events::StateEvent<Content>> \
|
||||
Cache::getStateEventsWithType<Content>(const std::string &room_id, \
|
||||
mtx::events::EventType type);
|
||||
|
||||
NHEKO_CACHE_GET_STATE_EVENT_FORWARD(mtx::events::state::Aliases)
|
||||
NHEKO_CACHE_GET_STATE_EVENT_FORWARD(mtx::events::state::Avatar)
|
||||
|
|
|
@ -13,6 +13,9 @@
|
|||
|
||||
#include <mtx/responses.hpp>
|
||||
|
||||
#include "Logging.h"
|
||||
#include "UserSettingsPage.h"
|
||||
|
||||
namespace http {
|
||||
|
||||
mtx::http::Client *
|
||||
|
@ -20,9 +23,15 @@ client()
|
|||
{
|
||||
static auto client_ = [] {
|
||||
auto c = std::make_shared<mtx::http::Client>();
|
||||
c->alt_svc_cache_path((QStandardPaths::writableLocation(QStandardPaths::CacheLocation) +
|
||||
"/curl_alt_svc_cache.txt")
|
||||
.toStdString());
|
||||
|
||||
// Disabled by default until CPU usage and reliability improves
|
||||
if (UserSettings::instance()->qsettings()->value("enable_http3").toBool()) {
|
||||
nhlog::net()->warn("Enabling http3 support. This is currently usually a worse "
|
||||
"experience, so you are on your own.");
|
||||
c->alt_svc_cache_path((QStandardPaths::writableLocation(QStandardPaths::CacheLocation) +
|
||||
"/curl_alt_svc_cache.txt")
|
||||
.toStdString());
|
||||
}
|
||||
return c;
|
||||
}();
|
||||
return client_.get();
|
||||
|
|
|
@ -32,16 +32,17 @@ MxcImageProvider::MxcImageProvider()
|
|||
timer->setInterval(std::chrono::hours(1));
|
||||
connect(timer, &QTimer::timeout, this, [] {
|
||||
QThreadPool::globalInstance()->start([] {
|
||||
nhlog::net()->debug("Running media purge");
|
||||
QDir dir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) +
|
||||
"/media_cache",
|
||||
"",
|
||||
QDir::SortFlags(QDir::Name | QDir::IgnoreCase),
|
||||
QDir::Filter::Writable | QDir::Filter::NoDotAndDotDot | QDir::Filter::Files);
|
||||
QDir::Filter::Writable | QDir::Filter::NoDotAndDotDot | QDir::Filter::Files |
|
||||
QDir::Filter::Dirs);
|
||||
|
||||
auto files = dir.entryInfoList();
|
||||
for (const auto &fileInfo : std::as_const(files)) {
|
||||
auto handleFile = [](const QFileInfo &fileInfo) {
|
||||
if (fileInfo.fileTime(QFile::FileTime::FileAccessTime)
|
||||
.daysTo(QDateTime::currentDateTime()) > 30) {
|
||||
.daysTo(QDateTime::currentDateTime()) > 14) {
|
||||
if (QFile::remove(fileInfo.absoluteFilePath()))
|
||||
nhlog::net()->debug("Deleted stale media '{}'",
|
||||
fileInfo.absoluteFilePath().toStdString());
|
||||
|
@ -49,6 +50,24 @@ MxcImageProvider::MxcImageProvider()
|
|||
nhlog::net()->warn("Failed to delete stale media '{}'",
|
||||
fileInfo.absoluteFilePath().toStdString());
|
||||
}
|
||||
};
|
||||
|
||||
auto files = dir.entryInfoList();
|
||||
for (const auto &fileInfo : std::as_const(files)) {
|
||||
if (fileInfo.isDir()) {
|
||||
// handle one level of legacy directories
|
||||
auto nestedDir = QDir(fileInfo.absoluteFilePath(),
|
||||
"",
|
||||
QDir::SortFlags(QDir::Name | QDir::IgnoreCase),
|
||||
QDir::Filter::Writable | QDir::Filter::NoDotAndDotDot |
|
||||
QDir::Filter::Files)
|
||||
.entryInfoList();
|
||||
for (const auto &nestedFile : std::as_const(nestedDir)) {
|
||||
handleFile(nestedFile);
|
||||
}
|
||||
} else {
|
||||
handleFile(fileInfo);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <QWindow>
|
||||
|
||||
#include "TrayIcon.h"
|
||||
#include "UserSettingsPage.h"
|
||||
|
||||
MsgCountComposedIcon::MsgCountComposedIcon(const QIcon &icon)
|
||||
: QIconEngine()
|
||||
|
@ -114,12 +115,30 @@ TrayIcon::TrayIcon(const QString &filename, QWindow *parent)
|
|||
|
||||
menu->addAction(viewAction_);
|
||||
menu->addAction(quitAction_);
|
||||
|
||||
QString toolTip = QLatin1String("nheko");
|
||||
QString profile = UserSettings::instance()->profile();
|
||||
if (!profile.isEmpty())
|
||||
toolTip.append(QStringLiteral(" | %1").arg(profile));
|
||||
|
||||
setToolTip(toolTip);
|
||||
}
|
||||
|
||||
void
|
||||
TrayIcon::setUnreadCount(int count)
|
||||
{
|
||||
qGuiApp->setBadgeNumber(count);
|
||||
if (count != previousCount) {
|
||||
QString toolTip = QLatin1String("nheko");
|
||||
QString profile = UserSettings::instance()->profile();
|
||||
if (!profile.isEmpty())
|
||||
toolTip.append(QStringLiteral(" | %1").arg(profile));
|
||||
|
||||
if (count != 0)
|
||||
toolTip.append(tr("\n%n unread message(s)", "", count));
|
||||
|
||||
setToolTip(toolTip);
|
||||
}
|
||||
|
||||
#if !defined(Q_OS_MACOS) && !defined(Q_OS_WIN)
|
||||
if (count != previousCount) {
|
||||
|
@ -131,13 +150,6 @@ TrayIcon::setUnreadCount(int count)
|
|||
#else
|
||||
(void)previousCount;
|
||||
#endif
|
||||
|
||||
QString toolTip = QLatin1String("nheko");
|
||||
if (count > 0) {
|
||||
toolTip.append(tr("\n%n unread message(s)", "", count));
|
||||
}
|
||||
|
||||
setToolTip(toolTip);
|
||||
}
|
||||
|
||||
#include "moc_TrayIcon.cpp"
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
#include <QVideoFrame>
|
||||
#include <QVideoSink>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <mtx/responses/common.hpp>
|
||||
|
@ -578,8 +580,7 @@ InputBar::message(const QString &msg, MarkdownOverride useMarkdown, bool rainbow
|
|||
}
|
||||
}
|
||||
|
||||
text.body =
|
||||
QStringLiteral("%1\n%2").arg(body, QString::fromStdString(text.body)).toStdString();
|
||||
text.body = fmt::format("{}\n{}", body.toStdString(), text.body);
|
||||
|
||||
// NOTE(Nico): rich replies always need a formatted_body!
|
||||
text.format = "org.matrix.custom.html";
|
||||
|
|
|
@ -37,8 +37,14 @@ static CacheEntry *
|
|||
pullPresence(const QString &id)
|
||||
{
|
||||
auto p = cache::presence(id.toStdString());
|
||||
auto c = new CacheEntry{
|
||||
utils::replaceEmoji(QString::fromStdString(p.status_msg).toHtmlEscaped()), p.presence};
|
||||
|
||||
auto statusMsg = QString::fromStdString(p.status_msg);
|
||||
if (statusMsg.size() > 255) {
|
||||
statusMsg.truncate(255);
|
||||
statusMsg.append(u'…');
|
||||
}
|
||||
|
||||
auto c = new CacheEntry{utils::replaceEmoji(std::move(statusMsg).toHtmlEscaped()), p.presence};
|
||||
presences.insert(id, c);
|
||||
return c;
|
||||
}
|
||||
|
|
|
@ -238,9 +238,11 @@ TimelineFilter::filterAcceptsRow(int source_row, const QModelIndex &) const
|
|||
|
||||
if (auto s = sourceModel()) {
|
||||
auto idx = s->index(source_row, 0);
|
||||
if (!contentFilter.isEmpty() && !s->data(idx, TimelineModel::Body)
|
||||
.toString()
|
||||
.contains(contentFilter, Qt::CaseInsensitive)) {
|
||||
if (
|
||||
!contentFilter.isEmpty()
|
||||
&& !s->data(idx, TimelineModel::Body).toString().contains(contentFilter, Qt::CaseInsensitive)
|
||||
&& !s->data(idx, TimelineModel::UserName).toString().contains(contentFilter, Qt::CaseInsensitive)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -2073,10 +2073,12 @@ TimelineModel::cacheMedia(const QString &eventId,
|
|||
|
||||
const auto url = mxcUrl.toStdString();
|
||||
const auto name = QString(mxcUrl).remove(QStringLiteral("mxc://"));
|
||||
QFileInfo filename(
|
||||
QStringLiteral("%1/media_cache/%2.%3")
|
||||
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation), name, suffix));
|
||||
if (QDir::cleanPath(name) != name) {
|
||||
QFileInfo filename(QStringLiteral("%1/media_cache/%2.%3")
|
||||
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation),
|
||||
QString::fromUtf8(name.toUtf8().toBase64(
|
||||
QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)),
|
||||
suffix));
|
||||
if (QDir::cleanPath(filename.filePath()) != filename.filePath()) {
|
||||
nhlog::net()->warn("mxcUrl '{}' is not safe, not downloading file", url);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -53,10 +53,12 @@ MxcAnimatedImage::startDownload()
|
|||
|
||||
const auto url = mxcUrl.toStdString();
|
||||
const auto name = QString(mxcUrl).remove(QStringLiteral("mxc://"));
|
||||
QFileInfo filename(
|
||||
QStringLiteral("%1/media_cache/media/%2.%3")
|
||||
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation), name, suffix));
|
||||
if (QDir::cleanPath(name) != name) {
|
||||
QFileInfo filename(QStringLiteral("%1/media_cache/media/%2.%3")
|
||||
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation),
|
||||
QString::fromUtf8(name.toUtf8().toBase64(
|
||||
QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)),
|
||||
suffix));
|
||||
if (QDir::cleanPath(filename.filePath()) != filename.filePath()) {
|
||||
nhlog::net()->warn("mxcUrl '{}' is not safe, not downloading file", url);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -96,10 +96,12 @@ MxcMediaProxy::startDownload(bool onlyCached)
|
|||
|
||||
const auto url = mxcUrl.toStdString();
|
||||
const auto name = QString(mxcUrl).remove(QStringLiteral("mxc://"));
|
||||
QFileInfo filename(
|
||||
QStringLiteral("%1/media_cache/media/%2.%3")
|
||||
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation), name, suffix));
|
||||
if (QDir::cleanPath(name) != name) {
|
||||
QFileInfo filename(QStringLiteral("%1/media_cache/media/%2.%3")
|
||||
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation),
|
||||
QString::fromUtf8(name.toUtf8().toBase64(
|
||||
QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)),
|
||||
suffix));
|
||||
if (QDir::cleanPath(filename.filePath()) != filename.filePath()) {
|
||||
nhlog::net()->warn("mxcUrl '{}' is not safe, not downloading file", url);
|
||||
return;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue