Phase 5: TUI client (Bubbletea)
- cmd/tui/main.go: entry point with --server, --token, --version flags
- cmd/tui/config.go: config from ~/.config/dumpster/config.yaml or env vars
- cmd/tui/auth.go: login flow, session cookie management
- cmd/tui/api.go: HTTP client for servers, channels, messages, voice
- cmd/tui/model.go: root bubbletea model with state management
- cmd/tui/theme.go: Gruvbox dark lipgloss styles
- cmd/tui/servers.go: server bar with [D] style initials
- cmd/tui/channels.go: channel list with #text and 🔊 voice
- cmd/tui/chat.go: IRC-style message viewport with colored usernames
- cmd/tui/input.go: message input with > _ prompt
- cmd/tui/members.go: member list with ●/◐/○ status
- cmd/tui/ws.go: WebSocket client for real-time events
- cmd/tui/voice.go: voice state tracking and mute toggle
- cmd/tui/keys.go: vim-style keybindings (j/k, g/G, Tab, /, etc)
- cmd/tui/views.go: multi-pane layout rendering
- Makefile: added build-tui and run-tui targets
- go.mod/go.sum: added charmbracelet dependencies
This commit is contained in:
+116
@@ -0,0 +1,116 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
// RenderMessages renders the chat message list for the viewport.
|
||||
func RenderMessages(messages []Message, width int, currentUser string) string {
|
||||
if len(messages) == 0 {
|
||||
return HelpStyle.Render(" No messages yet. Press 'i' to type a message.")
|
||||
}
|
||||
|
||||
var lines []string
|
||||
|
||||
for _, msg := range messages {
|
||||
ts := formatTimestamp(msg.CreatedAt)
|
||||
|
||||
// Determine display name
|
||||
displayName := msg.AuthorName
|
||||
if msg.DisplayName != nil && *msg.DisplayName != "" {
|
||||
displayName = *msg.DisplayName
|
||||
}
|
||||
|
||||
// Color the username
|
||||
userColor := UsernameColor(msg.AuthorName)
|
||||
styledUser := UsernameStyle.Foreground(userColor).Render(displayName)
|
||||
|
||||
// Content
|
||||
content := msg.Content
|
||||
|
||||
// Show edited tag
|
||||
editedTag := ""
|
||||
if msg.EditedAt != nil {
|
||||
editedTag = EditedTag.Render(" (edited)")
|
||||
}
|
||||
|
||||
// IRC-style: [HH:MM] <user> message
|
||||
tsRendered := TimestampStyle.Render("[" + ts + "]")
|
||||
prefix := fmt.Sprintf("%s <%s> ", tsRendered, styledUser)
|
||||
|
||||
// Word wrap the content
|
||||
contentWidth := width - lipgloss.Width(prefix) - 2
|
||||
if contentWidth < 10 {
|
||||
contentWidth = 40
|
||||
}
|
||||
wrappedContent := wordWrap(content, contentWidth)
|
||||
|
||||
// First line has the prefix, continuation lines are indented
|
||||
contentLines := strings.Split(wrappedContent, "\n")
|
||||
for i, cl := range contentLines {
|
||||
if i == 0 {
|
||||
lines = append(lines, prefix+cl+editedTag)
|
||||
} else {
|
||||
indent := strings.Repeat(" ", lipgloss.Width(prefix))
|
||||
lines = append(lines, indent+cl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
// formatTimestamp parses the created_at string and returns HH:MM.
|
||||
func formatTimestamp(ts string) string {
|
||||
// Try various formats the server might return
|
||||
formats := []string{
|
||||
time.RFC3339,
|
||||
"2006-01-02T15:04:05.999999Z",
|
||||
"2006-01-02T15:04:05.999999-07:00",
|
||||
"2006-01-02 15:04:05",
|
||||
"2006-01-02T15:04:05Z",
|
||||
}
|
||||
|
||||
for _, f := range formats {
|
||||
if t, err := time.Parse(f, ts); err == nil {
|
||||
return t.Format("15:04")
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: try to extract HH:MM from the string
|
||||
if len(ts) >= 16 {
|
||||
return ts[11:16]
|
||||
}
|
||||
return "??:??"
|
||||
}
|
||||
|
||||
// wordWrap wraps text to the given width.
|
||||
func wordWrap(text string, width int) string {
|
||||
if width <= 0 {
|
||||
return text
|
||||
}
|
||||
|
||||
words := strings.Fields(text)
|
||||
if len(words) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
var lines []string
|
||||
currentLine := words[0]
|
||||
|
||||
for _, word := range words[1:] {
|
||||
if len(currentLine)+1+len(word) > width {
|
||||
lines = append(lines, currentLine)
|
||||
currentLine = word
|
||||
} else {
|
||||
currentLine += " " + word
|
||||
}
|
||||
}
|
||||
lines = append(lines, currentLine)
|
||||
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
Reference in New Issue
Block a user