feat: adding help button
This commit is contained in:
165
main.go
165
main.go
@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"flag"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
@ -11,6 +12,8 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/bubbles/help"
|
||||||
|
"github.com/charmbracelet/bubbles/key"
|
||||||
"github.com/charmbracelet/bubbles/list"
|
"github.com/charmbracelet/bubbles/list"
|
||||||
"github.com/charmbracelet/bubbles/viewport"
|
"github.com/charmbracelet/bubbles/viewport"
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
@ -51,10 +54,63 @@ func getPostWithoutMetaData(post string) (string, error) {
|
|||||||
return strings.Join(splitPost[index+2:], "\n"), nil
|
return strings.Join(splitPost[index+2:], "\n"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type keyMap struct {
|
||||||
|
Up key.Binding
|
||||||
|
Down key.Binding
|
||||||
|
Left key.Binding
|
||||||
|
Right key.Binding
|
||||||
|
Blog key.Binding
|
||||||
|
Help key.Binding
|
||||||
|
Quit key.Binding
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k keyMap) ShortHelp() []key.Binding {
|
||||||
|
return []key.Binding{k.Help, k.Quit}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k keyMap) FullHelp() [][]key.Binding {
|
||||||
|
return [][]key.Binding{
|
||||||
|
{k.Up, k.Down, k.Left, k.Right},
|
||||||
|
{k.Blog, k.Help, k.Quit},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var keys = keyMap{
|
||||||
|
Up: key.NewBinding(
|
||||||
|
key.WithKeys("up", "k"),
|
||||||
|
key.WithHelp("↑/k", "move up"),
|
||||||
|
),
|
||||||
|
Down: key.NewBinding(
|
||||||
|
key.WithKeys("down", "j"),
|
||||||
|
key.WithHelp("↓/j", "move down"),
|
||||||
|
),
|
||||||
|
Left: key.NewBinding(
|
||||||
|
key.WithKeys("left", "h"),
|
||||||
|
key.WithHelp("←/h", "move left"),
|
||||||
|
),
|
||||||
|
Right: key.NewBinding(
|
||||||
|
key.WithKeys("right", "l"),
|
||||||
|
key.WithHelp("→/l", "move right"),
|
||||||
|
),
|
||||||
|
Blog: key.NewBinding(
|
||||||
|
key.WithKeys("b"),
|
||||||
|
key.WithHelp("b", "go to blog"),
|
||||||
|
),
|
||||||
|
Help: key.NewBinding(
|
||||||
|
key.WithKeys("?"),
|
||||||
|
key.WithHelp("?", "toggle help"),
|
||||||
|
),
|
||||||
|
Quit: key.NewBinding(
|
||||||
|
key.WithKeys("q", "esc", "ctrl+c"),
|
||||||
|
key.WithHelp("q", "quit"),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
type Page int
|
type Page int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
FRONT Page = iota
|
FRONT Page = iota
|
||||||
|
POST_LIST
|
||||||
POST
|
POST
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -62,10 +118,15 @@ type model struct {
|
|||||||
list list.Model
|
list list.Model
|
||||||
allPosts []string
|
allPosts []string
|
||||||
|
|
||||||
|
keys keyMap
|
||||||
|
help help.Model
|
||||||
|
|
||||||
width int
|
width int
|
||||||
height int
|
height int
|
||||||
|
|
||||||
postModel postModel
|
frontPageContent string
|
||||||
|
frontPageModel postModel
|
||||||
|
postModel postModel
|
||||||
|
|
||||||
page Page
|
page Page
|
||||||
|
|
||||||
@ -75,6 +136,16 @@ type model struct {
|
|||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getViewPort(width int, height int) viewport.Model {
|
||||||
|
vp := viewport.New(width, height)
|
||||||
|
vp.Style = lipgloss.NewStyle().
|
||||||
|
BorderStyle(lipgloss.RoundedBorder()).
|
||||||
|
BorderForeground(lipgloss.Color("62")).
|
||||||
|
PaddingRight(2)
|
||||||
|
|
||||||
|
return vp
|
||||||
|
}
|
||||||
|
|
||||||
func initialModel() model {
|
func initialModel() model {
|
||||||
posts, err := getAllPosts("/home/johnc/Code/JohnTech/content/blog")
|
posts, err := getAllPosts("/home/johnc/Code/JohnTech/content/blog")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -99,11 +170,8 @@ func initialModel() model {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
vp := viewport.New(w, h)
|
// -1 to allow for the initial height of the help model
|
||||||
vp.Style = lipgloss.NewStyle().
|
vp := getViewPort(w, h-1)
|
||||||
BorderStyle(lipgloss.RoundedBorder()).
|
|
||||||
BorderForeground(lipgloss.Color("62")).
|
|
||||||
PaddingRight(2)
|
|
||||||
|
|
||||||
// We need to adjust the width of the glamour render from our main width
|
// We need to adjust the width of the glamour render from our main width
|
||||||
// to account for a few things:
|
// to account for a few things:
|
||||||
@ -124,7 +192,22 @@ func initialModel() model {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return model{list: list, allPosts: posts, page: FRONT, termRenderer: renderer, viewport: vp}
|
frontPageContent, err := os.ReadFile("/home/johnc/Code/JohnTech/content/_index.md")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
processedFrontPage, err := processMarkdown(string(frontPageContent))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
frontPageModel, err := createMarkdownPostModel(processedFrontPage, renderer, vp)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return model{list: list, allPosts: posts, page: FRONT, termRenderer: renderer, viewport: vp, frontPageModel: frontPageModel, frontPageContent: processedFrontPage, help: help.New(), keys: keys, width: w, height: h}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m model) Init() tea.Cmd {
|
func (m model) Init() tea.Cmd {
|
||||||
@ -134,6 +217,44 @@ func (m model) Init() tea.Cmd {
|
|||||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
switch m.page {
|
switch m.page {
|
||||||
case FRONT:
|
case FRONT:
|
||||||
|
updatedFrontPageModel, cmd := m.frontPageModel.Update(msg)
|
||||||
|
m.frontPageModel = updatedFrontPageModel.(postModel)
|
||||||
|
|
||||||
|
if cmd != nil {
|
||||||
|
if _, ok := cmd().(childMessage); ok {
|
||||||
|
return m, tea.Quit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case tea.KeyMsg:
|
||||||
|
switch {
|
||||||
|
case key.Matches(msg, m.keys.Help):
|
||||||
|
m.help.ShowAll = !m.help.ShowAll
|
||||||
|
|
||||||
|
adjustedViewportHeight := 0
|
||||||
|
if m.help.ShowAll {
|
||||||
|
adjustedViewportHeight = 4
|
||||||
|
} else {
|
||||||
|
adjustedViewportHeight = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
viewportYOffset := m.frontPageModel.viewport.YOffset
|
||||||
|
m.viewport = getViewPort(m.width, m.height-adjustedViewportHeight)
|
||||||
|
m.viewport.SetYOffset(viewportYOffset)
|
||||||
|
|
||||||
|
frontPageModel, err := createMarkdownPostModel(m.frontPageContent, m.termRenderer, m.viewport)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.frontPageModel = frontPageModel
|
||||||
|
case key.Matches(msg, m.keys.Blog):
|
||||||
|
m.page = POST_LIST
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, cmd
|
||||||
|
case POST_LIST:
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
case tea.KeyMsg:
|
case tea.KeyMsg:
|
||||||
switch msg.String() {
|
switch msg.String() {
|
||||||
@ -141,8 +262,12 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
m.page = POST
|
m.page = POST
|
||||||
selectedItem := m.list.SelectedItem().(item)
|
selectedItem := m.list.SelectedItem().(item)
|
||||||
|
|
||||||
postModel, err := createPostModel(m.allPosts[selectedItem.index], m.termRenderer, m.viewport)
|
processedPost, err := processMarkdown(m.allPosts[selectedItem.index])
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
postModel, err := createMarkdownPostModel(processedPost, m.termRenderer, m.viewport)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -166,7 +291,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
updatedPostModel, cmd := m.postModel.Update(msg)
|
updatedPostModel, cmd := m.postModel.Update(msg)
|
||||||
if cmd != nil {
|
if cmd != nil {
|
||||||
if _, ok := cmd().(childMessage); ok {
|
if _, ok := cmd().(childMessage); ok {
|
||||||
m.page = FRONT
|
m.page = POST_LIST
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,6 +307,10 @@ func (m model) View() string {
|
|||||||
// TODO: better type safety would be to do this over the type.
|
// TODO: better type safety would be to do this over the type.
|
||||||
switch m.page {
|
switch m.page {
|
||||||
case FRONT:
|
case FRONT:
|
||||||
|
helpView := m.help.View(m.keys)
|
||||||
|
|
||||||
|
return m.frontPageModel.View() + "\n" + helpView
|
||||||
|
case POST_LIST:
|
||||||
return docStyle.Render(m.list.View())
|
return docStyle.Render(m.list.View())
|
||||||
case POST:
|
case POST:
|
||||||
return m.postModel.View()
|
return m.postModel.View()
|
||||||
@ -195,7 +324,7 @@ const (
|
|||||||
port = "23234"
|
port = "23234"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func initServer() {
|
||||||
s, err := wish.NewServer(
|
s, err := wish.NewServer(
|
||||||
wish.WithAddress(net.JoinHostPort(host, port)),
|
wish.WithAddress(net.JoinHostPort(host, port)),
|
||||||
wish.WithHostKeyPath(".ssh/id_rsa"),
|
wish.WithHostKeyPath(".ssh/id_rsa"),
|
||||||
@ -228,6 +357,22 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var isSsh bool
|
||||||
|
flag.BoolVar(&isSsh, "ssh", false, "Start an SSH server instead of the TUI application.")
|
||||||
|
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if isSsh {
|
||||||
|
initServer()
|
||||||
|
} else {
|
||||||
|
p := tea.NewProgram(initialModel())
|
||||||
|
if _, err := p.Run(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// You can wire any Bubble Tea model up to the middleware with a function that
|
// You can wire any Bubble Tea model up to the middleware with a function that
|
||||||
// handles the incoming ssh.Session. Here we just grab the terminal info and
|
// handles the incoming ssh.Session. Here we just grab the terminal info and
|
||||||
// pass it to the new model. You can also return tea.ProgramOptions (such as
|
// pass it to the new model. You can also return tea.ProgramOptions (such as
|
||||||
|
19
post.go
19
post.go
@ -42,31 +42,32 @@ func (m postModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createPostModel(post string, termRender *glamour.TermRenderer, viewPort viewport.Model) (postModel, error) {
|
func processMarkdown(markdown string) (string, error) {
|
||||||
postContent, err := getPostWithoutMetaData(post)
|
postContent, err := getPostWithoutMetaData(markdown)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return postModel{}, err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
postInfo, err := getPostInfo(post)
|
postInfo, err := getPostInfo(markdown)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return postModel{}, err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is stupid
|
// This is stupid
|
||||||
lines := strings.Split(postContent, "\n")
|
lines := strings.Split(postContent, "\n")
|
||||||
lines = append([]string{"# " + postInfo.Title[1:len(postInfo.Title)-1]}, lines...)
|
lines = append([]string{"# " + postInfo.Title[1:len(postInfo.Title)-1]}, lines...)
|
||||||
|
|
||||||
post = strings.Join(lines, "\n")
|
return strings.Join(lines, "\n"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createMarkdownPostModel(markdown string, termRender *glamour.TermRenderer, viewPort viewport.Model) (postModel, error) {
|
||||||
m := postModel{
|
m := postModel{
|
||||||
viewport: viewPort,
|
viewport: viewPort,
|
||||||
}
|
}
|
||||||
|
|
||||||
out, err := termRender.Render(post)
|
out, err := termRender.Render(markdown)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: fix this shit
|
return postModel{}, err
|
||||||
panic(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m.viewport.SetContent(out)
|
m.viewport.SetContent(out)
|
||||||
|
Reference in New Issue
Block a user