Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
diamondburned
GitHub Repository: diamondburned/gtkcord4
Path: blob/main/internal/messages/summary.go
366 views
1
package messages
2
3
import (
4
"fmt"
5
"html"
6
"strings"
7
8
"github.com/diamondburned/arikawa/v3/discord"
9
"github.com/diamondburned/arikawa/v3/gateway"
10
"github.com/diamondburned/gotk4/pkg/gtk/v4"
11
"github.com/diamondburned/gotk4/pkg/pango"
12
"github.com/diamondburned/gotkit/app/locale"
13
"github.com/diamondburned/gotkit/gtkutil/cssutil"
14
"libdb.so/dissent/internal/gtkcord"
15
)
16
17
var summaryCSS = cssutil.Applier("message-summary-row", `
18
.message-summary-row {
19
margin: 0.25em 0;
20
}
21
.message-summary-symbol {
22
font-size: 2em;
23
min-width: calc((9px * 2) + {$message_avatar_size});
24
min-height: calc(1em + 0.7rem);
25
}
26
.message-summary-title {
27
margin: 0.1em 0;
28
}
29
`)
30
31
type messageSummaryWidget struct {
32
key messageKey
33
header *gtk.Label
34
title *gtk.Label
35
body *gtk.Label
36
}
37
38
func (v *View) updateSummaries(summaries []gateway.ConversationSummary) {
39
if !showSummaries.Value() {
40
return
41
}
42
43
for _, summary := range summaries {
44
v.appendSummary(summary)
45
}
46
47
for id, sw := range v.summaries {
48
if _, ok := v.rows[sw.key]; !ok {
49
delete(v.summaries, id)
50
}
51
}
52
}
53
54
func (v *View) appendSummary(summary gateway.ConversationSummary) (messageKey, bool) {
55
// Skip this summary if the EndID isn't in the current channel.
56
endMsg, ok := v.rows[messageKeyID(summary.EndID)]
57
if !ok {
58
return "", false
59
}
60
61
if v.summaries == nil {
62
v.summaries = make(map[discord.Snowflake]messageSummaryWidget, 2)
63
}
64
65
sw, ok := v.summaries[summary.ID]
66
if !ok {
67
header := gtk.NewLabel("")
68
header.AddCSSClass("message-summary-header")
69
header.SetEllipsize(pango.EllipsizeEnd)
70
header.SetXAlign(0)
71
header.SetHExpand(true)
72
73
title := gtk.NewLabel("")
74
title.AddCSSClass("message-summary-title")
75
title.SetWrap(true)
76
title.SetXAlign(0)
77
title.SetHExpand(true)
78
79
body := gtk.NewLabel("")
80
body.AddCSSClass("message-summary-body")
81
body.SetWrap(true)
82
body.SetXAlign(0)
83
body.SetHExpand(true)
84
85
sw = messageSummaryWidget{
86
key: messageKeyLocal(),
87
header: header,
88
title: title,
89
body: body,
90
}
91
92
right := gtk.NewBox(gtk.OrientationVertical, 0)
93
right.AddCSSClass("message-summary-right")
94
right.SetHExpand(true)
95
right.Append(header)
96
right.Append(title)
97
right.Append(body)
98
99
symbol := gtk.NewLabel("∗")
100
symbol.SetXAlign(0.5)
101
symbol.SetYAlign(0.5)
102
symbol.AddCSSClass("message-summary-symbol")
103
symbol.SetTooltipText(locale.Get("Summary of this conversation"))
104
105
box := gtk.NewBox(gtk.OrientationHorizontal, 0)
106
box.Append(symbol)
107
box.Append(right)
108
109
row := gtk.NewListBoxRow()
110
row.SetName(string(sw.key))
111
row.SetChild(box)
112
summaryCSS(row)
113
114
// TODO: highlight relevant messages when hovered.
115
// This will be a lot easier than just inserting this at the right
116
// position, probably.
117
118
v.summaries[summary.ID] = sw
119
v.rows[sw.key] = messageRow{
120
ListBoxRow: row,
121
info: messageInfo{
122
author: messageAuthor{userID: discord.NullUserID},
123
timestamp: discord.Timestamp(summary.EndID.Time()),
124
},
125
}
126
v.List.Insert(row, endMsg.Index()+1)
127
128
reset := v.surroundingMessagesResetter(sw.key)
129
reset()
130
}
131
132
state := gtkcord.FromContext(v.ctx).Offline()
133
markups := formatSummary(state, v.guildID, summary)
134
135
// Make everything small.
136
markups.header = "<small>" + markups.header + "</small>"
137
markups.title = "<small>" + markups.title + "</small>"
138
markups.body = "<small>" + markups.body + "</small>"
139
140
if markups.header != "" {
141
sw.header.SetMarkup(markups.header)
142
sw.header.SetVisible(false)
143
} else {
144
sw.header.SetVisible(true)
145
}
146
147
sw.title.SetMarkup(markups.title)
148
sw.body.SetMarkup(markups.body)
149
150
return sw.key, true
151
}
152
153
type summaryMarkups struct {
154
header string
155
title string
156
body string
157
}
158
159
func formatSummary(state *gtkcord.State, guildID discord.GuildID, summary gateway.ConversationSummary) summaryMarkups {
160
var markups summaryMarkups
161
162
if len(summary.People) > 0 {
163
var header strings.Builder
164
header.WriteString("<small>")
165
166
names := make([]string, 0, min(3, len(summary.People)))
167
for _, uID := range summary.People {
168
name, _ := state.MemberDisplayName(guildID, uID)
169
if name != "" {
170
name = "<b>" + html.EscapeString(name) + "</b>"
171
} else {
172
name = locale.Get("?")
173
}
174
names = append(names, name)
175
if len(names) == 3 {
176
break
177
}
178
}
179
180
switch len(summary.People) {
181
case 1:
182
header.WriteString(names[0])
183
case 2:
184
header.WriteString(names[0])
185
header.WriteString(locale.Get(" and "))
186
header.WriteString(names[1])
187
case 3:
188
header.WriteString(names[0])
189
header.WriteString(locale.Get(", "))
190
header.WriteString(names[1])
191
header.WriteString(locale.Get(" and "))
192
header.WriteString(names[2])
193
default:
194
header.WriteString(names[0])
195
header.WriteString(locale.Get(", "))
196
header.WriteString(names[1])
197
header.WriteString(locale.Get(" and "))
198
header.WriteString(locale.Sprintf("%d others", len(summary.People)-2))
199
}
200
201
header.WriteString(":</small>")
202
markups.header = header.String()
203
}
204
205
markups.title = "<b>" + html.EscapeString(summary.Topic) + "</b>"
206
markups.body = html.EscapeString(summary.ShortSummary)
207
208
return markups
209
}
210
211
var _ = cssutil.WriteCSS(`
212
.message-summaries-popover list {
213
background-color: transparent;
214
}
215
.message-summaries-popover > contents {
216
padding: 0;
217
}
218
.message-summary-item:first-child {
219
margin-top: 0.5em;
220
}
221
.message-summary-item:last-child {
222
margin-bottom: 0.5em;
223
}
224
.message-summary-item {
225
padding: 0.25em 0.5em;
226
margin: 0.25em 0;
227
}
228
.message-summary-item:not(:last-child) {
229
border-bottom: 1px solid @borders;
230
}
231
.message-summary-item label:nth-child(2) {
232
margin-top: 0.1em;
233
}
234
`)
235
236
func (v *View) initSummariesPopover(popover *gtk.Popover) bool {
237
popover.AddCSSClass("message-summaries-popover")
238
popover.SetOverflow(gtk.OverflowHidden)
239
state := gtkcord.FromContext(v.ctx).Offline()
240
241
summaries := state.SummaryState.Summaries(v.chID)
242
if len(summaries) == 0 {
243
placeholder := gtk.NewLabel(locale.Get("No message summaries available."))
244
placeholder.AddCSSClass("message-summaries-placeholder")
245
246
popover.SetChild(placeholder)
247
return true
248
}
249
250
list := gtk.NewBox(gtk.OrientationVertical, 0)
251
list.AddCSSClass("message-summaries-list")
252
list.SetHExpand(true)
253
254
for _, summary := range summaries {
255
markups := formatSummary(state, v.guildID, summary)
256
257
header := gtk.NewLabel(fmt.Sprintf(
258
`<span size="x-small">%s</span>`+"\n%s",
259
locale.TimeAgo(summary.EndID.Time()),
260
markups.header,
261
))
262
263
bottom := gtk.NewLabel(fmt.Sprintf(
264
"%s\n%s",
265
markups.title,
266
markups.body,
267
))
268
269
for _, label := range []*gtk.Label{header, bottom} {
270
label.AddCSSClass("popover-label")
271
label.SetXAlign(0)
272
label.SetHExpand(true)
273
label.SetWrap(true)
274
label.SetWrapMode(pango.WrapWordChar)
275
label.SetUseMarkup(true)
276
}
277
278
box := gtk.NewBox(gtk.OrientationVertical, 0)
279
box.AddCSSClass("message-summary-item")
280
box.Append(header)
281
box.Append(bottom)
282
283
// TODO: add little user icons for participants.
284
// we should probably use a grid for that.
285
286
// TODO: scroll to message on click.
287
list.Append(box)
288
}
289
290
scroll := gtk.NewScrolledWindow()
291
scroll.SetPropagateNaturalWidth(true)
292
scroll.SetPropagateNaturalHeight(true)
293
scroll.SetMaxContentHeight(500)
294
scroll.SetMaxContentWidth(300)
295
scroll.SetPolicy(gtk.PolicyNever, gtk.PolicyAutomatic)
296
scroll.SetChild(list)
297
298
popover.SetChild(scroll)
299
return true
300
}
301
302