summaryrefslogtreecommitdiff
path: root/client/channel_window.go
diff options
context:
space:
mode:
Diffstat (limited to 'client/channel_window.go')
-rw-r--r--client/channel_window.go166
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: