diff --git a/resources/icons/ui/bookmark-disabled.svg b/resources/icons/ui/bookmark-disabled.svg
new file mode 100644
index 00000000..00d47430
--- /dev/null
+++ b/resources/icons/ui/bookmark-disabled.svg
@@ -0,0 +1 @@
+<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M3.28 2.22a.75.75 0 1 0-1.06 1.06l2.788 2.788a3.3 3.3 0 0 0-.005.181v14.996a.75.75 0 0 0 1.188.609L12 17.673l5.812 4.18A.75.75 0 0 0 19 21.246v-1.183l1.718 1.718a.75.75 0 0 0 1.061-1.06L3.28 2.22Zm14.221 16.342v1.22L12.44 16.14a.75.75 0 0 0-.876 0l-5.061 3.642V7.563L17.5 18.562ZM17.501 6.25v8.07l1.5 1.5V6.25A3.25 3.25 0 0 0 15.751 3H8.253c-.595 0-1.153.16-1.633.438l1.133 1.133a1.75 1.75 0 0 1 .5-.072h7.498c.967 0 1.75.784 1.75 1.75Z" fill="#212121"/></svg>
\ No newline at end of file
diff --git a/resources/icons/ui/bookmark.svg b/resources/icons/ui/bookmark.svg
new file mode 100644
index 00000000..59fec384
--- /dev/null
+++ b/resources/icons/ui/bookmark.svg
@@ -0,0 +1 @@
+<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M6.19 21.854a.75.75 0 0 1-1.188-.61V6.25a3.25 3.25 0 0 1 3.25-3.25h7.499A3.25 3.25 0 0 1 19 6.249v14.996a.75.75 0 0 1-1.188.609l-5.811-4.181-5.812 4.18ZM17.5 6.249a1.75 1.75 0 0 0-1.75-1.75H8.253a1.75 1.75 0 0 0-1.75 1.75v13.532l5.062-3.64a.75.75 0 0 1 .876 0l5.06 3.64V6.25Z" fill="#212121"/></svg>
\ No newline at end of file
diff --git a/resources/icons/ui/dismiss_thread.svg b/resources/icons/ui/dismiss_thread.svg
new file mode 100644
index 00000000..3285d31c
--- /dev/null
+++ b/resources/icons/ui/dismiss_thread.svg
@@ -0,0 +1,4 @@
+<svg width="96" height="96" version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M7 18h13a.75.75 0 0 1 .102 1.493L20 19.5H7a.75.75 0 0 1-.102-1.493L7 18Zm10-3a.75.75 0 0 1 .102 1.493L17 16.5H4a.75.75 0 0 1-.102-1.493L4 15h13Zm3-3a.75.75 0 0 1 .102 1.493L20 13.5H7a.75.75 0 0 1-.102-1.493L7 12h13ZM6 5a2.75 2.75 0 0 1 2.55 1.717.75.75 0 0 1-1.346.655l-.045-.091A1.25 1.25 0 1 0 6 9h11.5a.75.75 0 0 1 .102 1.493l-.102.007H6A2.75 2.75 0 0 1 6 5Zm14 1a.75.75 0 0 1 .102 1.493L20 7.5h-9a.75.75 0 0 1-.102-1.493L11 6h9Z" fill="#212121"/>
+ <path d="m3.2035 3.2035c0.29289-0.2929 0.76777-0.2929 1.0607 0 16.532 16.532 0 0 16.532 16.532 0.29295 0.29288 0.29295 0.76778 0 1.0606-0.29288 0.29288-0.7677 0.29288-1.0606 0-16.532-16.532-0.11015-0.11011-16.532-16.532-0.2929-0.29289-0.2929-0.76777 1e-7 -1.0607z" fill="#212121" stroke-width=".75"/>
+</svg>
diff --git a/resources/icons/ui/needle.svg b/resources/icons/ui/needle.svg
new file mode 100644
index 00000000..a210c958
--- /dev/null
+++ b/resources/icons/ui/needle.svg
@@ -0,0 +1,6 @@
+<svg width="32" height="32" version="1.1" viewBox="0 0 8.4667 8.4667" xmlns="http://www.w3.org/2000/svg">
+ <g transform="rotate(17.066 .26297 6.1037)" stroke="#000" stroke-linejoin="round">
+ <path d="m1.0583 0.26458c-0.79375 0-0.79375 0.79375-0.79375 0.79375l0.79375 6.35 0.79375-6.35s-9e-7 -0.79375-0.79375-0.79375zm0 0.26458a0.52917 0.52917 0 0 1 0.52917 0.52917 0.52917 0.52917 0 0 1-0.52917 0.52917 0.52917 0.52917 0 0 1-0.52917-0.52917 0.52917 0.52917 0 0 1 0.52917-0.52917z" fill-rule="evenodd" stroke-width=".026458"/>
+ <path d="m1.0583 1.0583s4.4594-1.5552 5.2917 0c0.60018 1.1215-2.242 1.9092-2.1167 3.175 0.049161 0.49648 1.0583 0.55943 1.0583 1.0583s-1.0583 1.0583-1.0583 1.0583" fill="none" stroke-linecap="round" stroke-width=".52917"/>
+ </g>
+</svg>
diff --git a/resources/icons/ui/new-thread.svg b/resources/icons/ui/new-thread.svg
new file mode 100644
index 00000000..f79f2d99
--- /dev/null
+++ b/resources/icons/ui/new-thread.svg
@@ -0,0 +1 @@
+<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M17.5 12a5.5 5.5 0 1 1 0 11 5.5 5.5 0 0 1 0-11Zm0 2-.09.007a.5.5 0 0 0-.402.402L17 14.5V17L14.498 17l-.09.008a.5.5 0 0 0-.402.402l-.008.09.008.09a.5.5 0 0 0 .402.402l.09.008H17v2.503l.008.09a.5.5 0 0 0 .402.402l.09.008.09-.008a.5.5 0 0 0 .402-.402l.008-.09V18l2.504.001.09-.008a.5.5 0 0 0 .402-.402l.008-.09-.008-.09a.5.5 0 0 0-.403-.402l-.09-.008H18v-2.5l-.008-.09a.5.5 0 0 0-.402-.403L17.5 14Zm-6.481 4c.04.519.14 1.021.294 1.5H7a.75.75 0 0 1-.102-1.493L7 18h4.019Zm.479-3c-.198.475-.34.977-.422 1.5H4a.75.75 0 0 1-.102-1.493L4 15h7.498Zm2.537-3a6.534 6.534 0 0 0-1.659 1.5H7a.75.75 0 0 1-.102-1.493L7 12h7.035ZM6 5a2.75 2.75 0 0 1 2.55 1.717.75.75 0 0 1-1.346.655l-.045-.091A1.25 1.25 0 1 0 6 9h11.5a.75.75 0 0 1 .102 1.493l-.102.007H6A2.75 2.75 0 0 1 6 5Zm14 1a.75.75 0 0 1 .102 1.493L20 7.5h-9a.75.75 0 0 1-.102-1.493L11 6h9Z" fill="#212121"/></svg>
\ No newline at end of file
diff --git a/resources/icons/ui/thread.svg b/resources/icons/ui/thread.svg
new file mode 100644
index 00000000..81eab2a0
--- /dev/null
+++ b/resources/icons/ui/thread.svg
@@ -0,0 +1 @@
+<svg width="96" height="96" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M7 18h13a.75.75 0 0 1 .102 1.493L20 19.5H7a.75.75 0 0 1-.102-1.493L7 18Zm10-3a.75.75 0 0 1 .102 1.493L17 16.5H4a.75.75 0 0 1-.102-1.493L4 15h13Zm3-3a.75.75 0 0 1 .102 1.493L20 13.5H7a.75.75 0 0 1-.102-1.493L7 12h13ZM6 5a2.75 2.75 0 0 1 2.55 1.717.75.75 0 0 1-1.346.655l-.045-.091A1.25 1.25 0 1 0 6 9h11.5a.75.75 0 0 1 .102 1.493l-.102.007H6A2.75 2.75 0 0 1 6 5Zm14 1a.75.75 0 0 1 .102 1.493L20 7.5h-9a.75.75 0 0 1-.102-1.493L11 6h9Z" fill="#212121"/></svg>
diff --git a/resources/qml/MessageInput.qml b/resources/qml/MessageInput.qml
index 6848f85c..6174a6c0 100644
--- a/resources/qml/MessageInput.qml
+++ b/resources/qml/MessageInput.qml
@@ -378,6 +378,10 @@ Rectangle {
messageInput.forceActiveFocus();
}
+ function onThreadChanged() {
+ messageInput.forceActiveFocus();
+ }
+
ignoreUnknownSignals: true
target: room
}
diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml
index 2e1d9398..29e5ccf5 100644
--- a/resources/qml/MessageView.qml
+++ b/resources/qml/MessageView.qml
@@ -116,9 +116,7 @@ Item {
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Edit")
onClicked: {
- if (row.model.isEditable)
- chat.model.editAction(row.model.eventId);
-
+ if (row.model.isEditable) chat.model.edit = row.model.eventId;
}
}
@@ -140,6 +138,19 @@ Item {
}
ImageButton {
+ id: threadButton
+
+ visible: chat.model ? chat.model.permissions.canSend(MtxEvent.TextMessage) : false
+ width: 16
+ hoverEnabled: true
+ image: row.model.threadId ? ":/icons/icons/ui/thread.svg" : ":/icons/icons/ui/new-thread.svg"
+ ToolTip.visible: hovered
+ ToolTip.delay: Nheko.tooltipDelay
+ ToolTip.text: row.model.threadId ? qsTr("Reply in thread") : qsTr("New thread")
+ onClicked: chat.model.thread = (row.model.threadId || row.model.eventId)
+ }
+
+ ImageButton {
id: replyButton
visible: chat.model ? chat.model.permissions.canSend(MtxEvent.TextMessage) : false
@@ -149,7 +160,7 @@ Item {
ToolTip.visible: hovered
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Reply")
- onClicked: chat.model.replyAction(row.model.eventId)
+ onClicked: chat.model.reply = row.model.eventId
}
ImageButton {
@@ -161,7 +172,7 @@ Item {
ToolTip.visible: hovered
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Options")
- onClicked: messageContextMenu.show(row.model.eventId, row.model.type, row.model.isSender, row.model.isEncrypted, row.model.isEditable, "", row.model.body, optionsButton)
+ onClicked: messageContextMenu.show(row.model.eventId, row.model.threadId, row.model.type, row.model.isSender, row.model.isEncrypted, row.model.isEditable, "", row.model.body, optionsButton)
}
}
@@ -196,8 +207,10 @@ Item {
room.input.declineUploads();
else if(chat.model.reply)
chat.model.reply = undefined;
- else
+ else if (chat.model.edit)
chat.model.edit = undefined;
+ else
+ chat.model.thread = undefined
TimelineManager.focusMessageInput();
}
}
@@ -383,6 +396,7 @@ Item {
required property bool isStateEvent
required property bool previousMessageIsStateEvent
required property string replyTo
+ required property string threadId
required property string userId
required property string roomTopic
required property string roomName
@@ -448,6 +462,7 @@ Item {
isEdited: wrapper.isEdited
isStateEvent: wrapper.isStateEvent
replyTo: wrapper.replyTo
+ threadId: wrapper.threadId
userId: wrapper.userId
userName: wrapper.userName
roomTopic: wrapper.roomTopic
@@ -554,6 +569,7 @@ Item {
id: messageContextMenu
property string eventId
+ property string threadId
property string link
property string text
property int eventType
@@ -561,8 +577,9 @@ Item {
property bool isEditable
property bool isSender
- function show(eventId_, eventType_, isSender_, isEncrypted_, isEditable_, link_, text_, showAt_) {
+ function show(eventId_, threadId_, eventType_, isSender_, isEncrypted_, isEditable_, link_, text_, showAt_) {
eventId = eventId_;
+ threadId = threadId_;
eventType = eventType_;
isEncrypted = isEncrypted_;
isEditable = isEditable_;
@@ -623,14 +640,21 @@ Item {
Platform.MenuItem {
visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false
text: qsTr("Repl&y")
- onTriggered: room.replyAction(messageContextMenu.eventId)
+ onTriggered: room.reply = (messageContextMenu.eventId)
}
Platform.MenuItem {
visible: messageContextMenu.isEditable && (room ? room.permissions.canSend(MtxEvent.TextMessage) : false)
enabled: visible
text: qsTr("&Edit")
- onTriggered: room.editAction(messageContextMenu.eventId)
+ onTriggered: room.edit = (messageContextMenu.eventId)
+ }
+
+ Platform.MenuItem {
+ visible: (room ? room.permissions.canSend(MtxEvent.TextMessage) : false)
+ enabled: visible
+ text: qsTr("&Thread")
+ onTriggered: room.thread = (messageContextMenu.threadId || messageContextMenu.eventId)
}
Platform.MenuItem {
@@ -641,7 +665,7 @@ Item {
}
Platform.MenuItem {
- text: qsTr("Read receip&ts")
+ text: qsTr("&Read receipts")
onTriggered: room.showReadReceipts(messageContextMenu.eventId)
}
diff --git a/resources/qml/ReplyPopup.qml b/resources/qml/ReplyPopup.qml
index 914f5d33..ec1de346 100644
--- a/resources/qml/ReplyPopup.qml
+++ b/resources/qml/ReplyPopup.qml
@@ -13,9 +13,9 @@ Rectangle {
id: replyPopup
Layout.fillWidth: true
- visible: room && (room.reply || room.edit)
+ visible: room && (room.reply || room.edit || room.thread)
// Height of child, plus margins, plus border
- implicitHeight: (room && room.reply ? replyPreview.height : closeEditButton.height) + Nheko.paddingSmall
+ implicitHeight: (room && room.reply ? replyPreview.height : Math.max(closeEditButton.height, closeThreadButton.height)) + Nheko.paddingSmall
color: Nheko.colors.window
z: 3
@@ -71,7 +71,7 @@ Rectangle {
id: closeEditButton
visible: room && room.edit
- anchors.right: parent.right
+ anchors.right: closeThreadButton.left
anchors.margins: 8
anchors.top: parent.top
hoverEnabled: true
@@ -83,4 +83,21 @@ Rectangle {
onClicked: room.edit = undefined
}
+ ImageButton {
+ id: closeThreadButton
+
+ visible: room && room.thread
+ anchors.right: parent.right
+ anchors.margins: 8
+ anchors.top: parent.top
+ hoverEnabled: true
+ buttonTextColor: TimelineManager.userColor(room.thread, Nheko.colors.base)
+ image: ":/icons/icons/ui/dismiss_thread.svg"
+ width: 22
+ height: 22
+ ToolTip.visible: closeThreadButton.hovered
+ ToolTip.text: qsTr("Cancel Thread")
+ onClicked: room.thread = undefined
+ }
+
}
diff --git a/resources/qml/TimelineRow.qml b/resources/qml/TimelineRow.qml
index dc640099..173c3fb5 100644
--- a/resources/qml/TimelineRow.qml
+++ b/resources/qml/TimelineRow.qml
@@ -33,6 +33,7 @@ AbstractButton {
required property bool isEdited
required property bool isStateEvent
required property string replyTo
+ required property string threadId
required property string userId
required property string userName
required property string roomTopic
@@ -58,14 +59,14 @@ AbstractButton {
// this looks better without margins
TapHandler {
acceptedButtons: Qt.RightButton
- onSingleTapped: messageContextMenu.show(eventId, type, isSender, isEncrypted, isEditable, contentItem.child.hoveredLink, contentItem.child.copyText)
+ onSingleTapped: messageContextMenu.show(eventId, threadId, type, isSender, isEncrypted, isEditable, contentItem.child.hoveredLink, contentItem.child.copyText)
gesturePolicy: TapHandler.ReleaseWithinBounds
acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad
}
}
- onPressAndHold: messageContextMenu.show(eventId, type, isSender, isEncrypted, isEditable, contentItem.child.hoveredLink, contentItem.child.copyText)
+ onPressAndHold: messageContextMenu.show(eventId, threadId, type, isSender, isEncrypted, isEditable, contentItem.child.hoveredLink, contentItem.child.copyText)
onDoubleClicked: chat.model.reply = eventId
DragHandler {
@@ -229,6 +230,20 @@ AbstractButton {
anchors.verticalCenter: ts.verticalCenter
}
+ ImageButton {
+ visible: threadId
+ Layout.alignment: Qt.AlignRight | Qt.AlignTop
+ height: parent.iconSize
+ width: parent.iconSize
+ image: ":/icons/icons/ui/thread.svg"
+ buttonTextColor: TimelineManager.userColor(threadId, Nheko.colors.base)
+ ToolTip.visible: hovered
+ ToolTip.delay: Nheko.tooltipDelay
+ ToolTip.text: qsTr("Part of a thread")
+ anchors.verticalCenter: ts.verticalCenter
+ onClicked: room.thread = threadId
+ }
+
Image {
visible: isEdited || eventId == chat.model.edit
Layout.alignment: Qt.AlignRight | Qt.AlignTop
diff --git a/resources/res.qrc b/resources/res.qrc
index 7affe702..595dd5a7 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -23,6 +23,9 @@
<file>icons/ui/microphone-mute.svg</file>
<file>icons/ui/microphone-unmute.svg</file>
<file>icons/ui/music.svg</file>
+ <file>icons/ui/thread.svg</file>
+ <file>icons/ui/new-thread.svg</file>
+ <file>icons/ui/dismiss_thread.svg</file>
<file>icons/ui/options.svg</file>
<file>icons/ui/pause-symbol.svg</file>
<file>icons/ui/people.svg</file>
|