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
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
+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 }