diff options
Diffstat (limited to 'client/channel_window.go')
| -rw-r--r-- | client/channel_window.go | 166 |
1 files changed, 155 insertions, 11 deletions
diff --git a/client/channel_window.go b/client/channel_window.go index 9a78b34..3239b47 100644 --- a/client/channel_window.go +++ b/client/channel_window.go @@ -4,9 +4,12 @@ import ( "citrons.xyz/talk/client/buffer" "citrons.xyz/talk/client/window" "citrons.xyz/talk/client/clipboard" + "citrons.xyz/talk/client/object" "citrons.xyz/talk/proto" "citrons.xyz/talk/tui" + "github.com/rivo/uniseg" "strconv" + "strings" "time" ) @@ -18,6 +21,8 @@ type channelWindow struct { window.DefaultWindow location channelLocation watchedUsers map[string]bool + messageCache object.ObjCache + replyingTo string loadingHistory bool endOfHistory bool } @@ -34,6 +39,7 @@ func (cl channelLocation) CreateWindow() window.Window { cw := &channelWindow { location: cl, watchedUsers: make(map[string]bool), } + cw.messageCache = object.NewCache(cw) globalApp.cache.Watch(cl.id) cw.loadMoreHistory() return cw @@ -69,6 +75,10 @@ func (cw *channelWindow) put(msg proto.Object) { if msg.Kind == "membership" { cw.endOfHistory = false } + cw.messageCache.Update(msg.Id, msg) + if !cw.messageCache.IsWatched(msg.Id) { + cw.messageCache.Watch(msg.Id) + } cw.Buf.Add(channelMsg {msg, cw}) } @@ -106,11 +116,20 @@ func (cw *channelWindow) Send(text string) { } } } - msg := proto.Object {"m", "", map[string]string {"": text}} + fields := map[string]string {"": text} + if cw.replyingTo != "" { + fields["reply"] = cw.replyingTo + cw.replyingTo = "" + } + msg := proto.Object {"m", "", fields} globalApp.Request(proto.NewCmd("p", cw.location.id, msg), cb) cw.In.SetText("") } +func (cw *channelWindow) replyTo(id string) { + cw.replyingTo = id +} + func (cw *channelWindow) leaveChannel() { globalApp.Request(proto.NewCmd("leave", cw.location.id), nil) globalApp.windowCache.Evict(cw.location) @@ -154,7 +173,12 @@ func (cw *channelWindow) loadMoreHistory() { cw.endOfHistory = true } for i := len(response.Args) - 1; i >= 0; i-- { - cw.Buf.AddTop(channelMsg {response.Args[i], cw}) + m := response.Args[i] + cw.messageCache.Update(m.Id, m) + if !cw.messageCache.IsWatched(m.Id) { + cw.messageCache.Watch(m.Id) + } + cw.Buf.AddTop(channelMsg {m, cw}) } case "fail": cw.endOfHistory = true @@ -163,6 +187,29 @@ func (cw *channelWindow) loadMoreHistory() { globalApp.Request(proto.NewCmd("history", cw.location.id, rq), cb) } +func (cw *channelWindow) GetInfo( + messageId string, callback func(*proto.Object)) { + cb := func (response proto.Command) { + if response.Kind == "history" && len(response.Args) != 0 { + callback(&response.Args[0]) + } else { + callback(nil) + } + } + rq := proto.Object {"at", "", map[string]string {"": messageId}} + globalApp.Request(proto.NewCmd("history", cw.location.id, rq), cb) +} + +func (cw *channelWindow) Sub(messageId string) { + cw.GetInfo(messageId, func(m *proto.Object) { + if m != nil { + cw.messageCache.Update(messageId, *m) + } + }) +} + +func (cw *channelWindow) Unsub(messageId string) {} + type userListMsg struct { index int channelName string @@ -240,6 +287,23 @@ func (cw *channelWindow) ShowStatusLine() { } } +func (cw *channelWindow) ShowComposingReply() { + msg := cw.messageCache.Get(cw.replyingTo) + if msg != nil { + mouse := tui.Push("reply message content", tui.Box { + Width: tui.Fill, Height: 1, NoWrap: true, + Style: &tui.Style {Fg: 248, Bg: tui.Black}, + }) + tui.Text("> ", nil) + tui.Text(msg.Fields[""], nil) + tui.Pop() + if mouse.Pressed && mouse.Button == 0 { + cw.replyingTo = "" + globalApp.redraw = true + } + } +} + func (m channelMsg) Id() string { return "buffer." + m.Object.Id } @@ -254,33 +318,103 @@ func (m channelMsg) showDate(bg int32) { tui.Pop() } -func (m channelMsg) showName(bg int32) { +func (m channelMsg) showName(bg int32, uid string, abbreviate bool) { nameStyle := tui.Style {Bg: bg, Fg: tui.White} - if m.Fields["f"] == globalApp.uid { + if uid == globalApp.uid { nameStyle.Fg = tui.Cyan } - if m.window.isGone(m.Fields["f"]) { + if m.window.isGone(uid) { nameStyle.Italic = true } else { nameStyle.Bold = true } - mouse := tui.Push(m.Id() + ".name", tui.Box { + mouse := tui.Push(m.Id() + "." + uid, tui.Box { Width: tui.TextSize, Height: 1, NoWrap: true, }) + switch { case mouse.Button == 0 && mouse.Pressed: fallthrough case tui.MenuOption("who?"): - globalApp.cmdWindow.who(m.Fields["f"]) + globalApp.cmdWindow.who(uid) globalApp.redraw = true } - tui.Text(m.window.username(m.Fields["f"]), &nameStyle) + + name := m.window.username(uid) + var dotdotdot bool + if abbreviate && uniseg.StringWidth(name) > 12 { + dotdotdot = true + var sb strings.Builder + g := uniseg.NewGraphemes(name) + width := 0 + for g.Next() { + width = width + g.Width() + if width > 11 { + break + } + sb.WriteString(g.Str()) + } + name = sb.String() + } + tui.Text(name, &nameStyle) + if dotdotdot { + tui.Text("…", nil) + } + + tui.Pop() +} + +func (m channelMsg) showReply(bg int32, replyTo *proto.Object) { + tui.Push(m.Id() + ".reply message", tui.Box { + Width: tui.Children, Height: 1, Dir: tui.Right, + }) + + tui.Push("", tui.Box { + Width: tui.TextSize, Height: 1, NoWrap: true, + }) + tui.Text(" re ", &tui.Style {Fg: tui.Blue, Bg: bg}) + tui.Pop() + + var kind string + if replyTo != nil { + kind = replyTo.Kind + } + switch kind { + case "m": + m.showName(bg, replyTo.Fields["f"], true) + tui.Push("", tui.Box { + Width: tui.TextSize, Height: 1, NoWrap: true, + }) + tui.Text(": ", &tui.Style {Fg: tui.BrightBlack, Bg: bg}) + text := strings.TrimSpace(replyTo.Fields[""]) + tui.Text(text, &tui.Style {Bg: bg, Fg: 248}) + tui.Pop() + default: + tui.Push("", tui.Box { + Width: tui.TextSize, Height: 1, NoWrap: true, + }) + tui.Text("...", nil) + tui.Pop() + } + tui.Pop() } func (m channelMsg) Show(odd bool) { var bg int32 = colorDefault[odd] + var ( + isReply bool + replyTo *proto.Object + ) + if m.Fields["reply"] != "" { + isReply = true + if !m.window.messageCache.IsWatched(m.Fields["reply"]) { + m.window.messageCache.Watch(m.Fields["reply"]) + } + replyTo = m.window.messageCache.Get(m.Fields["reply"]) + } + switch m.Kind { case "join", "leave": tui.Push("", tui.Box { @@ -300,17 +434,24 @@ func (m channelMsg) Show(odd bool) { tui.Text(m.Kind, &tui.Style {Fg: tui.Blue, Bg: bg}) tui.Text(": ", &tui.Style {Fg: tui.BrightBlack, Bg: bg}) tui.Pop() - m.showName(bg) + m.showName(bg, m.Fields["f"], false) tui.Pop() tui.Pop() case "m": tui.Push("", tui.Box {Width: tui.Fill, Height: 1, Dir: tui.Left}) m.showDate(bg) - m.showName(bg) + + tui.Push("", tui.Box {Width: tui.Children, Height: 1, Dir: tui.Right}) + m.showName(bg, m.Fields["f"], isReply) + if isReply { + m.showReply(bg, replyTo) + } + tui.Pop() + tui.Pop() - tui.Push(m.Id() + ".content", tui.Box { + mouse := tui.Push(m.Id() + ".content", tui.Box { Width: tui.Fill, Height: tui.TextSize, Margins: [4]int {1, 0, 0, 0}, }) @@ -318,7 +459,10 @@ func (m channelMsg) Show(odd bool) { switch { case tui.MenuOption("copy"): clipboard.Get().Copy(m.Fields[""]) + case mouse.Button == 0 && mouse.Pressed: + fallthrough case tui.MenuOption("reply"): + m.window.replyTo(m.Object.Id) } tui.Pop() default: |
