commit
1f27fcbcf9
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
src/vendor/*
|
||||
!vendor.json
|
9
TODO.md
9
TODO.md
@ -16,14 +16,17 @@ Bugs:
|
||||
of chat pane (y). Because message will sometimes span more than one
|
||||
line and we're able to scroll. Only figure out how many messages you
|
||||
want to load.
|
||||
- [ ] GetMessages for a channel can result in `json: cannot unmarshal number
|
||||
- [x] GetMessages for a channel can result in `json: cannot unmarshal number
|
||||
into Go value of type string` https://github.com/nlopes/slack/issues/92
|
||||
- [ ] docs at exported functions
|
||||
- [ ] incoming message event.go probably need a type switch
|
||||
- [ ] set channel on start
|
||||
|
||||
Features:
|
||||
|
||||
- [x] channel name in chat pane
|
||||
- [x] new message indicator
|
||||
- [x] scrolling in chat pane
|
||||
- [ ] group channels, im channels
|
||||
- [ ] scrolling in channel pane
|
||||
- [x] group channels, im channels
|
||||
- [x] scrolling in channel pane
|
||||
- [ ] remove unsubscribed or closed channels/groups/im
|
||||
|
@ -4,21 +4,19 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/erroneousboat/slack-term/src/service"
|
||||
"github.com/gizak/termui"
|
||||
|
||||
"github.com/erroneousboat/slack-term/src/service"
|
||||
)
|
||||
|
||||
type Channels struct {
|
||||
List *termui.List
|
||||
SlackChannels []SlackChannel
|
||||
SelectedChannel int
|
||||
Offset int
|
||||
CursorPosition int
|
||||
}
|
||||
|
||||
type SlackChannel struct {
|
||||
Name string
|
||||
ID string
|
||||
}
|
||||
|
||||
// CreateChannels is the constructor for the Channels component
|
||||
func CreateChannels(svc *service.SlackService, inputHeight int) *Channels {
|
||||
channels := &Channels{
|
||||
List: termui.NewList(),
|
||||
@ -28,6 +26,8 @@ func CreateChannels(svc *service.SlackService, inputHeight int) *Channels {
|
||||
channels.List.Height = termui.TermHeight() - inputHeight
|
||||
|
||||
channels.SelectedChannel = 0
|
||||
channels.Offset = 0
|
||||
channels.CursorPosition = channels.List.InnerBounds().Min.Y
|
||||
|
||||
channels.GetChannels(svc)
|
||||
|
||||
@ -38,9 +38,16 @@ func CreateChannels(svc *service.SlackService, inputHeight int) *Channels {
|
||||
func (c *Channels) Buffer() termui.Buffer {
|
||||
buf := c.List.Buffer()
|
||||
|
||||
for y, item := range c.List.Items {
|
||||
for i, item := range c.List.Items[c.Offset:] {
|
||||
|
||||
y := c.List.InnerBounds().Min.Y + i
|
||||
|
||||
if y > c.List.InnerBounds().Max.Y-1 {
|
||||
break
|
||||
}
|
||||
|
||||
var cells []termui.Cell
|
||||
if y == c.SelectedChannel {
|
||||
if y == c.CursorPosition {
|
||||
cells = termui.DefaultTxBuilder.Build(
|
||||
item, termui.ColorBlack, termui.ColorWhite)
|
||||
} else {
|
||||
@ -53,22 +60,28 @@ func (c *Channels) Buffer() termui.Buffer {
|
||||
x := 0
|
||||
for _, cell := range cells {
|
||||
width := cell.Width()
|
||||
buf.Set(
|
||||
c.List.InnerBounds().Min.X+x,
|
||||
c.List.InnerBounds().Min.Y+y,
|
||||
cell,
|
||||
)
|
||||
buf.Set(c.List.InnerBounds().Min.X+x, y, cell)
|
||||
x += width
|
||||
}
|
||||
|
||||
// When not at the end of the pane fill it up empty characters
|
||||
for x < c.List.InnerBounds().Max.X-1 {
|
||||
if y == c.CursorPosition {
|
||||
buf.Set(x+1, y,
|
||||
termui.Cell{
|
||||
Ch: ' ', Fg: termui.ColorBlack, Bg: termui.ColorWhite,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
buf.Set(x+1, y, termui.Cell{Ch: ' '})
|
||||
}
|
||||
x++
|
||||
}
|
||||
}
|
||||
|
||||
return buf
|
||||
}
|
||||
|
||||
func (c *Channels) Add() {
|
||||
|
||||
}
|
||||
|
||||
// GetHeight implements interface termui.GridBufferer
|
||||
func (c *Channels) GetHeight() int {
|
||||
return c.List.Block.GetHeight()
|
||||
@ -90,19 +103,9 @@ func (c *Channels) SetY(y int) {
|
||||
}
|
||||
|
||||
// GetChannels will get all available channels from the SlackService
|
||||
// and add them to the List as well as to the SlackChannels, this is done
|
||||
// to better relate the ID and name given to Channels, for Chat.GetMessages.
|
||||
// See event.go actionChangeChannel for more explanation
|
||||
func (c *Channels) GetChannels(svc *service.SlackService) {
|
||||
for _, slackChan := range svc.GetChannels() {
|
||||
c.List.Items = append(c.List.Items, fmt.Sprintf(" %s", slackChan.Name))
|
||||
c.SlackChannels = append(
|
||||
c.SlackChannels,
|
||||
SlackChannel{
|
||||
ID: slackChan.ID,
|
||||
Name: slackChan.Name,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,6 +118,7 @@ func (c *Channels) SetSelectedChannel(index int) {
|
||||
func (c *Channels) MoveCursorUp() {
|
||||
if c.SelectedChannel > 0 {
|
||||
c.SetSelectedChannel(c.SelectedChannel - 1)
|
||||
c.ScrollUp()
|
||||
c.ClearNewMessageIndicator()
|
||||
}
|
||||
}
|
||||
@ -123,17 +127,38 @@ func (c *Channels) MoveCursorUp() {
|
||||
func (c *Channels) MoveCursorDown() {
|
||||
if c.SelectedChannel < len(c.List.Items)-1 {
|
||||
c.SetSelectedChannel(c.SelectedChannel + 1)
|
||||
c.ScrollDown()
|
||||
c.ClearNewMessageIndicator()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Channels) ScrollUp() {
|
||||
if c.CursorPosition == c.List.InnerBounds().Min.Y {
|
||||
if c.Offset > 0 {
|
||||
c.Offset--
|
||||
}
|
||||
} else {
|
||||
c.CursorPosition--
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Channels) ScrollDown() {
|
||||
if c.CursorPosition == c.List.InnerBounds().Max.Y-1 {
|
||||
if c.Offset < len(c.List.Items)-1 {
|
||||
c.Offset++
|
||||
}
|
||||
} else {
|
||||
c.CursorPosition++
|
||||
}
|
||||
}
|
||||
|
||||
// NewMessage will be called when a new message arrives and will
|
||||
// render an asterisk in front of the channel name
|
||||
func (c *Channels) NewMessage(channelID string) {
|
||||
func (c *Channels) NewMessage(svc *service.SlackService, channelID string) {
|
||||
var index int
|
||||
|
||||
// Get the correct Channel from SlackChannels
|
||||
for i, channel := range c.SlackChannels {
|
||||
// Get the correct Channel from svc.Channels
|
||||
for i, channel := range svc.Channels {
|
||||
if channelID == channel.ID {
|
||||
index = i
|
||||
break
|
||||
@ -141,7 +166,7 @@ func (c *Channels) NewMessage(channelID string) {
|
||||
}
|
||||
|
||||
if !strings.Contains(c.List.Items[index], "*") {
|
||||
// The order of SlackChannels relates to the order of
|
||||
// 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", strings.TrimSpace(c.List.Items[index]))
|
||||
}
|
||||
|
@ -3,8 +3,9 @@ package components
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/erroneousboat/slack-term/src/service"
|
||||
"github.com/gizak/termui"
|
||||
|
||||
"github.com/erroneousboat/slack-term/src/service"
|
||||
)
|
||||
|
||||
type Chat struct {
|
||||
@ -14,7 +15,7 @@ type Chat struct {
|
||||
}
|
||||
|
||||
// CreateChat is the constructor for the Chat struct
|
||||
func CreateChat(svc *service.SlackService, inputHeight int, selectedChannel SlackChannel) *Chat {
|
||||
func CreateChat(svc *service.SlackService, inputHeight int, selectedChannel interface{}) *Chat {
|
||||
chat := &Chat{
|
||||
List: termui.NewList(),
|
||||
Offset: 0,
|
||||
@ -23,8 +24,8 @@ func CreateChat(svc *service.SlackService, inputHeight int, selectedChannel Slac
|
||||
chat.List.Height = termui.TermHeight() - inputHeight
|
||||
chat.List.Overflow = "wrap"
|
||||
|
||||
chat.GetMessages(svc, selectedChannel.ID)
|
||||
chat.SetBorderLabel(selectedChannel.Name)
|
||||
chat.GetMessages(svc, selectedChannel)
|
||||
// chat.SetBorderLabel(selectedChannel.Name)
|
||||
|
||||
return chat
|
||||
}
|
||||
@ -136,7 +137,7 @@ func (c *Chat) SetY(y int) {
|
||||
|
||||
// GetMessages will get an array of strings for a specific channel which will
|
||||
// contain messages in turn all these messages will be added to List.Items
|
||||
func (c *Chat) GetMessages(svc *service.SlackService, channel string) {
|
||||
func (c *Chat) GetMessages(svc *service.SlackService, channel interface{}) {
|
||||
// Get the count of message that fit in the pane
|
||||
count := c.List.InnerBounds().Max.Y - c.List.InnerBounds().Min.Y
|
||||
messages := svc.GetMessages(channel, count)
|
||||
|
@ -1,8 +1,9 @@
|
||||
package components
|
||||
|
||||
import (
|
||||
"github.com/erroneousboat/slack-term/src/service"
|
||||
"github.com/gizak/termui"
|
||||
|
||||
"github.com/erroneousboat/slack-term/src/service"
|
||||
)
|
||||
|
||||
// Input is the definition of and input box
|
||||
|
@ -1,8 +1,8 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"github.com/erroneousboat/slack"
|
||||
"github.com/gizak/termui"
|
||||
"github.com/nlopes/slack"
|
||||
|
||||
"github.com/erroneousboat/slack-term/src/context"
|
||||
"github.com/erroneousboat/slack-term/src/views"
|
||||
@ -90,14 +90,18 @@ func incomingMessageHandler(ctx *context.AppContext) {
|
||||
m := ctx.Service.CreateMessageFromMessageEvent(ev)
|
||||
|
||||
// Add message to the selected channel
|
||||
if ev.Channel == ctx.View.Channels.SlackChannels[ctx.View.Channels.SelectedChannel].ID {
|
||||
if ev.Channel == ctx.Service.Channels[ctx.View.Channels.SelectedChannel].ID {
|
||||
ctx.View.Chat.AddMessage(m)
|
||||
termui.Render(ctx.View.Chat)
|
||||
|
||||
// TODO: set Chat.Offset to 0?
|
||||
// TODO: set Chat.Offset to 0, to automatically scroll
|
||||
// down?
|
||||
}
|
||||
|
||||
// Set new message indicator for channel
|
||||
// Set new message indicator for channel, I'm leaving
|
||||
// this here because I also want to be notified when
|
||||
// I'm currently in a channel but not in the terminal
|
||||
// window (tmux)
|
||||
actionNewMessage(ctx, ev.Channel)
|
||||
}
|
||||
}
|
||||
@ -137,7 +141,7 @@ func actionSend(ctx *context.AppContext) {
|
||||
if !ctx.View.Input.IsEmpty() {
|
||||
ctx.View.Input.SendMessage(
|
||||
ctx.Service,
|
||||
ctx.View.Channels.SlackChannels[ctx.View.Channels.SelectedChannel].ID,
|
||||
ctx.Service.Channels[ctx.View.Channels.SelectedChannel].ID,
|
||||
ctx.View.Input.Text(),
|
||||
)
|
||||
ctx.View.Input.Clear()
|
||||
@ -164,7 +168,7 @@ func actionCommandMode(ctx *context.AppContext) {
|
||||
func actionGetMessages(ctx *context.AppContext) {
|
||||
ctx.View.Chat.GetMessages(
|
||||
ctx.Service,
|
||||
ctx.View.Channels.SlackChannels[ctx.View.Channels.SelectedChannel].ID,
|
||||
ctx.Service.Channels[ctx.View.Channels.SelectedChannel],
|
||||
)
|
||||
|
||||
termui.Render(ctx.View.Chat)
|
||||
@ -192,12 +196,12 @@ func actionChangeChannel(ctx *context.AppContext) {
|
||||
// Get message for the new channel
|
||||
ctx.View.Chat.GetMessages(
|
||||
ctx.Service,
|
||||
ctx.View.Channels.SlackChannels[ctx.View.Channels.SelectedChannel].ID,
|
||||
ctx.Service.SlackChannels[ctx.View.Channels.SelectedChannel],
|
||||
)
|
||||
|
||||
// Set channel name for the Chat pane
|
||||
ctx.View.Chat.SetBorderLabel(
|
||||
ctx.View.Channels.SlackChannels[ctx.View.Channels.SelectedChannel].Name,
|
||||
ctx.Service.Channels[ctx.View.Channels.SelectedChannel].Name,
|
||||
)
|
||||
|
||||
termui.Render(ctx.View.Channels)
|
||||
@ -205,7 +209,7 @@ func actionChangeChannel(ctx *context.AppContext) {
|
||||
}
|
||||
|
||||
func actionNewMessage(ctx *context.AppContext, channelID string) {
|
||||
ctx.View.Channels.NewMessage(channelID)
|
||||
ctx.View.Channels.NewMessage(ctx.Service, channelID)
|
||||
termui.Render(ctx.View.Channels)
|
||||
}
|
||||
|
||||
|
@ -6,14 +6,15 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/nlopes/slack"
|
||||
"github.com/erroneousboat/slack"
|
||||
)
|
||||
|
||||
type SlackService struct {
|
||||
Client *slack.Client
|
||||
RTM *slack.RTM
|
||||
Channels []slack.Channel
|
||||
UserCache map[string]string
|
||||
Client *slack.Client
|
||||
RTM *slack.RTM
|
||||
SlackChannels []interface{}
|
||||
Channels []Channel
|
||||
UserCache map[string]string
|
||||
}
|
||||
|
||||
type Channel struct {
|
||||
@ -42,17 +43,45 @@ func NewSlackService(token string) *SlackService {
|
||||
func (s *SlackService) GetChannels() []Channel {
|
||||
var chans []Channel
|
||||
|
||||
// Channel
|
||||
slackChans, err := s.Client.GetChannels(true)
|
||||
if err != nil {
|
||||
chans = append(chans, Channel{})
|
||||
}
|
||||
|
||||
s.Channels = slackChans
|
||||
|
||||
for _, slackChan := range slackChans {
|
||||
chans = append(chans, Channel{slackChan.ID, slackChan.Name})
|
||||
for _, chn := range slackChans {
|
||||
s.SlackChannels = append(s.SlackChannels, chn)
|
||||
chans = append(chans, Channel{chn.ID, chn.Name})
|
||||
}
|
||||
|
||||
// TODO: json: cannot unmarshal number into Go value of type string, GetMessages
|
||||
// Groups
|
||||
slackGroups, err := s.Client.GetGroups(true)
|
||||
if err != nil {
|
||||
chans = append(chans, Channel{})
|
||||
}
|
||||
for _, grp := range slackGroups {
|
||||
s.SlackChannels = append(s.SlackChannels, grp)
|
||||
chans = append(chans, Channel{grp.ID, grp.Name})
|
||||
}
|
||||
|
||||
// IM
|
||||
slackIM, err := s.Client.GetIMChannels()
|
||||
if err != nil {
|
||||
chans = append(chans, Channel{})
|
||||
}
|
||||
for _, im := range slackIM {
|
||||
s.SlackChannels = append(s.SlackChannels, im)
|
||||
|
||||
// Uncover name
|
||||
name, ok := s.UserCache[im.User]
|
||||
if !ok {
|
||||
name = im.User
|
||||
}
|
||||
chans = append(chans, Channel{im.ID, name})
|
||||
}
|
||||
|
||||
s.Channels = chans
|
||||
|
||||
return chans
|
||||
}
|
||||
|
||||
@ -66,7 +95,54 @@ func (s *SlackService) SendMessage(channel string, message string) {
|
||||
s.Client.PostMessage(channel, message, postParams)
|
||||
}
|
||||
|
||||
func (s *SlackService) GetMessages(channel string, count int) []string {
|
||||
func (s *SlackService) GetMessages(channel interface{}, count int) []string {
|
||||
// https://api.slack.com/methods/channels.history
|
||||
historyParams := slack.HistoryParameters{
|
||||
Count: count,
|
||||
Inclusive: false,
|
||||
Unreads: false,
|
||||
}
|
||||
|
||||
// https://godoc.org/github.com/nlopes/slack#History
|
||||
history := new(slack.History)
|
||||
var err error
|
||||
switch chnType := channel.(type) {
|
||||
case slack.Channel:
|
||||
history, err = s.Client.GetChannelHistory(chnType.ID, historyParams)
|
||||
if err != nil {
|
||||
log.Fatal(err) // FIXME
|
||||
}
|
||||
case slack.Group:
|
||||
// TODO: json: cannot unmarshal number into Go value of type string<Paste>
|
||||
history, err = s.Client.GetGroupHistory(chnType.ID, historyParams)
|
||||
if err != nil {
|
||||
log.Fatal(err) // FIXME
|
||||
}
|
||||
case slack.IM:
|
||||
history, err = s.Client.GetIMHistory(chnType.ID, historyParams)
|
||||
if err != nil {
|
||||
log.Fatal(err) // FIXME
|
||||
}
|
||||
}
|
||||
|
||||
// Construct the messages
|
||||
var messages []string
|
||||
for _, message := range history.Messages {
|
||||
msg := s.CreateMessage(message)
|
||||
messages = append(messages, msg)
|
||||
}
|
||||
|
||||
// Reverse the order of the messages, we want the newest in
|
||||
// the last place
|
||||
var messagesReversed []string
|
||||
for i := len(messages) - 1; i >= 0; i-- {
|
||||
messagesReversed = append(messagesReversed, messages[i])
|
||||
}
|
||||
|
||||
return messagesReversed
|
||||
}
|
||||
|
||||
func (s *SlackService) GetMessagesForChannel(channel string, count int) []string {
|
||||
// https://api.slack.com/methods/channels.history
|
||||
historyParams := slack.HistoryParameters{
|
||||
Count: count,
|
||||
@ -98,6 +174,10 @@ func (s *SlackService) GetMessages(channel string, count int) []string {
|
||||
return messagesReversed
|
||||
}
|
||||
|
||||
func (s *SlackService) GetMessageForGroup() {
|
||||
|
||||
}
|
||||
|
||||
// CreateMessage will create a string formatted message that can be rendered
|
||||
// in the Chat pane.
|
||||
//
|
||||
|
45
src/vendor/vendor.json
vendored
45
src/vendor/vendor.json
vendored
@ -1,6 +1,49 @@
|
||||
{
|
||||
"comment": "",
|
||||
"ignore": "test",
|
||||
"package": [],
|
||||
"package": [
|
||||
{
|
||||
"checksumSHA1": "2xFMeuNz5+Q/Zoj4UB7VLVEUUBg=",
|
||||
"path": "github.com/erroneousboat/slack",
|
||||
"revision": "bffff62454d1f96706a775078228eb4313863cf2",
|
||||
"revisionTime": "2016-10-01T12:00:35Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "h3TS3NxvpHvzwUysrtqVu2BntAE=",
|
||||
"path": "github.com/gizak/termui",
|
||||
"revision": "23b25db08316d51303b96a02edebae1649136565",
|
||||
"revisionTime": "2016-08-14T02:39:28Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "DdH3xAkzAWJ4B/LGYJyCeRsly2I=",
|
||||
"path": "github.com/mattn/go-runewidth",
|
||||
"revision": "d6bea18f789704b5f83375793155289da36a3c7f",
|
||||
"revisionTime": "2016-03-15T04:07:12Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "L3leymg2RT8hFl5uL+5KP/LpBkg=",
|
||||
"path": "github.com/mitchellh/go-wordwrap",
|
||||
"revision": "ad45545899c7b13c020ea92b2072220eefad42b8",
|
||||
"revisionTime": "2015-03-14T17:03:34Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "tmVf+KYwk1cpNJqPkL3/TIugsPI=",
|
||||
"path": "github.com/nlopes/slack",
|
||||
"revision": "49e63910abfdb2e5416d48e1b08ec8b8ac510c16",
|
||||
"revisionTime": "2016-09-26T17:06:30Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "bcgbmZhNRsfsLgd8obtn7yIey6I=",
|
||||
"path": "github.com/nsf/termbox-go",
|
||||
"revision": "e8f6d27f72a2f2bb598eb3579afd5ea364ef67f7",
|
||||
"revisionTime": "2016-08-08T04:50:38Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "likOl7O0BJntqagR050kkOBuY4o=",
|
||||
"path": "golang.org/x/net/websocket",
|
||||
"revision": "0d8126fc6144eb5257485c59148128691e8ff849",
|
||||
"revisionTime": "2016-09-30T20:34:21Z"
|
||||
}
|
||||
],
|
||||
"rootPath": "github.com/erroneousboat/slack-term/src"
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
package views
|
||||
|
||||
import (
|
||||
"github.com/gizak/termui"
|
||||
|
||||
"github.com/erroneousboat/slack-term/src/components"
|
||||
"github.com/erroneousboat/slack-term/src/service"
|
||||
|
||||
"github.com/gizak/termui"
|
||||
)
|
||||
|
||||
type View struct {
|
||||
@ -20,8 +20,9 @@ func CreateChatView(svc *service.SlackService) *View {
|
||||
channels := components.CreateChannels(svc, input.Par.Height)
|
||||
|
||||
chat := components.CreateChat(
|
||||
svc, input.Par.Height,
|
||||
channels.SlackChannels[channels.SelectedChannel],
|
||||
svc,
|
||||
input.Par.Height,
|
||||
svc.SlackChannels[channels.SelectedChannel],
|
||||
)
|
||||
|
||||
mode := components.CreateMode()
|
||||
|
Loading…
Reference in New Issue
Block a user