diff --git a/components/channels.go b/components/channels.go index 560c58b..73f5413 100644 --- a/components/channels.go +++ b/components/channels.go @@ -1,13 +1,9 @@ package components import ( - "fmt" "strings" - "sync" "github.com/erroneousboat/termui" - - "github.com/erroneousboat/slack-term/service" ) const ( @@ -17,9 +13,6 @@ const ( IconGroup = "☰" IconIM = "●" IconNotification = "*" - - PresenceAway = "away" - PresenceActive = "active" ) // Channels is the definition of a Channels component @@ -123,67 +116,8 @@ func (c *Channels) SetY(y int) { c.List.SetY(y) } -// SetChannels will set the channels from the service, to the -// Items field -func (c *Channels) SetChannels(channels []service.Channel) { - c.List.Items = make([]string, len(channels)) - - // WaitGroup needed, else SetPresenceChannels will start - // too early - var wg sync.WaitGroup - for i, slackChan := range channels { - wg.Add(1) - go func(i int, slackChan service.Channel) { - label := setChannelLabel(slackChan, false) - c.List.Items[i] = label - wg.Done() - }(i, slackChan) - } - wg.Wait() -} - -// SetPresenceChannels will set the icon for all the IM channels -func (c *Channels) SetPresenceChannels(channels []service.Channel) { - for i, slackChan := range channels { - go func(i int, slackChan service.Channel) { - if slackChan.Type == service.ChannelTypeIM { - c.SetPresenceChannel(i, slackChan.Presence) - } - }(i, slackChan) - } -} - -// SetPresenceChannel will set the correct icon for one IM channel, on -// a Presence change event -func (c *Channels) SetPresenceChannelEvent(channels []service.Channel, userID string, presence string) { - // Get the correct Channel from svc.Channels - var index int - for i, channel := range channels { - if userID == channel.UserID { - index = i - break - } - } - - c.SetPresenceChannel(index, presence) -} - -// SetPresenceChannel will set the correct icon for one IM channel -func (c *Channels) SetPresenceChannel(i int, presence string) { - switch presence { - case PresenceActive: - c.List.Items[i] = strings.Replace( - c.List.Items[i], IconOffline, IconOnline, 1, - ) - case PresenceAway: - c.List.Items[i] = strings.Replace( - c.List.Items[i], IconOnline, IconOffline, 1, - ) - default: - c.List.Items[i] = strings.Replace( - c.List.Items[i], IconOnline, IconOffline, 1, - ) - } +func (c *Channels) SetChannels(channels []string) { + c.List.Items = channels } // SetSelectedChannel sets the SelectedChannel given the index @@ -201,7 +135,6 @@ func (c *Channels) MoveCursorUp() { if c.SelectedChannel > 0 { c.SetSelectedChannel(c.SelectedChannel - 1) c.ScrollUp() - c.MarkAsRead() } } @@ -210,7 +143,6 @@ func (c *Channels) MoveCursorDown() { if c.SelectedChannel < len(c.List.Items)-1 { c.SetSelectedChannel(c.SelectedChannel + 1) c.ScrollDown() - c.MarkAsRead() } } @@ -298,67 +230,3 @@ func (c *Channels) Search(term string) { } } } - -// MarkAsUnread will be called when a new message arrives and will -// render an notification icon in front of the channel name -func (c *Channels) MarkAsUnread(channels []service.Channel, channelID string) { - // Get the correct Channel from svc.Channels - var index int - for i, channel := range channels { - if channelID == channel.ID { - index = i - break - } - } - - if !strings.Contains(c.List.Items[index], IconNotification) { - // The order of svc.Channels relates to the order of - // List.Items, index will be the index of the channel - c.List.Items[index] = fmt.Sprintf( - "%s %s", IconNotification, strings.TrimSpace(c.List.Items[index]), - ) - } - - // Play terminal bell sound - fmt.Print("\a") -} - -// MarkAsRead will remove the notification icon in front of a channel that -// received a new message. This will happen as one will move up or down the -// cursor for Channels -func (c *Channels) MarkAsRead() { - channelName := strings.Split( - c.List.Items[c.SelectedChannel], - fmt.Sprintf("%s ", IconNotification), - ) - - if len(channelName) > 1 { - c.List.Items[c.SelectedChannel] = fmt.Sprintf(" %s", channelName[1]) - } else { - c.List.Items[c.SelectedChannel] = channelName[0] - } -} - -// setChannelLabel will set the label of the channel, meaning, how it -// is displayed on screen. Based on the type, different icons are -// shown, as well as an optional notification icon. -func setChannelLabel(channel service.Channel, notification bool) string { - var prefix string - if notification { - prefix = IconNotification - } else { - prefix = " " - } - - var label string - switch channel.Type { - case service.ChannelTypeChannel: - label = fmt.Sprintf("%s %s %s", prefix, IconChannel, channel.Name) - case service.ChannelTypeGroup: - label = fmt.Sprintf("%s %s %s", prefix, IconGroup, channel.Name) - case service.ChannelTypeIM: - label = fmt.Sprintf("%s %s %s", prefix, IconIM, channel.Name) - } - - return label -} diff --git a/components/chat.go b/components/chat.go index 5155487..b772e36 100644 --- a/components/chat.go +++ b/components/chat.go @@ -5,13 +5,30 @@ import ( "html" "sort" "strings" + "time" "github.com/erroneousboat/termui" "github.com/erroneousboat/slack-term/config" - "github.com/erroneousboat/slack-term/service" ) +type Message struct { + Time time.Time + Name string + Content string +} + +func (m Message) ToString() string { + return html.UnescapeString( + fmt.Sprintf( + "[%s] <%s> %s", + m.Time.Format("15:04"), + m.Name, + m.Content, + ), + ) +} + // Chat is the definition of a Chat component type Chat struct { List *termui.List @@ -207,16 +224,7 @@ func (c *Chat) ScrollDown() { } // SetBorderLabel will set Label of the Chat pane to the specified string -func (c *Chat) SetBorderLabel(channel service.Channel) { - var channelName string - if channel.Topic != "" { - channelName = fmt.Sprintf("%s - %s", - html.UnescapeString(channel.Name), - html.UnescapeString(channel.Topic), - ) - } else { - channelName = channel.Name - } +func (c *Chat) SetBorderLabel(channelName string) { c.List.BorderLabel = channelName } diff --git a/components/input.go b/components/input.go index ce1ab20..a566b31 100644 --- a/components/input.go +++ b/components/input.go @@ -3,8 +3,6 @@ package components import ( "github.com/erroneousboat/termui" runewidth "github.com/mattn/go-runewidth" - - "github.com/erroneousboat/slack-term/service" ) // Input is the definition of an Input component @@ -71,11 +69,6 @@ func (i *Input) SetY(y int) { i.Par.SetY(y) } -// SendMessage send the input text through the SlackService -func (i *Input) SendMessage(svc *service.SlackService, channel string, message string) { - svc.SendMessage(channel, message) -} - // Insert will insert a given key at the place of the current CursorPositionText func (i *Input) Insert(key rune) { // Append key to the left side diff --git a/handlers/event.go b/handlers/event.go index 91f8681..ecfb7ef 100644 --- a/handlers/event.go +++ b/handlers/event.go @@ -1,6 +1,7 @@ package handlers import ( + "fmt" "os" "strconv" "time" @@ -59,7 +60,7 @@ func eventHandler(ctx *context.AppContext) { // Place your debugging statements here if ctx.Debug { - ctx.View.Debug.Println("hello world") + ctx.View.Debug.Println("event received") } } }() @@ -207,9 +208,8 @@ func actionSend(ctx *context.AppContext) { ctx.View.Input.Clear() ctx.View.Refresh() - ctx.View.Input.SendMessage( - ctx.Service, - ctx.Service.Channels[ctx.View.Channels.SelectedChannel].ID, + ctx.Service.SendMessage( + ctx.View.Channels.SelectedChannel, message, ) } @@ -330,25 +330,30 @@ func actionChangeChannel(ctx *context.AppContext) { // Set messages for the channel ctx.View.Chat.SetMessages(messages) + // FIXME // Set channel name for the Chat pane ctx.View.Chat.SetBorderLabel( - ctx.Service.Channels[ctx.View.Channels.SelectedChannel], + ctx.Service.Channels[ctx.View.Channels.SelectedChannel].GetChannelName(), ) // Clear notification icon if there is any - ctx.View.Channels.MarkAsRead() + ctx.Service.MarkAsRead(ctx.View.Channels.SelectedChannel) + ctx.View.Channels.SetChannels(ctx.Service.ChannelsToString()) termui.Render(ctx.View.Channels) termui.Render(ctx.View.Chat) } func actionNewMessage(ctx *context.AppContext, channelID string) { - ctx.View.Channels.MarkAsUnread(ctx.Service.Channels, channelID) + ctx.Service.MarkAsUnread(ctx.View.Channels.SelectedChannel) + ctx.View.Channels.SetChannels(ctx.Service.ChannelsToString()) termui.Render(ctx.View.Channels) + fmt.Print("\a") } func actionSetPresence(ctx *context.AppContext, channelID string, presence string) { - ctx.View.Channels.SetPresenceChannelEvent(ctx.Service.Channels, channelID, presence) + ctx.Service.SetPresenceChannelEvent(channelID, presence) + ctx.View.Channels.SetChannels(ctx.Service.ChannelsToString()) termui.Render(ctx.View.Channels) } diff --git a/service/channel.go b/service/channel.go new file mode 100644 index 0000000..bd5a2b7 --- /dev/null +++ b/service/channel.go @@ -0,0 +1,71 @@ +package service + +import ( + "fmt" + "html" + + "github.com/erroneousboat/slack-term/components" +) + +const ( + PresenceAway = "away" + PresenceActive = "active" +) + +type Channel struct { + ID string + Name string + Topic string + Type string + UserID string + Presence string + Notification bool +} + +// ToString will set the label of the channel, how it will be +// displayed on screen. Based on the type, different icons are +// shown, as well as an optional notification icon. +func (c Channel) ToString() string { + var prefix string + if c.Notification { + prefix = components.IconNotification + } else { + prefix = " " + } + + var label string + switch c.Type { + case ChannelTypeChannel: + label = fmt.Sprintf("%s %s %s", prefix, components.IconChannel, c.Name) + case ChannelTypeGroup: + label = fmt.Sprintf("%s %s %s", prefix, components.IconGroup, c.Name) + case ChannelTypeIM: + var icon string + switch c.Presence { + case PresenceActive: + icon = components.IconOnline + case PresenceAway: + icon = components.IconOffline + default: + icon = components.IconIM + } + label = fmt.Sprintf("%s %s %s", prefix, icon, c.Name) + } + + return label +} + +// GetChannelName will return a formatted representation of the +// name of the channel +func (c Channel) GetChannelName() string { + var channelName string + if c.Topic != "" { + channelName = fmt.Sprintf("%s - %s", + html.UnescapeString(c.Name), + html.UnescapeString(c.Topic), + ) + } else { + channelName = c.Name + } + return channelName +} diff --git a/service/slack.go b/service/slack.go index 86a9766..60c6c04 100644 --- a/service/slack.go +++ b/service/slack.go @@ -11,6 +11,7 @@ import ( "github.com/nlopes/slack" + "github.com/erroneousboat/slack-term/components" "github.com/erroneousboat/slack-term/config" ) @@ -30,15 +31,6 @@ type SlackService struct { CurrentUsername string } -type Channel struct { - ID string - Name string - Topic string - Type string - UserID string - Presence string -} - // NewSlackService is the constructor for the SlackService and will initialize // the RTM and a Client func NewSlackService(token string) (*SlackService, error) { @@ -84,7 +76,7 @@ func NewSlackService(token string) (*SlackService, error) { // Because the channels are of different types, we will append them to // an []interface as well as to a []Channel which will give us easy access // to the id and name of the Channel. -func (s *SlackService) GetChannels() []Channel { +func (s *SlackService) GetChannels() []string { var chans []Channel // Channel @@ -159,7 +151,33 @@ func (s *SlackService) GetChannels() []Channel { s.Channels = chans - return chans + var channels []string + for _, chn := range s.Channels { + channels = append(channels, chn.ToString()) + } + return channels +} + +// ChannelsToString will relay the string representation for a channel +func (s *SlackService) ChannelsToString() []string { + var channels []string + for _, chn := range s.Channels { + channels = append(channels, chn.ToString()) + } + return channels +} + +// SetPresenceChannelEvent will set the presence of a IM channel +func (s *SlackService) SetPresenceChannelEvent(userID string, presence string) { + // Get the correct Channel from svc.Channels + var index int + for i, channel := range s.Channels { + if userID == channel.UserID { + index = i + break + } + } + s.Channels[index].Presence = presence } // GetSlackChannel returns the representation of a slack channel @@ -199,8 +217,45 @@ func (s *SlackService) SetChannelReadMark(channel interface{}) { } } +// MarkAsRead will set the channel as read +func (s *SlackService) MarkAsRead(channelID int) { + channel := s.Channels[channelID] + + if channel.Notification { + channel.Notification = false + + switch channel.Type { + case ChannelTypeChannel: + s.Client.SetChannelReadMark( + channel.ID, fmt.Sprintf("%f", + float64(time.Now().Unix())), + ) + case ChannelTypeGroup: + s.Client.SetGroupReadMark( + channel.ID, fmt.Sprintf("%f", + float64(time.Now().Unix())), + ) + case ChannelTypeIM: + s.Client.MarkIMChannel( + channel.ID, fmt.Sprintf("%f", + float64(time.Now().Unix())), + ) + } + } +} + +// MarkAsUnread will set the channel as unread +func (s *SlackService) MarkAsUnread(channelID int) { + channel := s.Channels[channelID] + + if !channel.Notification { + channel.Notification = true + } +} + // SendMessage will send a message to a particular channel -func (s *SlackService) SendMessage(channel string, message string) { +func (s *SlackService) SendMessage(channelID int, message string) { + // https://godoc.org/github.com/nlopes/slack#PostMessageParameters postParams := slack.PostMessageParameters{ AsUser: true, @@ -208,7 +263,7 @@ func (s *SlackService) SendMessage(channel string, message string) { } // https://godoc.org/github.com/nlopes/slack#Client.PostMessage - s.Client.PostMessage(channel, message, postParams) + s.Client.PostMessage(s.Channels[channelID].ID, message, postParams) } // GetMessages will get messages for a channel, group or im channel delimited @@ -313,14 +368,13 @@ func (s *SlackService) CreateMessage(message slack.Message) []string { intTime := int64(floatTime) // Format message - msg := fmt.Sprintf( - "[%s] <%s> %s", - time.Unix(intTime, 0).Format("15:04"), - name, - parseMessage(s, message.Text), - ) + msg := components.Message{ + Time: time.Unix(intTime, 0), + Name: name, + Content: parseMessage(s, message.Text), + } - msgs = append(msgs, msg) + msgs = append(msgs, msg.ToString()) return msgs } @@ -379,14 +433,13 @@ func (s *SlackService) CreateMessageFromMessageEvent(message *slack.MessageEvent intTime := int64(floatTime) // Format message - msg := fmt.Sprintf( - "[%s] <%s> %s", - time.Unix(intTime, 0).Format("15:04"), - name, - parseMessage(s, message.Text), - ) + msg := components.Message{ + Time: time.Unix(intTime, 0), + Name: name, + Content: parseMessage(s, message.Text), + } - msgs = append(msgs, msg) + msgs = append(msgs, msg.ToString()) return msgs } diff --git a/views/view.go b/views/view.go index ed488e9..2227d6b 100644 --- a/views/view.go +++ b/views/view.go @@ -25,7 +25,7 @@ func CreateView(svc *service.SlackService) *View { // Channels: fill the component slackChans := svc.GetChannels() channels.SetChannels(slackChans) - channels.SetPresenceChannels(slackChans) + // channels.SetPresenceChannels(slackChans) // Chat: create the component chat := components.CreateChatComponent(input.Par.Height) @@ -36,7 +36,7 @@ func CreateView(svc *service.SlackService) *View { chat.GetMaxItems(), ) chat.SetMessages(slackMsgs) - chat.SetBorderLabel(svc.Channels[channels.SelectedChannel]) + chat.SetBorderLabel(svc.Channels[channels.SelectedChannel].GetChannelName()) // Debug: create the component debug := components.CreateDebugComponent(input.Par.Height)