1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
|
// Copyright (C) 2026 Danilo M. <danix@danix.xyz>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
import QtQuick
import QtQuick.Controls
import ".."
// The history panel, over the transcript. Empty search: every conversation,
// newest first. Text in the field: the daemon's full-text results, with the
// matched words in bold from its snippet markers.
Rectangle {
id: panel
property var canvas
color: Qt.alpha(Theme.base, 0.97)
radius: 8
onVisibleChanged: if (visible) { field.text = ""; field.forceActiveFocus(); }
MouseArea { anchors.fill: parent } // keep clicks off the transcript below
TextField {
id: field
anchors { top: parent.top; left: parent.left; right: parent.right }
height: 32
placeholderText: "Search conversations"
color: Theme.text
font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 }
background: Rectangle {
radius: 6
color: Qt.alpha(Theme.surface, 0.9)
border.width: 1
border.color: Qt.alpha(Theme.text, 0.15)
}
onTextChanged: debounce.restart()
}
// One query per pause in typing, not per key.
Timer {
id: debounce
interval: 150
onTriggered: field.text.trim() === "" ? panel.canvas.send({ type: "list" })
: panel.canvas.send({ type: "search", q: field.text })
}
ListView {
id: rows
anchors { top: field.bottom; topMargin: 8; left: parent.left; right: parent.right; bottom: parent.bottom }
clip: true
spacing: 4
boundsBehavior: Flickable.StopAtBounds
model: field.text.trim() === "" ? panel.canvas.convs : panel.canvas.results
delegate: Rectangle {
id: r
required property var modelData
width: rows.width
height: col.height + 12
radius: 6
color: hover.hovered ? Qt.alpha(Theme.surface, 0.9) : "transparent"
border.width: r.modelData.id === panel.canvas.convId ? 1 : 0
border.color: Qt.alpha(Theme.accent, 0.5)
HoverHandler { id: hover }
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: { panel.canvas.send({ type: "open", id: r.modelData.id }); panel.canvas.historyOpen = false; }
}
Column {
id: col
anchors { left: parent.left; right: del.left; verticalCenter: parent.verticalCenter; margins: 8 }
spacing: 2
Text {
width: col.width
text: r.modelData.title
elide: Text.ElideRight
color: Theme.text
font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 }
}
Text {
width: col.width
// A list row has its date; a search row its snippet.
text: r.modelData.snippet !== undefined
// Backticks go: a match inside a code span would
// show its ** markers literally.
? r.modelData.snippet.replace(/\s+/g, " ").replace(/`/g, "")
: new Date(r.modelData.updated * 1000).toLocaleString(Qt.locale(), Locale.ShortFormat)
textFormat: r.modelData.snippet !== undefined ? Text.MarkdownText : Text.PlainText
elide: Text.ElideRight
maximumLineCount: 1
color: Theme.subtext
font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 }
}
}
// Two clicks: the first arms it for 3 seconds, as the clipboard's
// clear-all does.
Button {
id: del
anchors { right: parent.right; rightMargin: 6; verticalCenter: parent.verticalCenter }
text: armed.running ? "Sure?" : "Delete"
danger: true
onClicked: {
if (!armed.running) { armed.restart(); return; }
armed.stop();
panel.canvas.send({ type: "delete", id: r.modelData.id });
}
Timer { id: armed; interval: 3000 }
}
}
Text {
anchors.centerIn: parent
visible: rows.count === 0
text: field.text.trim() === "" ? "No conversations yet." : "Nothing found."
color: Theme.overlay
font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 }
}
}
}
|