commit 3eac6eb
sewn
·
2026-07-30 21:48:21 +0000 UTC
parent 4edb6ca
Add displaying +draft/react and unreactions
5 files changed,
+143,
-10
M
app.go
+25,
-0
1@@ -1558,6 +1558,29 @@ func (app *App) handleIRCEvent(netID string, ev interface{}) {
2 bounds, hasBounds := app.messageBounds[bk]
3 boundsNew := bounds
4 for _, m := range ev.Messages {
5+ if re, ok := m.(irc.ReactEvent); ok {
6+ found := false
7+ for i := len(linesAfter) - 1; !found && i >= 0; i-- {
8+ if linesAfter[i].ID == re.ID {
9+ panic("after")
10+ linesAfter[i].ApplyReact(re.User, re.React, re.Removal)
11+ found = true
12+ }
13+ }
14+ if found {
15+ continue
16+ }
17+ for i := len(linesBefore) - 1; !found && i >= 0; i-- {
18+ if linesBefore[i].ID == re.ID {
19+ linesBefore[i].ApplyReact(re.User, re.React, re.Removal)
20+ found = true
21+ }
22+ }
23+ if !found {
24+ app.win.ApplyReact(netID, ev.Target, re.ID, re.User, re.React, re.Removal)
25+ }
26+ continue
27+ }
28 var line ui.Line
29 switch ev := m.(type) {
30 case irc.MessageEvent:
31@@ -1611,6 +1634,8 @@ func (app *App) handleIRCEvent(netID string, ev interface{}) {
32 lines = append(lines, line)
33 }
34 app.win.AddLines("", ui.Overlay, lines, nil)
35+ case irc.ReactEvent:
36+ app.win.ApplyReact(netID, ev.Target, ev.ID, ev.User, ev.React, ev.Removal)
37 case irc.ReadEvent:
38 app.win.SetRead(netID, ev.Target, ev.Timestamp)
39 case irc.MetadataChangeEvent:
+8,
-0
1@@ -99,6 +99,14 @@ type MessageEvent struct {
2 Time time.Time
3 }
4
5+type ReactEvent struct {
6+ ID string
7+ User string
8+ Target string
9+ Removal bool
10+ React string
11+}
12+
13 type ListItem struct {
14 Channel string
15 Count string
+16,
-10
1@@ -1447,10 +1447,6 @@ func (s *Session) handleMessageRegistered(msg Message, playback bool) (Event, er
2 }
3 return ev, nil
4 case "TAGMSG":
5- if playback {
6- return nil, nil
7- }
8-
9 var target string
10 if err := msg.ParseParams(&target); err != nil {
11 return nil, err
12@@ -1459,12 +1455,8 @@ func (s *Session) handleMessageRegistered(msg Message, playback bool) (Event, er
13 targetCf := s.casemap(target)
14 nickCf := s.casemap(msg.Prefix.Name)
15
16- if s.IsMe(msg.Prefix.Name) {
17- // TAGMSG from self
18- break
19- }
20-
21- if t, ok := msg.Tags["+typing"]; ok {
22+ // Ignore +typing from self
23+ if t, ok := msg.Tags["+typing"]; ok && !playback && !s.IsMe(msg.Prefix.Name) {
24 switch t {
25 case "active":
26 s.typings.Active(targetCf, nickCf)
27@@ -1472,6 +1464,20 @@ func (s *Session) handleMessageRegistered(msg Message, playback bool) (Event, er
28 s.typings.Done(targetCf, nickCf)
29 }
30 }
31+
32+ if id := msg.ReplyTo(); id != "" {
33+ if c, ok := s.channels[targetCf]; ok {
34+ target = c.Name
35+ } else if s.IsMe(target) {
36+ target = msg.Prefix.Name
37+ }
38+ if react, ok := msg.Tags["+draft/react"]; ok {
39+ return ReactEvent{ID: id, User: msg.Prefix.Name, Target: target, React: react}, nil
40+ }
41+ if react, ok := msg.Tags["+draft/unreact"]; ok {
42+ return ReactEvent{ID: id, User: msg.Prefix.Name, Target: target, React: react, Removal: true}, nil
43+ }
44+ }
45 case "BATCH":
46 var id string
47 if err := msg.ParseParams(&id); err != nil {
+90,
-0
1@@ -4,6 +4,7 @@ import (
2 "fmt"
3 "math"
4 "sort"
5+ "strconv"
6 "strings"
7 "time"
8
9@@ -53,6 +54,7 @@ type Line struct {
10 ID string
11 ReplyTo string
12 Reply *Reply
13+ Reacts []React
14
15 splitPoints []point
16 width int
17@@ -64,6 +66,11 @@ type Reply struct {
18 Body StyledString
19 }
20
21+type React struct {
22+ React string
23+ Users []string
24+}
25+
26 func (l *Line) IsZero() bool {
27 return l.Body.string == ""
28 }
29@@ -210,6 +217,9 @@ func (l *Line) Rows(vx *Vaxis, width int) int {
30 if l.Reply != nil {
31 n++
32 }
33+ if len(l.Reacts) > 0 {
34+ n++
35+ }
36 return n
37 }
38
39@@ -568,6 +578,53 @@ func (bs *BufferList) AddLine(netID, title string, line Line) {
40 }
41 }
42
43+func (l *Line) ApplyReact(user, react string, removal bool) {
44+ for i := range l.Reacts {
45+ if l.Reacts[i].React != react {
46+ continue
47+ }
48+
49+ us := l.Reacts[i].Users
50+ for i := range us {
51+ if us[i] != user {
52+ continue
53+ }
54+
55+ if removal {
56+ us = append(us[:i], us[i+1:]...)
57+ } else {
58+ // Already reacted
59+ return
60+ }
61+ break
62+ }
63+ if !removal {
64+ us = append(us, user)
65+ }
66+ l.Reacts[i].Users = us
67+ return
68+ }
69+
70+ // Reaction does not exist in this message
71+ if removal {
72+ return
73+ }
74+ l.Reacts = append(l.Reacts, React{React: react, Users: []string{user}})
75+}
76+
77+func (bs *BufferList) ApplyReact(netID, title, id, user, react string, removal bool) {
78+ _, b := bs.at(netID, title)
79+ if b == nil {
80+ return
81+ }
82+ for i := len(b.lines) - 1; i >= 0; i-- {
83+ if b.lines[i].ID == id {
84+ b.lines[i].ApplyReact(user, react, removal)
85+ return
86+ }
87+ }
88+}
89+
90 func (bs *BufferList) resolveReply(netID, title, replyTo string) *Reply {
91 _, b := bs.at(netID, title)
92 if b == nil {
93@@ -608,6 +665,25 @@ func (bs *BufferList) replyPreviewText(reply *Reply) StyledString {
94 return sb.StyledString()
95 }
96
97+func (bs *BufferList) reactsText(reacts []React, selected bool) StyledString {
98+ var sb StyledStringBuilder
99+ for i, r := range reacts {
100+ if i > 0 {
101+ sb.WriteStyledString(PlainString(" "))
102+ }
103+ sb.WriteStyledString(ColorString("[", bs.ui.config.Colors.Gray))
104+ sb.WriteStyledString(PlainString(r.React))
105+ sb.WriteStyledString(PlainString(" "))
106+ if selected {
107+ sb.WriteStyledString(ColorString(strings.Join(r.Users, ", "), bs.ui.config.Colors.Gray))
108+ } else {
109+ sb.WriteStyledString(ColorString(strconv.Itoa(len(r.Users)), bs.ui.config.Colors.Gray))
110+ }
111+ sb.WriteStyledString(ColorString("]", bs.ui.config.Colors.Gray))
112+ }
113+ return sb.StyledString()
114+}
115+
116 func (bs *BufferList) AddLines(netID, title string, before, after []Line) {
117 _, b := bs.at(netID, title)
118 if b == nil {
119@@ -1381,6 +1457,9 @@ func (bs *BufferList) DrawTimeline(ui *UI, x0, y0, nickColWidth int) {
120 if line.Reply != nil {
121 yi--
122 }
123+ if len(line.Reacts) > 0 {
124+ yi--
125+ }
126 if y0+bs.tlHeight <= yi {
127 continue
128 }
129@@ -1541,6 +1620,17 @@ func (bs *BufferList) DrawTimeline(ui *UI, x0, y0, nickColWidth int) {
130 })
131 }
132 }
133+
134+ if len(line.Reacts) == 0 {
135+ continue
136+ }
137+ y++
138+ if y >= y0 && y < y0+bs.tlHeight {
139+ reacts := bs.reactsText(line.Reacts, selected)
140+ reacts.string = truncate(bs.ui.vx, reacts.string, bs.textWidth, "…")
141+ x := x1
142+ printString(vx, &x, y, reacts)
143+ }
144 }
145
146 b.isAtTop = y0 <= yi
M
ui/ui.go
+4,
-0
1@@ -616,6 +616,10 @@ func (ui *UI) SetRead(netID, buffer string, timestamp time.Time) {
2 ui.bs.SetRead(netID, buffer, timestamp)
3 }
4
5+func (ui *UI) ApplyReact(netID, buffer, id, user, react string, removal bool) {
6+ ui.bs.ApplyReact(netID, buffer, id, user, react, removal)
7+}
8+
9 func (ui *UI) UpdateRead() (netID, buffer string, timestamp time.Time) {
10 return ui.bs.UpdateRead()
11 }