aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--interface/ui.go2
-rw-r--r--matrix/matrix.go21
-rw-r--r--matrix/rooms/room.go19
-rw-r--r--ui/room-list.go360
-rw-r--r--ui/view-main.go55
5 files changed, 359 insertions, 98 deletions
diff --git a/interface/ui.go b/interface/ui.go
index ec85b0f..c36819a 100644
--- a/interface/ui.go
+++ b/interface/ui.go
@@ -54,6 +54,8 @@ type MainView interface {
SetRooms(roomIDs []string)
SaveAllHistory()
+ UpdateTags(room *rooms.Room, newTags []rooms.RoomTag)
+
SetTyping(roomID string, users []string)
ParseEvent(roomView RoomView, evt *gomatrix.Event) Message
diff --git a/matrix/matrix.go b/matrix/matrix.go
index 87013ac..d58ce20 100644
--- a/matrix/matrix.go
+++ b/matrix/matrix.go
@@ -18,7 +18,6 @@ package matrix
import (
"bytes"
- "encoding/json"
"fmt"
"io"
"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.
func (c *Container) HandleTag(evt *gomatrix.Event) {
- debug.Print("Received updated tags for", evt.RoomID)
- dat, _ := json.MarshalIndent(&evt.Content, "", " ")
- debug.Print(string(dat))
+ room := c.config.Session.GetRoom(evt.RoomID)
+
+ 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) {
diff --git a/matrix/rooms/room.go b/matrix/rooms/room.go
index 7b4a8b5..44a386b 100644
--- a/matrix/rooms/room.go
+++ b/matrix/rooms/room.go
@@ -34,6 +34,14 @@ const (
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.
type Room struct {
*gomatrix.Room
@@ -53,7 +61,9 @@ type Room struct {
// a notificationless message like bot notices.
HasNewMessages bool
- Tags []string
+ // List of tags given to this room
+ RawTags []RoomTag
+ // Timestamp of previously received actual message.
LastReceivedMessage time.Time
// MXID -> Member cache calculated from membership events.
@@ -102,6 +112,13 @@ func (room *Room) MarkRead() {
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
// on the type/state_key combination.
func (room *Room) UpdateState(event *gomatrix.Event) {
diff --git a/ui/room-list.go b/ui/room-list.go
index cad1b5a..a70fd68 100644
--- a/ui/room-list.go
+++ b/ui/room-list.go
@@ -18,7 +18,9 @@ package ui
import (
"fmt"
+ "regexp"
"strconv"
+ "strings"
"time"
"maunium.net/go/gomuks/matrix/rooms"
@@ -27,13 +29,21 @@ import (
"maunium.net/go/tview"
)
+type roomListItem struct {
+ room *rooms.Room
+ priority float64
+}
+
type RoomList struct {
*tview.Box
+ // The list of tags in display order.
+ tags []string
// The list of rooms, in reverse order.
- items []*rooms.Room
+ items map[string][]*rooms.Room
// The selected room.
- selected *rooms.Room
+ selected *rooms.Room
+ selectedTag string
// The item main text color.
mainTextColor tcell.Color
@@ -46,7 +56,7 @@ type RoomList struct {
func NewRoomList() *RoomList {
return &RoomList{
Box: tview.NewBox(),
- items: []*rooms.Room{},
+ items: make(map[string][]*rooms.Room),
mainTextColor: tcell.ColorWhite,
selectedTextColor: tcell.ColorWhite,
@@ -55,107 +65,248 @@ func NewRoomList() *RoomList {
}
func (list *RoomList) Contains(roomID string) bool {
- for _, room := range list.items {
- if room.ID == roomID {
- return true
+ for _, roomList := range list.items {
+ for _, room := range roomList {
+ if room.ID == roomID {
+ return true
+ }
}
}
return false
}
func (list *RoomList) Add(room *rooms.Room) {
- list.items = append(list.items, nil)
- insertAt := len(list.items) - 1
- for i := 0; i < len(list.items)-1; i++ {
- if list.items[i].LastReceivedMessage.After(room.LastReceivedMessage) {
+ for _, tag := range room.Tags() {
+ list.AddToTag(tag.Tag, room)
+ }
+}
+
+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
break
}
}
- for i := len(list.items) - 1; i > insertAt; i-- {
- list.items[i] = list.items[i-1]
+ // Move newer rooms forward in the array.
+ 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) {
- index := list.Index(room)
- if index != -1 {
- list.items = append(list.items[0:index], list.items[index+1:]...)
- if room == list.selected {
- if index > 0 {
- list.selected = list.items[index-1]
- } else if len(list.items) > 0 {
- list.selected = list.items[0]
- } else {
- list.selected = nil
+ for _, tag := range room.Tags() {
+ list.RemoveFromTag(tag.Tag, room)
+ }
+}
+
+func (list *RoomList) RemoveFromTag(tag string, room *rooms.Room) {
+ items, ok := list.items[tag]
+ if !ok {
+ return
+ }
+
+ 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) {
+ 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
- for i := 0; i < len(list.items)-1; i++ {
- if list.items[i] == room {
+ for i := 0; i < len(items)-1; i++ {
+ if items[i] == room {
found = true
}
if found {
- list.items[i] = list.items[i+1]
+ items[i] = items[i+1]
}
}
- list.items[len(list.items)-1] = room
- room.LastReceivedMessage = time.Now()
+ if found {
+ items[len(items)-1] = room
+ room.LastReceivedMessage = time.Now()
+ }
}
func (list *RoomList) Clear() {
- list.items = []*rooms.Room{}
+ list.items = make(map[string][]*rooms.Room)
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.selectedTag = ""
}
func (list *RoomList) HasSelected() bool {
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
}
-func (list *RoomList) Previous() *rooms.Room {
+func (list *RoomList) First() (string, *rooms.Room) {
+ for _, tag := range list.tags {
+ items := list.items[tag]
+ if len(items) > 0 {
+ return tag, items[0]
+ }
+ }
+ return "", nil
+}
+
+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 {
- return nil
+ return "", nil
} else if list.selected == nil {
- return list.items[0]
+ return list.First()
}
- index := list.Index(list.selected)
- if index == len(list.items)-1 {
- return list.items[0]
+ 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.items[index+1]
+ return list.selectedTag, items[index+1]
}
-func (list *RoomList) Next() *rooms.Room {
+func (list *RoomList) Next() (string, *rooms.Room) {
if len(list.items) == 0 {
- return nil
+ return "", 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 == 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
- for index, entry := range list.items {
+ items := list.items[tag]
+ for index, entry := range items {
if entry == room {
roomIndex = index
break
@@ -164,11 +315,46 @@ func (list *RoomList) Index(room *rooms.Room) int {
return roomIndex
}
-func (list *RoomList) Get(n int) *rooms.Room {
- if n < 0 || n > len(list.items)-1 {
- return nil
+func (list *RoomList) Get(n int) (string, *rooms.Room) {
+ if n < 0 {
+ 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.
@@ -179,54 +365,60 @@ func (list *RoomList) Draw(screen tcell.Screen) {
bottomLimit := y + height
var offset int
+ /* TODO fix offset
currentItemIndex := list.Index(list.selected)
if currentItemIndex >= height {
offset = currentItemIndex + 1 - height
- }
+ }*/
// Draw the list items.
- for i := len(list.items) - 1; i >= 0; i-- {
- item := list.items[i]
- index := len(list.items) - 1 - i
-
- if index < offset {
- continue
- }
+ for _, tag := range list.tags {
+ items := list.items[tag]
+ widget.WriteLine(screen, tview.AlignLeft, list.GetTagDisplayName(tag), x, y, width, tcell.StyleDefault.Underline(true).Bold(true))
+ y++
+ for i := len(items) - 1; i >= 0; i-- {
+ item := items[i]
+ index := len(items) - 1 - i
- if y >= bottomLimit {
- break
- }
+ if index < offset {
+ continue
+ }
- text := item.GetTitle()
+ if y >= bottomLimit {
+ break
+ }
- lineWidth := width
+ text := item.GetTitle()
- 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)
- }
+ lineWidth := width
- if item.UnreadMessages > 0 {
- unreadMessageCount := "99+"
- if item.UnreadMessages < 100 {
- unreadMessageCount = strconv.Itoa(item.UnreadMessages)
+ style := tcell.StyleDefault.Foreground(list.mainTextColor)
+ if tag == list.selectedTag && item == list.selected {
+ style = style.Foreground(list.selectedTextColor).Background(list.selectedBackgroundColor)
}
- if item.Highlighted {
- unreadMessageCount += "!"
+ if item.HasNewMessages {
+ style = style.Bold(true)
}
- 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)
+ 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
+ }
- y++
- if y >= bottomLimit {
- break
+ widget.WriteLine(screen, tview.AlignLeft, text, x, y, lineWidth, style)
+
+ y++
+ if y >= bottomLimit {
+ break
+ }
}
}
}
diff --git a/ui/view-main.go b/ui/view-main.go
index d1af09d..ba3a55b 100644
--- a/ui/view-main.go
+++ b/ui/view-main.go
@@ -248,7 +248,7 @@ func (view *MainView) MouseEventHandler(roomView *RoomView, event *tcell.EventMo
return event
}
-func (view *MainView) SwitchRoom(room *rooms.Room) {
+func (view *MainView) SwitchRoom(tag string, room *rooms.Room) {
if room == nil {
return
}
@@ -258,13 +258,13 @@ func (view *MainView) SwitchRoom(room *rooms.Room) {
if roomView.MessageView().ScrollOffset == 0 {
roomView.Room.MarkRead()
}
- view.roomList.SetSelected(room)
+ view.roomList.SetSelected(tag, room)
view.parent.app.SetFocus(view)
view.parent.Render()
}
func (view *MainView) Focus(delegate func(p tview.Primitive)) {
- room := view.roomList.Selected()
+ room := view.roomList.SelectedRoom()
if room != nil {
roomView, ok := view.rooms[room.ID]
if ok {
@@ -283,7 +283,6 @@ func (view *MainView) SaveAllHistory() {
}
func (view *MainView) addRoomPage(room *rooms.Room) {
-
if !view.roomView.HasPage(room.ID) {
roomView := NewRoomView(view, room).
SetInputSubmitFunc(view.InputSubmit).
@@ -304,7 +303,13 @@ func (view *MainView) addRoomPage(room *rooms.Room) {
}
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) {
@@ -335,14 +340,46 @@ func (view *MainView) SetRooms(roomIDs []string) {
view.roomList.Clear()
view.roomView.Clear()
view.rooms = make(map[string]*RoomView)
- for index, roomID := range roomIDs {
+ for _, roomID := range roomIDs {
room := view.matrix.GetRoom(roomID)
view.roomList.Add(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) {
@@ -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) {
// 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.
isFocused := view.lastFocusTime.Add(30 * time.Second).Before(time.Now())