Handle tag events

This commit is contained in:
Tulir Asokan 2018-04-24 02:13:17 +03:00
parent 135fcbf284
commit 2a0145db88
5 changed files with 371 additions and 110 deletions

View File

@ -54,6 +54,8 @@ type MainView interface {
SetRooms(roomIDs []string) SetRooms(roomIDs []string)
SaveAllHistory() SaveAllHistory()
UpdateTags(room *rooms.Room, newTags []rooms.RoomTag)
SetTyping(roomID string, users []string) SetTyping(roomID string, users []string)
ParseEvent(roomView RoomView, evt *gomatrix.Event) Message ParseEvent(roomView RoomView, evt *gomatrix.Event) Message

View File

@ -18,7 +18,6 @@ package matrix
import ( import (
"bytes" "bytes"
"encoding/json"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
@ -255,9 +254,23 @@ func (c *Container) HandlePushRules(evt *gomatrix.Event) {
// HandleTag is the event handler for the m.tag account data event. // HandleTag is the event handler for the m.tag account data event.
func (c *Container) HandleTag(evt *gomatrix.Event) { func (c *Container) HandleTag(evt *gomatrix.Event) {
debug.Print("Received updated tags for", evt.RoomID) room := c.config.Session.GetRoom(evt.RoomID)
dat, _ := json.MarshalIndent(&evt.Content, "", " ")
debug.Print(string(dat)) tags, _ := evt.Content["tags"].(map[string]interface{})
newTags := make([]rooms.RoomTag, len(tags))
index := 0
for tag, infoifc := range tags {
info, _ := infoifc.(map[string]interface{})
order, _ := info["order"].(float64)
newTags[index] = rooms.RoomTag{
Tag: tag,
Order: order,
}
index++
}
mainView := c.ui.MainView()
mainView.UpdateTags(room, newTags)
} }
func (c *Container) processOwnMembershipChange(evt *gomatrix.Event) { func (c *Container) processOwnMembershipChange(evt *gomatrix.Event) {

View File

@ -34,6 +34,14 @@ const (
MemberRoomName MemberRoomName
) )
// RoomTag is a tag given to a specific room.
type RoomTag struct {
// The name of the tag.
Tag string
// The order of the tag. Smaller values are ordered higher.
Order float64
}
// Room represents a single Matrix room. // Room represents a single Matrix room.
type Room struct { type Room struct {
*gomatrix.Room *gomatrix.Room
@ -53,7 +61,9 @@ type Room struct {
// a notificationless message like bot notices. // a notificationless message like bot notices.
HasNewMessages bool HasNewMessages bool
Tags []string // List of tags given to this room
RawTags []RoomTag
// Timestamp of previously received actual message.
LastReceivedMessage time.Time LastReceivedMessage time.Time
// MXID -> Member cache calculated from membership events. // MXID -> Member cache calculated from membership events.
@ -102,6 +112,13 @@ func (room *Room) MarkRead() {
room.HasNewMessages = false room.HasNewMessages = false
} }
func (room *Room) Tags() []RoomTag {
if len(room.RawTags) == 0 {
return []RoomTag{{"", 0.5}}
}
return room.RawTags
}
// UpdateState updates the room's current state with the given Event. This will clobber events based // UpdateState updates the room's current state with the given Event. This will clobber events based
// on the type/state_key combination. // on the type/state_key combination.
func (room *Room) UpdateState(event *gomatrix.Event) { func (room *Room) UpdateState(event *gomatrix.Event) {

View File

@ -18,7 +18,9 @@ package ui
import ( import (
"fmt" "fmt"
"regexp"
"strconv" "strconv"
"strings"
"time" "time"
"maunium.net/go/gomuks/matrix/rooms" "maunium.net/go/gomuks/matrix/rooms"
@ -27,13 +29,21 @@ import (
"maunium.net/go/tview" "maunium.net/go/tview"
) )
type roomListItem struct {
room *rooms.Room
priority float64
}
type RoomList struct { type RoomList struct {
*tview.Box *tview.Box
// The list of tags in display order.
tags []string
// The list of rooms, in reverse order. // The list of rooms, in reverse order.
items []*rooms.Room items map[string][]*rooms.Room
// The selected room. // The selected room.
selected *rooms.Room selected *rooms.Room
selectedTag string
// The item main text color. // The item main text color.
mainTextColor tcell.Color mainTextColor tcell.Color
@ -46,7 +56,7 @@ type RoomList struct {
func NewRoomList() *RoomList { func NewRoomList() *RoomList {
return &RoomList{ return &RoomList{
Box: tview.NewBox(), Box: tview.NewBox(),
items: []*rooms.Room{}, items: make(map[string][]*rooms.Room),
mainTextColor: tcell.ColorWhite, mainTextColor: tcell.ColorWhite,
selectedTextColor: tcell.ColorWhite, selectedTextColor: tcell.ColorWhite,
@ -55,107 +65,248 @@ func NewRoomList() *RoomList {
} }
func (list *RoomList) Contains(roomID string) bool { func (list *RoomList) Contains(roomID string) bool {
for _, room := range list.items { for _, roomList := range list.items {
if room.ID == roomID { for _, room := range roomList {
return true if room.ID == roomID {
return true
}
} }
} }
return false return false
} }
func (list *RoomList) Add(room *rooms.Room) { func (list *RoomList) Add(room *rooms.Room) {
list.items = append(list.items, nil) for _, tag := range room.Tags() {
insertAt := len(list.items) - 1 list.AddToTag(tag.Tag, room)
for i := 0; i < len(list.items)-1; i++ { }
if list.items[i].LastReceivedMessage.After(room.LastReceivedMessage) { }
func (list *RoomList) CheckTag(tag string) {
index := list.IndexTag(tag)
items, ok := list.items[tag]
if len(items) == 0 {
delete(list.items, tag)
ok = false
}
if ok && index == -1 {
list.tags = append(list.tags, tag)
} else if index != -1 {
list.tags = append(list.tags[0:index], list.tags[index+1:]...)
}
}
func (list *RoomList) AddToTag(tag string, room *rooms.Room) {
items, ok := list.items[tag]
if !ok {
list.items[tag] = []*rooms.Room{room}
return
}
// Add space for new item.
items = append(items, nil)
// The default insert index is the newly added slot.
// That index will be used if all other rooms in the list have the same LastReceivedMessage timestamp.
insertAt := len(items) - 1
// Find the spot where the new room should be put according to the last received message timestamps.
for i := 0; i < len(items)-1; i++ {
if items[i].LastReceivedMessage.After(room.LastReceivedMessage) {
insertAt = i insertAt = i
break break
} }
} }
for i := len(list.items) - 1; i > insertAt; i-- { // Move newer rooms forward in the array.
list.items[i] = list.items[i-1] for i := len(items) - 1; i > insertAt; i-- {
items[i] = items[i-1]
} }
list.items[insertAt] = room // Insert room.
items[insertAt] = room
list.items[tag] = items
list.CheckTag(tag)
} }
func (list *RoomList) Remove(room *rooms.Room) { func (list *RoomList) Remove(room *rooms.Room) {
index := list.Index(room) for _, tag := range room.Tags() {
if index != -1 { list.RemoveFromTag(tag.Tag, room)
list.items = append(list.items[0:index], list.items[index+1:]...) }
if room == list.selected { }
if index > 0 {
list.selected = list.items[index-1] func (list *RoomList) RemoveFromTag(tag string, room *rooms.Room) {
} else if len(list.items) > 0 { items, ok := list.items[tag]
list.selected = list.items[0] if !ok {
} else { return
list.selected = nil }
index := list.indexInTag(tag, room)
if index == -1 {
return
}
items = append(items[0:index], items[index+1:]...)
if len(items) == 0 {
delete(list.items, tag)
} else {
list.items[tag] = items
}
if room == list.selected {
// Room is currently selected, move selection to another room.
if index > 0 {
list.selected = items[index-1]
} else if len(items) > 0 {
list.selected = items[0]
} else if len(list.items) > 0 {
for _, tag := range list.tags {
moreItems := list.items[tag]
if len(moreItems) > 0 {
list.selected = moreItems[0]
list.selectedTag = tag
}
} }
} else {
list.selected = nil
list.selectedTag = ""
} }
} }
list.CheckTag(tag)
} }
func (list *RoomList) Bump(room *rooms.Room) { func (list *RoomList) Bump(room *rooms.Room) {
for _, tag := range room.Tags() {
list.bumpInTag(tag.Tag, room)
}
}
func (list *RoomList) bumpInTag(tag string, room *rooms.Room) {
items, ok := list.items[tag]
if !ok {
return
}
found := false found := false
for i := 0; i < len(list.items)-1; i++ { for i := 0; i < len(items)-1; i++ {
if list.items[i] == room { if items[i] == room {
found = true found = true
} }
if found { if found {
list.items[i] = list.items[i+1] items[i] = items[i+1]
} }
} }
list.items[len(list.items)-1] = room if found {
room.LastReceivedMessage = time.Now() items[len(items)-1] = room
room.LastReceivedMessage = time.Now()
}
} }
func (list *RoomList) Clear() { func (list *RoomList) Clear() {
list.items = []*rooms.Room{} list.items = make(map[string][]*rooms.Room)
list.selected = nil list.selected = nil
list.selectedTag = ""
} }
func (list *RoomList) SetSelected(room *rooms.Room) { func (list *RoomList) SetSelected(tag string, room *rooms.Room) {
list.selected = room list.selected = room
list.selectedTag = ""
} }
func (list *RoomList) HasSelected() bool { func (list *RoomList) HasSelected() bool {
return list.selected != nil return list.selected != nil
} }
func (list *RoomList) Selected() *rooms.Room { func (list *RoomList) Selected() (string, *rooms.Room) {
return list.selectedTag, list.selected
}
func (list *RoomList) SelectedRoom() *rooms.Room {
return list.selected return list.selected
} }
func (list *RoomList) Previous() *rooms.Room { func (list *RoomList) First() (string, *rooms.Room) {
if len(list.items) == 0 { for _, tag := range list.tags {
return nil items := list.items[tag]
} else if list.selected == nil { if len(items) > 0 {
return list.items[0] return tag, items[0]
}
} }
return "", nil
index := list.Index(list.selected)
if index == len(list.items)-1 {
return list.items[0]
}
return list.items[index+1]
} }
func (list *RoomList) Next() *rooms.Room { func (list *RoomList) Last() (string, *rooms.Room) {
for tagIndex := len(list.tags) - 1; tagIndex >= 0; tagIndex-- {
tag := list.tags[tagIndex]
items := list.items[tag]
if len(items) > 0 {
return tag, items[len(items)-1]
}
}
return "", nil
}
func (list *RoomList) IndexTag(tag string) int {
for index, entry := range list.tags {
if tag == entry {
return index
}
}
return -1
}
func (list *RoomList) Previous() (string, *rooms.Room) {
if len(list.items) == 0 { if len(list.items) == 0 {
return nil return "", nil
} else if list.selected == nil { } else if list.selected == nil {
return list.items[0] return list.First()
} }
index := list.Index(list.selected) items := list.items[list.selectedTag]
index := list.indexInTag(list.selectedTag, list.selected)
if index == len(items)-1 {
tagIndex := list.IndexTag(list.selectedTag)
tagIndex++
for ; tagIndex < len(list.tags); tagIndex++ {
nextTag := list.tags[tagIndex]
nextTagItems := list.items[nextTag]
if len(nextTagItems) > 0 {
return nextTag, nextTagItems[0]
}
}
return list.First()
}
return list.selectedTag, items[index+1]
}
func (list *RoomList) Next() (string, *rooms.Room) {
if len(list.items) == 0 {
return "", nil
} else if list.selected == nil {
return list.First()
}
items := list.items[list.selectedTag]
index := list.indexInTag(list.selectedTag, list.selected)
if index == 0 { if index == 0 {
return list.items[len(list.items)-1] tagIndex := list.IndexTag(list.selectedTag)
tagIndex--
for ; tagIndex >= 0; tagIndex-- {
prevTag := list.tags[tagIndex]
prevTagItems := list.items[prevTag]
if len(prevTagItems) > 0 {
return prevTag, prevTagItems[len(prevTagItems)-1]
}
}
return list.Last()
} }
return list.items[index-1] return list.selectedTag, items[index-1]
} }
func (list *RoomList) Index(room *rooms.Room) int { func (list *RoomList) indexInTag(tag string, room *rooms.Room) int {
roomIndex := -1 roomIndex := -1
for index, entry := range list.items { items := list.items[tag]
for index, entry := range items {
if entry == room { if entry == room {
roomIndex = index roomIndex = index
break break
@ -164,11 +315,46 @@ func (list *RoomList) Index(room *rooms.Room) int {
return roomIndex return roomIndex
} }
func (list *RoomList) Get(n int) *rooms.Room { func (list *RoomList) Get(n int) (string, *rooms.Room) {
if n < 0 || n > len(list.items)-1 { if n < 0 {
return nil return "", nil
}
for _, tag := range list.tags {
// Tag header
n--
items := list.items[tag]
if n < 0 {
return "", nil
} else if n < len(items) {
return tag, items[len(items)-1-n]
}
// Tag items
n -= len(items)
}
return "", nil
}
var nsRegex = regexp.MustCompile("^[a-z]\\.[a-z](?:\\.[a-z])*$")
func (list *RoomList) GetTagDisplayName(tag string) string {
switch {
case len(tag) == 0:
return "Rooms"
case tag == "m.favourite":
return "Favorites"
case tag == "m.lowpriority":
return "Low Priority"
case strings.HasPrefix(tag, "m."):
return strings.Title(strings.Replace(tag[len("m."):], "_", " ", -1))
case strings.HasPrefix(tag, "u."):
return tag[len("u."):]
case !nsRegex.MatchString(tag):
return tag
default:
return ""
} }
return list.items[len(list.items)-1-n]
} }
// Draw draws this primitive onto the screen. // Draw draws this primitive onto the screen.
@ -179,54 +365,60 @@ func (list *RoomList) Draw(screen tcell.Screen) {
bottomLimit := y + height bottomLimit := y + height
var offset int var offset int
/* TODO fix offset
currentItemIndex := list.Index(list.selected) currentItemIndex := list.Index(list.selected)
if currentItemIndex >= height { if currentItemIndex >= height {
offset = currentItemIndex + 1 - height offset = currentItemIndex + 1 - height
} }*/
// Draw the list items. // Draw the list items.
for i := len(list.items) - 1; i >= 0; i-- { for _, tag := range list.tags {
item := list.items[i] items := list.items[tag]
index := len(list.items) - 1 - i widget.WriteLine(screen, tview.AlignLeft, list.GetTagDisplayName(tag), x, y, width, tcell.StyleDefault.Underline(true).Bold(true))
if index < offset {
continue
}
if y >= bottomLimit {
break
}
text := item.GetTitle()
lineWidth := width
style := tcell.StyleDefault.Foreground(list.mainTextColor)
if item == list.selected {
style = style.Foreground(list.selectedTextColor).Background(list.selectedBackgroundColor)
}
if item.HasNewMessages {
style = style.Bold(true)
}
if item.UnreadMessages > 0 {
unreadMessageCount := "99+"
if item.UnreadMessages < 100 {
unreadMessageCount = strconv.Itoa(item.UnreadMessages)
}
if item.Highlighted {
unreadMessageCount += "!"
}
unreadMessageCount = fmt.Sprintf("(%s)", unreadMessageCount)
widget.WriteLine(screen, tview.AlignRight, unreadMessageCount, x+lineWidth-6, y, 6, style)
lineWidth -= len(unreadMessageCount) + 1
}
widget.WriteLine(screen, tview.AlignLeft, text, x, y, lineWidth, style)
y++ y++
if y >= bottomLimit { for i := len(items) - 1; i >= 0; i-- {
break item := items[i]
index := len(items) - 1 - i
if index < offset {
continue
}
if y >= bottomLimit {
break
}
text := item.GetTitle()
lineWidth := width
style := tcell.StyleDefault.Foreground(list.mainTextColor)
if tag == list.selectedTag && item == list.selected {
style = style.Foreground(list.selectedTextColor).Background(list.selectedBackgroundColor)
}
if item.HasNewMessages {
style = style.Bold(true)
}
if item.UnreadMessages > 0 {
unreadMessageCount := "99+"
if item.UnreadMessages < 100 {
unreadMessageCount = strconv.Itoa(item.UnreadMessages)
}
if item.Highlighted {
unreadMessageCount += "!"
}
unreadMessageCount = fmt.Sprintf("(%s)", unreadMessageCount)
widget.WriteLine(screen, tview.AlignRight, unreadMessageCount, x+lineWidth-6, y, 6, style)
lineWidth -= len(unreadMessageCount) + 1
}
widget.WriteLine(screen, tview.AlignLeft, text, x, y, lineWidth, style)
y++
if y >= bottomLimit {
break
}
} }
} }
} }

View File

@ -248,7 +248,7 @@ func (view *MainView) MouseEventHandler(roomView *RoomView, event *tcell.EventMo
return event return event
} }
func (view *MainView) SwitchRoom(room *rooms.Room) { func (view *MainView) SwitchRoom(tag string, room *rooms.Room) {
if room == nil { if room == nil {
return return
} }
@ -258,13 +258,13 @@ func (view *MainView) SwitchRoom(room *rooms.Room) {
if roomView.MessageView().ScrollOffset == 0 { if roomView.MessageView().ScrollOffset == 0 {
roomView.Room.MarkRead() roomView.Room.MarkRead()
} }
view.roomList.SetSelected(room) view.roomList.SetSelected(tag, room)
view.parent.app.SetFocus(view) view.parent.app.SetFocus(view)
view.parent.Render() view.parent.Render()
} }
func (view *MainView) Focus(delegate func(p tview.Primitive)) { func (view *MainView) Focus(delegate func(p tview.Primitive)) {
room := view.roomList.Selected() room := view.roomList.SelectedRoom()
if room != nil { if room != nil {
roomView, ok := view.rooms[room.ID] roomView, ok := view.rooms[room.ID]
if ok { if ok {
@ -283,7 +283,6 @@ func (view *MainView) SaveAllHistory() {
} }
func (view *MainView) addRoomPage(room *rooms.Room) { func (view *MainView) addRoomPage(room *rooms.Room) {
if !view.roomView.HasPage(room.ID) { if !view.roomView.HasPage(room.ID) {
roomView := NewRoomView(view, room). roomView := NewRoomView(view, room).
SetInputSubmitFunc(view.InputSubmit). SetInputSubmitFunc(view.InputSubmit).
@ -304,7 +303,13 @@ func (view *MainView) addRoomPage(room *rooms.Room) {
} }
func (view *MainView) GetRoom(roomID string) ifc.RoomView { func (view *MainView) GetRoom(roomID string) ifc.RoomView {
return view.rooms[roomID] room, ok := view.rooms[roomID]
if !ok {
view.AddRoom(roomID)
room, _ := view.rooms[roomID]
return room
}
return room
} }
func (view *MainView) AddRoom(roomID string) { func (view *MainView) AddRoom(roomID string) {
@ -335,14 +340,46 @@ func (view *MainView) SetRooms(roomIDs []string) {
view.roomList.Clear() view.roomList.Clear()
view.roomView.Clear() view.roomView.Clear()
view.rooms = make(map[string]*RoomView) view.rooms = make(map[string]*RoomView)
for index, roomID := range roomIDs { for _, roomID := range roomIDs {
room := view.matrix.GetRoom(roomID) room := view.matrix.GetRoom(roomID)
view.roomList.Add(room) view.roomList.Add(room)
view.addRoomPage(room) view.addRoomPage(room)
if index == len(roomIDs)-1 { }
view.SwitchRoom(room) view.SwitchRoom(view.roomList.First())
}
func (view *MainView) UpdateTags(room *rooms.Room, newTags []rooms.RoomTag) {
if len(newTags) == 0 {
for _, tag := range room.RawTags {
view.roomList.RemoveFromTag(tag.Tag, room)
}
view.roomList.AddToTag("", room)
} else if len(room.RawTags) == 0 {
view.roomList.RemoveFromTag("", room)
for _, tag := range newTags {
view.roomList.AddToTag(tag.Tag, room)
}
} else {
NewTags:
for _, newTag := range newTags {
for _, oldTag := range room.RawTags {
if newTag.Tag == oldTag.Tag {
continue NewTags
}
}
view.roomList.AddToTag(newTag.Tag, room)
}
OldTags:
for _, oldTag := range room.RawTags {
for _, newTag := range newTags {
if newTag.Tag == oldTag.Tag {
continue OldTags
}
}
view.roomList.RemoveFromTag(oldTag.Tag, room)
} }
} }
room.RawTags = newTags
} }
func (view *MainView) SetTyping(room string, users []string) { func (view *MainView) SetTyping(room string, users []string) {
@ -362,7 +399,7 @@ func sendNotification(room *rooms.Room, sender, text string, critical, sound boo
func (view *MainView) NotifyMessage(room *rooms.Room, message ifc.Message, should pushrules.PushActionArrayShould) { func (view *MainView) NotifyMessage(room *rooms.Room, message ifc.Message, should pushrules.PushActionArrayShould) {
// Whether or not the room where the message came is the currently shown room. // Whether or not the room where the message came is the currently shown room.
isCurrent := room == view.roomList.Selected() isCurrent := room == view.roomList.SelectedRoom()
// Whether or not the terminal window is focused. // Whether or not the terminal window is focused.
isFocused := view.lastFocusTime.Add(30 * time.Second).Before(time.Now()) isFocused := view.lastFocusTime.Add(30 * time.Second).Before(time.Now())