diff --git a/config/config.go b/config/config.go index 78294f7..e5f1bb1 100644 --- a/config/config.go +++ b/config/config.go @@ -5,6 +5,7 @@ import ( "path" "strings" + "github.com/charmbracelet/lipgloss" "github.com/pelletier/go-toml/v2" "github.com/spf13/viper" ) @@ -24,11 +25,46 @@ type SystemConfig struct { Config map[string]interface{} } +type ThemeItemConfig struct { + Foreground lipgloss.AdaptiveColor + Background lipgloss.AdaptiveColor + Border struct { + Foreground lipgloss.AdaptiveColor + Background lipgloss.AdaptiveColor + Border lipgloss.Border + Sides []bool + } + Padding []int + Margin []int +} + type Config struct { Debug bool Log string Systems []SystemConfig + + Theme struct { + DialogBox struct { + Window ThemeItemConfig + Titlebar ThemeItemConfig + Bottombar ThemeItemConfig + } + + PostsList struct { + List ThemeItemConfig + Item ThemeItemConfig + } + + Post struct { + Author ThemeItemConfig + Subject ThemeItemConfig + } + + Reply struct { + Author ThemeItemConfig + } + } } func Load() (Config, error) { @@ -48,6 +84,72 @@ func Load() (Config, error) { viper.SetDefault("Debug", "true") viper.SetDefault("Log", path.Join(cacheDir, "gobbs.log")) + // PostsList List + viper.SetDefault("Theme.PostsList.List.Margin", + []int{0, 0, 0, 0}) + viper.SetDefault("Theme.PostsList.List.Padding", + []int{1, 1, 1, 1}) + viper.SetDefault("Theme.PostsList.List.Border.Border", + lipgloss.RoundedBorder()) + viper.SetDefault("Theme.PostsList.List.Border.Sides", + []bool{true, true, true, true}, + ) + viper.SetDefault("Theme.PostsList.List.Border.Foreground", + lipgloss.AdaptiveColor{Light: "#333333", Dark: "#cccccc"}) + + // DialogBox Window + viper.SetDefault("Theme.DialogBox.Window.Margin", + []int{0, 0, 0, 0}) + viper.SetDefault("Theme.DialogBox.Window.Padding", + []int{0, 0, 0, 0}) + viper.SetDefault("Theme.DialogBox.Window.Border.Border", + lipgloss.ThickBorder()) + viper.SetDefault("Theme.DialogBox.Window.Border.Sides", + []bool{false, true, true, true}, + ) + viper.SetDefault("Theme.DialogBox.Window.Border.Foreground", + lipgloss.AdaptiveColor{Light: "#333333", Dark: "#cccccc"}) + + // DialogBox Titlebar + viper.SetDefault("Theme.DialogBox.Titlebar.Margin", + []int{0, 0, 1, 0}) + viper.SetDefault("Theme.DialogBox.Titlebar.Padding", + []int{0, 1, 0, 1}) + viper.SetDefault("Theme.DialogBox.Titlebar.Foreground", + lipgloss.AdaptiveColor{Light: "#ffffff", Dark: "#000000"}) + viper.SetDefault("Theme.DialogBox.Titlebar.Background", + lipgloss.AdaptiveColor{Light: "#333333", Dark: "#cccccc"}) + + // DialogBox Bottombar + viper.SetDefault("Theme.DialogBox.Bottombar.Margin", + []int{1, 0, 0, 0}) + viper.SetDefault("Theme.DialogBox.Bottombar.Padding", + []int{0, 1, 0, 1}) + viper.SetDefault("Theme.DialogBox.Bottombar.Foreground", + lipgloss.AdaptiveColor{Light: "#aaaaaa", Dark: "#999999"}) + + // Post Author + viper.SetDefault("Theme.Post.Author.Padding", + []int{0, 1, 0, 1}) + viper.SetDefault("Theme.Post.Author.Foreground", + lipgloss.AdaptiveColor{Light: "#F25D94", Dark: "#F25D94"}) + + // Post Subject + viper.SetDefault("Theme.Post.Subject.Padding", + []int{0, 1, 0, 1}) + viper.SetDefault("Theme.Post.Subject.Foreground", + lipgloss.AdaptiveColor{Light: "#FFFFFF", Dark: "#FFFFFF"}) + viper.SetDefault("Theme.Post.Subject.Background", + lipgloss.AdaptiveColor{Light: "#F25D94", Dark: "#F25D94"}) + + // Reply Author + viper.SetDefault("Theme.Reply.Author.Padding", + []int{0, 1, 0, 1}) + viper.SetDefault("Theme.Reply.Author.Foreground", + lipgloss.AdaptiveColor{Light: "#000000", Dark: "#00000"}) + viper.SetDefault("Theme.Reply.Author.Foreground", + lipgloss.AdaptiveColor{Light: "#874BFD", Dark: "#874BFD"}) + viper.SetConfigName("gobbs") viper.SetConfigType("toml") viper.AddConfigPath(cfgDir) diff --git a/models/post/post.go b/models/post/post.go index ffa07a0..982c748 100644 --- a/models/post/post.go +++ b/models/post/post.go @@ -41,10 +41,9 @@ func (post Post) Title() string { func (post Post) Description() string { return fmt.Sprintf( - "[%s] %s in %s on %s", - post.ID, - post.Author.Name, + "in %s by %s on %s", post.Forum.Name, - post.CreatedAt.Format("Jan 2 2006"), + post.Author.Name, + post.CreatedAt.Format("02 Jan 06 15:04 MST"), ) } diff --git a/ui/ctx/ctx.go b/ui/ctx/ctx.go index 3dd7e1a..c95017d 100644 --- a/ui/ctx/ctx.go +++ b/ui/ctx/ctx.go @@ -3,6 +3,7 @@ package ctx import ( "github.com/mrusme/gobbs/config" "github.com/mrusme/gobbs/system" + "github.com/mrusme/gobbs/ui/theme" "go.uber.org/zap" ) @@ -13,6 +14,7 @@ type Ctx struct { Systems []*system.System Loading bool Logger *zap.SugaredLogger + Theme *theme.Theme } func New( @@ -25,6 +27,7 @@ func New( Config: cfg, Loading: false, Logger: logger, + Theme: theme.New(cfg), } } diff --git a/ui/helpers/helpers.go b/ui/helpers/overlay.go similarity index 100% rename from ui/helpers/helpers.go rename to ui/helpers/overlay.go diff --git a/ui/theme/theme.go b/ui/theme/theme.go new file mode 100644 index 0000000..35bebaf --- /dev/null +++ b/ui/theme/theme.go @@ -0,0 +1,114 @@ +package theme + +import ( + "github.com/charmbracelet/lipgloss" + "github.com/mrusme/gobbs/config" +) + +type Theme struct { + DialogBox struct { + Window lipgloss.Style + Titlebar lipgloss.Style + Bottombar lipgloss.Style + } + + PostsList struct { + List lipgloss.Style + Item lipgloss.Style + } + + Post struct { + Author lipgloss.Style + Subject lipgloss.Style + } + + Reply struct { + Author lipgloss.Style + } +} + +func New(cfg *config.Config) (*Theme) { + t := new(Theme) + // viewportStyle = lipgloss.NewStyle(). + // Margin(0, 0, 0, 0). + // Padding(0, 0). + // BorderTop(false). + // BorderLeft(false). + // BorderRight(false). + // BorderBottom(false) + // + + t.PostsList.List = lipgloss.NewStyle(). + Margin(cfg.Theme.PostsList.List.Margin...). + Padding(cfg.Theme.PostsList.List.Padding...). + Border(cfg.Theme.PostsList.List.Border.Border, cfg.Theme.PostsList.List.Border.Sides...). + BorderForeground(cfg.Theme.PostsList.List.Border.Foreground). + BorderBackground(cfg.Theme.PostsList.List.Border.Background). + Foreground(cfg.Theme.PostsList.List.Foreground). + Background(cfg.Theme.PostsList.List.Background) + + t.PostsList.Item = lipgloss.NewStyle(). + Margin(cfg.Theme.PostsList.Item.Margin...). + Padding(cfg.Theme.PostsList.Item.Padding...). + Border(cfg.Theme.PostsList.Item.Border.Border, cfg.Theme.PostsList.Item.Border.Sides...). + BorderForeground(cfg.Theme.PostsList.Item.Border.Foreground). + BorderBackground(cfg.Theme.PostsList.Item.Border.Background). + Foreground(cfg.Theme.PostsList.Item.Foreground). + Background(cfg.Theme.PostsList.Item.Background) + + t.DialogBox.Window = lipgloss.NewStyle(). + Margin(cfg.Theme.DialogBox.Window.Margin...). + Padding(cfg.Theme.DialogBox.Window.Padding...). + Border(cfg.Theme.DialogBox.Window.Border.Border, cfg.Theme.DialogBox.Window.Border.Sides...). + BorderForeground(cfg.Theme.DialogBox.Window.Border.Foreground). + BorderBackground(cfg.Theme.DialogBox.Window.Border.Background). + Foreground(cfg.Theme.DialogBox.Window.Foreground). + Background(cfg.Theme.DialogBox.Window.Background) + + t.DialogBox.Titlebar = lipgloss.NewStyle(). + Margin(cfg.Theme.DialogBox.Titlebar.Margin...). + Padding(cfg.Theme.DialogBox.Titlebar.Padding...). + Border(cfg.Theme.DialogBox.Titlebar.Border.Border, cfg.Theme.DialogBox.Titlebar.Border.Sides...). + BorderForeground(cfg.Theme.DialogBox.Titlebar.Border.Foreground). + BorderBackground(cfg.Theme.DialogBox.Titlebar.Border.Background). + Foreground(cfg.Theme.DialogBox.Titlebar.Foreground). + Background(cfg.Theme.DialogBox.Titlebar.Background) + + t.DialogBox.Bottombar = lipgloss.NewStyle(). + Margin(cfg.Theme.DialogBox.Bottombar.Margin...). + Padding(cfg.Theme.DialogBox.Bottombar.Padding...). + Border(cfg.Theme.DialogBox.Bottombar.Border.Border, cfg.Theme.DialogBox.Bottombar.Border.Sides...). + BorderForeground(cfg.Theme.DialogBox.Bottombar.Border.Foreground). + BorderBackground(cfg.Theme.DialogBox.Bottombar.Border.Background). + Foreground(cfg.Theme.DialogBox.Bottombar.Foreground). + Background(cfg.Theme.DialogBox.Bottombar.Background) + + t.Post.Author = lipgloss.NewStyle(). + Margin(cfg.Theme.Post.Author.Margin...). + Padding(cfg.Theme.Post.Author.Padding...). + Border(cfg.Theme.Post.Author.Border.Border, cfg.Theme.Post.Author.Border.Sides...). + BorderForeground(cfg.Theme.Post.Author.Border.Foreground). + BorderBackground(cfg.Theme.Post.Author.Border.Background). + Foreground(cfg.Theme.Post.Author.Foreground). + Background(cfg.Theme.Post.Author.Background) + + t.Post.Subject = lipgloss.NewStyle(). + Margin(cfg.Theme.Post.Subject.Margin...). + Padding(cfg.Theme.Post.Subject.Padding...). + Border(cfg.Theme.Post.Subject.Border.Border, cfg.Theme.Post.Subject.Border.Sides...). + BorderForeground(cfg.Theme.Post.Subject.Border.Foreground). + BorderBackground(cfg.Theme.Post.Subject.Border.Background). + Foreground(cfg.Theme.Post.Subject.Foreground). + Background(cfg.Theme.Post.Subject.Background) + + t.Reply.Author = lipgloss.NewStyle(). + Margin(cfg.Theme.Reply.Author.Margin...). + Padding(cfg.Theme.Reply.Author.Padding...). + Border(cfg.Theme.Reply.Author.Border.Border, cfg.Theme.Reply.Author.Border.Sides...). + BorderForeground(cfg.Theme.Reply.Author.Border.Foreground). + BorderBackground(cfg.Theme.Reply.Author.Border.Background). + Foreground(cfg.Theme.Reply.Author.Foreground). + Background(cfg.Theme.Reply.Author.Background) + + return t +} diff --git a/ui/views/posts/posts.go b/ui/views/posts/posts.go index a00c6c3..c6d2c88 100644 --- a/ui/views/posts/posts.go +++ b/ui/views/posts/posts.go @@ -18,71 +18,13 @@ import ( ) var ( - ViewBorderColor = lipgloss.AdaptiveColor{ - Light: "#b0c4de", - Dark: "#b0c4de", - } - - DialogBorderColor = lipgloss.AdaptiveColor{ - Light: "#b0c4de", - Dark: "#b0c4de", - } -) - -var ( - listStyle = lipgloss.NewStyle(). - Margin(0, 0, 0, 0). - Padding(1, 1). - Border(lipgloss.DoubleBorder()). - BorderForeground(ViewBorderColor). - BorderTop(true). - BorderLeft(true). - BorderRight(true). - BorderBottom(true) - viewportStyle = lipgloss.NewStyle(). - Margin(0, 0, 0, 0). - Padding(0, 0). - BorderTop(false). - BorderLeft(false). - BorderRight(false). - BorderBottom(false) - - dialogBoxStyle = lipgloss.NewStyle(). - Border(lipgloss.ThickBorder()). - BorderForeground(DialogBorderColor). - Padding(0, 0). - Margin(0, 0, 0, 0). - BorderTop(false). - BorderLeft(true). - BorderRight(true). - BorderBottom(true) - - dialogBoxTitlebarStyle = lipgloss.NewStyle(). - Align(lipgloss.Center). - Background(lipgloss.Color("#87cefa")). - Foreground(lipgloss.Color("#000000")). - Padding(0, 1). - Margin(0, 0, 1, 0) - - dialogBoxBottombarStyle = lipgloss.NewStyle(). - Foreground(lipgloss.Color("#999999")). - Padding(0, 1). - Margin(1, 0, 0, 0) - - postAuthorStyle = lipgloss.NewStyle(). - Foreground(lipgloss.Color("#F25D94")). - Padding(0, 1) - - postSubjectStyle = lipgloss.NewStyle(). - Foreground(lipgloss.Color("#FFFFFF")). - Background(lipgloss.Color("#F25D94")). - Padding(0, 1) - - replyAuthorStyle = lipgloss.NewStyle(). - Foreground(lipgloss.Color("#000000")). - Background(lipgloss.Color("#874BFD")). - Padding(0, 1) + Margin(0, 0, 0, 0). + Padding(0, 0). + BorderTop(false). + BorderLeft(false). + BorderRight(false). + BorderBottom(false) ) type KeyMap struct { @@ -130,7 +72,8 @@ func NewModel(c *ctx.Ctx) Model { } m.list = list.New(m.items, list.NewDefaultDelegate(), 0, 0) - m.list.Title = "Posts" + m.list.SetShowTitle(false) + m.list.SetShowStatusBar(false) m.ctx = c m.a, _ = aggregator.New(m.ctx) @@ -167,8 +110,8 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { viewportWidth := m.ctx.Content[0] - 9 viewportHeight := m.ctx.Content[1] - 10 - listStyle.Width(listWidth) - listStyle.Height(listHeight) + m.ctx.Theme.PostsList.List.Width(listWidth) + m.ctx.Theme.PostsList.List.Height(listHeight) m.list.SetSize( listWidth-2, listHeight-2, @@ -196,12 +139,12 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmd tea.Cmd if m.viewportOpen == false { - // listStyle.BorderForeground(lipgloss.Color("#FFFFFF")) + // m.ctx.Theme.PostsList.List.BorderForeground(lipgloss.Color("#FFFFFF")) // viewportStyle.BorderForeground(lipgloss.Color("#874BFD")) m.list, cmd = m.list.Update(msg) cmds = append(cmds, cmd) } else if m.viewportOpen == true { - // listStyle.BorderForeground(lipgloss.Color("#874BFD")) + // m.ctx.Theme.PostsList.List.BorderForeground(lipgloss.Color("#874BFD")) // viewportStyle.BorderForeground(lipgloss.Color("#FFFFFF")) m.viewport, cmd = m.viewport.Update(msg) cmds = append(cmds, cmd) @@ -215,15 +158,16 @@ func (m Model) View() string { view.WriteString(lipgloss.JoinHorizontal( lipgloss.Top, - listStyle.Render(m.list.View()), + m.ctx.Theme.PostsList.List.Render(m.list.View()), )) if m.viewportOpen { - titlebar := dialogBoxTitlebarStyle. + titlebar := m.ctx.Theme.DialogBox.Titlebar. + Align(lipgloss.Center). Width(m.viewport.Width + 4). Render("Post") - bottombar := dialogBoxBottombarStyle. + bottombar := m.ctx.Theme.DialogBox.Bottombar. Width(m.viewport.Width + 4). Render("r reply ยท esc close") @@ -234,7 +178,9 @@ func (m Model) View() string { bottombar, ) - return helpers.PlaceOverlay(3, 2, dialogBoxStyle.Render(ui), view.String(), true) + return helpers.PlaceOverlay(3, 2, + m.ctx.Theme.DialogBox.Window.Render(ui), + view.String(), true) } return view.String() @@ -288,10 +234,10 @@ func (m *Model) renderViewport(p *post.Post) string { } out += fmt.Sprintf( " %s\n %s\n%s", - postAuthorStyle.Render( + m.ctx.Theme.Post.Author.Render( fmt.Sprintf("%s %s:", p.Author.Name, adj), ), - postSubjectStyle.Render(p.Subject), + m.ctx.Theme.Post.Subject.Render(p.Subject), body, ) @@ -331,7 +277,7 @@ func (m *Model) renderReplies( } out += fmt.Sprintf( "\n\n %s %s\n%s", - replyAuthorStyle.Render( + m.ctx.Theme.Reply.Author.Render( author, ), lipgloss.NewStyle().Foreground(lipgloss.Color("#874BFD")).Render(