add hotkey menu navigation

Closes: #13
This commit is contained in:
nbsp 2025-04-29 05:18:30 +03:00
parent 9d4053da0b
commit 350727cd58
No known key found for this signature in database
GPG Key ID: 7184AC1C9835CE48
14 changed files with 166 additions and 61 deletions

1
.nanpa/salsa-old-jam.kdl Normal file
View File

@ -0,0 +1 @@
patch type="added" "shortcuts to list items in menu and management"

View File

@ -4,7 +4,7 @@ SCDOC ?= scdoc
all: neofeels doc all: neofeels doc
neofeels: neofeels: **/*.go
$(GO) build $(GOFLAGS) . $(GO) build $(GOFLAGS) .
ifeq (, $(shell which $(SCDOC) 2>/dev/null)) ifeq (, $(shell which $(SCDOC) 2>/dev/null))
@ -18,6 +18,3 @@ endif
clean: clean:
rm neofeels doc/neofeels.1 rm neofeels doc/neofeels.1
.PHONY: all neofeels doc clean

View File

@ -25,14 +25,14 @@ type Backups struct {
func NewBackups() *Backups { func NewBackups() *Backups {
os.MkdirAll(ttbp.PathUserBackups, 0700) os.MkdirAll(ttbp.PathUserBackups, 0700)
backups, _ := os.ReadDir(ttbp.PathUserBackups) backups, _ := os.ReadDir(ttbp.PathUserBackups)
list := []string{} list := [][]vaxis.Segment{}
for _, backup := range backups { for _, backup := range backups {
timestamp, err := time.Parse("feels-backup-20060102-150405.tar.gz", backup.Name()) timestamp, err := time.Parse("feels-backup-20060102-150405.tar.gz", backup.Name())
if err != nil { if err != nil {
continue continue
} }
// XXX: for some reason this gets fucked up with timezones??? // XXX: for some reason this gets fucked up with timezones???
list = append(list, humanize.Time(timestamp.Local())) list = append(list, []vaxis.Segment{{Text: humanize.Time(timestamp.Local())}})
} }
return &Backups{ return &Backups{

View File

@ -23,14 +23,14 @@ func NewBrowse() *Browse {
posts = append(posts, ttbp.GetPostsForUser(user.Name)...) posts = append(posts, ttbp.GetPostsForUser(user.Name)...)
} }
posts = ttbp.SortPostsByRecent(posts) posts = ttbp.SortPostsByRecent(posts)
list := []string{} list := [][]vaxis.Segment{}
for _, post := range posts { for _, post := range posts {
list = append(list, fmt.Sprintf( list = append(list, []vaxis.Segment{{Text: fmt.Sprintf(
"~%-15s %s (%d words)", "~%-15s %s (%d words)",
post.Author, post.Author,
post.LastEdited.Format("2006-01-02 15:04"), post.LastEdited.Format("2006-01-02 15:04"),
post.Words, post.Words,
)) )}})
} }
return &Browse{ return &Browse{

View File

@ -23,13 +23,13 @@ type Bury struct {
func NewBury() *Bury { func NewBury() *Bury {
user, _ := user.Current() user, _ := user.Current()
posts := ttbp.GetPostsForUser(user.Username) posts := ttbp.GetPostsForUser(user.Username)
list := []string{} list := [][]vaxis.Segment{}
for _, post := range posts { for _, post := range posts {
list = append(list, fmt.Sprintf( list = append(list, []vaxis.Segment{{Text: fmt.Sprintf(
"%s (%d words)", "%s (%d words)",
post.Date.Format("2006-01-02"), post.Date.Format("2006-01-02"),
post.Words, post.Words,
)) )}})
} }
return &Bury{ return &Bury{

View File

@ -18,12 +18,12 @@ type Config struct {
descriptions []string descriptions []string
} }
var configList = []string{ var configList = [][]vaxis.Segment{
"pager", []vaxis.Segment{{Text: "pager"}},
"publish to html", []vaxis.Segment{{Text: "publish to html"}},
"publish to gopher", []vaxis.Segment{{Text: "publish to gopher"}},
"default to nopub", []vaxis.Segment{{Text: "default to nopub"}},
"default to html", []vaxis.Segment{{Text: "default to html"}},
} }
func NewConfig() *Config { func NewConfig() *Config {
@ -112,7 +112,7 @@ func (config *Config) Event(state *ui.State, event vaxis.Event) (processed bool)
func (config *Config) Draw(state *ui.State) { func (config *Config) Draw(state *ui.State) {
if config.config.Publishing { if config.config.Publishing {
config.list.SetItems(append(configList, "html publish directory")) config.list.SetItems(append(configList, []vaxis.Segment{{Text: "html publish directory"}}))
} else { } else {
config.list.SetItems(configList) config.list.SetItems(configList)
} }

View File

@ -22,13 +22,13 @@ type Delete struct {
func NewDelete() *Delete { func NewDelete() *Delete {
user, _ := user.Current() user, _ := user.Current()
posts := ttbp.GetPostsForUser(user.Username) posts := ttbp.GetPostsForUser(user.Username)
list := []string{} list := [][]vaxis.Segment{}
for _, post := range posts { for _, post := range posts {
list = append(list, fmt.Sprintf( list = append(list, []vaxis.Segment{{Text: fmt.Sprintf(
"%s (%d words)", "%s (%d words)",
post.Date.Format("2006-01-02"), post.Date.Format("2006-01-02"),
post.Words, post.Words,
)) )}})
} }
return &Delete{ return &Delete{

View File

@ -23,15 +23,40 @@ type Management struct {
func NewManagement(index int) *Management { func NewManagement(index int) *Management {
return &Management{ return &Management{
title, title,
ui.NewList([]string{ ui.NewList([][]vaxis.Segment{
"read over feels", {
"modify feels publishing", {Text: "r", Style: vaxis.Style{Attribute: vaxis.AttrReverse}},
"backup your feels", {Text: "ead over feels"},
"import a feels backup", },
"bury some feels", {
"delete feels by day", {Text: "m", Style: vaxis.Style{Attribute: vaxis.AttrReverse}},
"purge all feels", {Text: "odify feels publishing"},
"wipe feels account", },
{
{Text: "b", Style: vaxis.Style{Attribute: vaxis.AttrReverse}},
{Text: "ackup your feels"},
},
{
{Text: "i", Style: vaxis.Style{Attribute: vaxis.AttrReverse}},
{Text: "mport a feels backup"},
},
{
{Text: "b"},
{Text: "u", Style: vaxis.Style{Attribute: vaxis.AttrReverse}},
{Text: "ry some feels"},
},
{
{Text: "d", Style: vaxis.Style{Attribute: vaxis.AttrReverse}},
{Text: "elete feels by day"},
},
{
{Text: "p", Style: vaxis.Style{Attribute: vaxis.AttrReverse}},
{Text: "urge all feels"},
},
{
{Text: "w", Style: vaxis.Style{Attribute: vaxis.AttrReverse}},
{Text: "ipe feels account"},
},
}, index), }, index),
"↑↓/kj move ↵ enter q return", "↑↓/kj move ↵ enter q return",
} }
@ -50,6 +75,22 @@ func (management *Management) Event(state *ui.State, event vaxis.Event) (process
management.list.End() management.list.End()
case "Home", "g": case "Home", "g":
management.list.Home() management.list.Home()
case "r":
management.list.SetIndex(0)
case "m":
management.list.SetIndex(1)
case "b":
management.list.SetIndex(2)
case "i":
management.list.SetIndex(3)
case "u":
management.list.SetIndex(4)
case "d":
management.list.SetIndex(5)
case "p":
management.list.SetIndex(6)
case "w":
management.list.SetIndex(7)
case "0", "1", "2", "3", "4", "5", "6", "7": case "0", "1", "2", "3", "4", "5", "6", "7":
i, _ := strconv.Atoi(key.String()) i, _ := strconv.Atoi(key.String())
management.list.SetIndex(i) management.list.SetIndex(i)

View File

@ -30,16 +30,48 @@ const title = ` ___ __
func NewMainMenu(index int) *MainMenu { func NewMainMenu(index int) *MainMenu {
return &MainMenu{ return &MainMenu{
title, title,
ui.NewList([]string{ ui.NewList([][]vaxis.Segment{
"record some feels", {
"manage your feels", {Text: "r", Style: vaxis.Style{Attribute: vaxis.AttrReverse}},
"check out your neighbors", {Text: "ecord some feels"},
"browse global feels", },
"visit your subscriptions", {
"scribble some graffiti", {Text: "m", Style: vaxis.Style{Attribute: vaxis.AttrReverse}},
"change your settings", {Text: "anage your feels"},
"see credits", },
"read documentation", {
{Text: "check out your "},
{Text: "n", Style: vaxis.Style{Attribute: vaxis.AttrReverse}},
{Text: "eighbors"},
},
{
{Text: "b", Style: vaxis.Style{Attribute: vaxis.AttrReverse}},
{Text: "rowse global feels"},
},
{
{Text: "visit your "},
{Text: "s", Style: vaxis.Style{Attribute: vaxis.AttrReverse}},
{Text: "ubscriptions"},
},
{
{Text: "scribble some gra"},
{Text: "f", Style: vaxis.Style{Attribute: vaxis.AttrReverse}},
{Text: "fiti"},
},
{
{Text: "c", Style: vaxis.Style{Attribute: vaxis.AttrReverse}},
{Text: "hange your settings"},
},
{
{Text: "see cr"},
{Text: "e", Style: vaxis.Style{Attribute: vaxis.AttrReverse}},
{Text: "dits"},
},
{
{Text: "read "},
{Text: "d", Style: vaxis.Style{Attribute: vaxis.AttrReverse}},
{Text: "ocumentation"},
},
}, index), }, index),
"↑↓/kj move ↵ enter q exit", "↑↓/kj move ↵ enter q exit",
} }
@ -58,6 +90,24 @@ func (menu *MainMenu) Event(state *ui.State, event vaxis.Event) (processed bool)
menu.list.End() menu.list.End()
case "Home", "g": case "Home", "g":
menu.list.Home() menu.list.Home()
case "r":
menu.list.SetIndex(0)
case "m":
menu.list.SetIndex(1)
case "n":
menu.list.SetIndex(2)
case "b":
menu.list.SetIndex(3)
case "s":
menu.list.SetIndex(4)
case "f":
menu.list.SetIndex(5)
case "c":
menu.list.SetIndex(6)
case "e":
menu.list.SetIndex(7)
case "d":
menu.list.SetIndex(8)
case "0", "1", "2", "3", "4", "5", "6", "7", "8": case "0", "1", "2", "3", "4", "5", "6", "7", "8":
i, _ := strconv.Atoi(key.String()) i, _ := strconv.Atoi(key.String())
menu.list.SetIndex(i) menu.list.SetIndex(i)

View File

@ -21,9 +21,9 @@ type Neighbors struct {
func NewNeighbors(index int) *Neighbors { func NewNeighbors(index int) *Neighbors {
users := ttbp.SortUsersByRecent(ttbp.GetUsers()) users := ttbp.SortUsersByRecent(ttbp.GetUsers())
subscriptions := ttbp.GetSubscriptions() subscriptions := ttbp.GetSubscriptions()
list := []string{} list := [][]vaxis.Segment{}
for _, user := range users { for _, user := range users {
list = append(list, formatNeighbor(user, subscriptions)) list = append(list, []vaxis.Segment{{Text: formatNeighbor(user, subscriptions)}})
} }
return &Neighbors{ return &Neighbors{
@ -79,7 +79,7 @@ func (neighbors *Neighbors) Event(state *ui.State, event vaxis.Event) (processed
neighbors.subscriptions.Subscribe(user) neighbors.subscriptions.Subscribe(user)
} }
neighbors.subscriptions.Write() neighbors.subscriptions.Write()
neighbors.list.SetItem(neighbors.list.Index(), formatNeighbor(user, neighbors.subscriptions)) neighbors.list.SetItem(neighbors.list.Index(), []vaxis.Segment{{Text: formatNeighbor(user, neighbors.subscriptions)}})
case "Enter", "l", "Right": case "Enter", "l", "Right":
ui.ViewChange <- NewUserPage(neighbors.neighbors[neighbors.list.Index()].Name, false, neighbors.list.Index()) ui.ViewChange <- NewUserPage(neighbors.neighbors[neighbors.list.Index()].Name, false, neighbors.list.Index())
} }

View File

@ -20,9 +20,9 @@ type Publishing struct {
func NewPublishing() *Publishing { func NewPublishing() *Publishing {
user, _ := user.Current() user, _ := user.Current()
posts := ttbp.GetPostsForUser(user.Username) posts := ttbp.GetPostsForUser(user.Username)
list := []string{} list := [][]vaxis.Segment{}
for _, post := range posts { for _, post := range posts {
list = append(list, formatPublishing(post)) list = append(list, []vaxis.Segment{{Text: formatPublishing(post)}})
} }
return &Publishing{ return &Publishing{
@ -71,13 +71,13 @@ func (publishing *Publishing) Event(state *ui.State, event vaxis.Event) (process
if len(publishing.list.Items()) > 0 { if len(publishing.list.Items()) > 0 {
publishing.posts[publishing.list.Index()].Nopub = !publishing.posts[publishing.list.Index()].Nopub publishing.posts[publishing.list.Index()].Nopub = !publishing.posts[publishing.list.Index()].Nopub
ttbp.ToggleNopub(publishing.posts[publishing.list.Index()].Date) ttbp.ToggleNopub(publishing.posts[publishing.list.Index()].Date)
publishing.list.SetItem(publishing.list.Index(), formatPublishing(publishing.posts[publishing.list.Index()])) publishing.list.SetItem(publishing.list.Index(), []vaxis.Segment{{Text: formatPublishing(publishing.posts[publishing.list.Index()])}})
} }
case "m": case "m":
if len(publishing.list.Items()) > 0 { if len(publishing.list.Items()) > 0 {
publishing.posts[publishing.list.Index()].HTML = !publishing.posts[publishing.list.Index()].HTML publishing.posts[publishing.list.Index()].HTML = !publishing.posts[publishing.list.Index()].HTML
ttbp.ToggleHTML(publishing.posts[publishing.list.Index()].Date) ttbp.ToggleHTML(publishing.posts[publishing.list.Index()].Date)
publishing.list.SetItem(publishing.list.Index(), formatPublishing(publishing.posts[publishing.list.Index()])) publishing.list.SetItem(publishing.list.Index(), []vaxis.Segment{{Text: formatPublishing(publishing.posts[publishing.list.Index()])}})
} }
} }
processed = true processed = true

View File

@ -19,11 +19,11 @@ type Subscriptions struct {
func NewSubscriptions(index int) *Subscriptions { func NewSubscriptions(index int) *Subscriptions {
users := ttbp.SortUsersByRecent(ttbp.GetUsers()) users := ttbp.SortUsersByRecent(ttbp.GetUsers())
subscriptions := ttbp.GetSubscriptions() subscriptions := ttbp.GetSubscriptions()
list := []string{} list := [][]vaxis.Segment{}
neighbors := []ttbp.User{} neighbors := []ttbp.User{}
for _, user := range users { for _, user := range users {
if subscriptions.IsSubscribed(user) { if subscriptions.IsSubscribed(user) {
list = append(list, formatNeighbor(user, subscriptions)) list = append(list, []vaxis.Segment{{Text: formatNeighbor(user, subscriptions)}})
neighbors = append(neighbors, user) neighbors = append(neighbors, user)
} }
} }
@ -64,7 +64,7 @@ func (subscriptions *Subscriptions) Event(state *ui.State, event vaxis.Event) (p
} else { } else {
subscriptions.subscriptions.Subscribe(user) subscriptions.subscriptions.Subscribe(user)
} }
subscriptions.list.SetItem(subscriptions.list.Index(), formatNeighbor(user, subscriptions.subscriptions)) subscriptions.list.SetItem(subscriptions.list.Index(), []vaxis.Segment{{Text: formatNeighbor(user, subscriptions.subscriptions)}})
} }
case "Enter", "l", "Right": case "Enter", "l", "Right":
if len(subscriptions.list.Items()) > 0 { if len(subscriptions.list.Items()) > 0 {

View File

@ -25,13 +25,13 @@ type UserPage struct {
func NewUserPage(user string, self bool, index int) *UserPage { func NewUserPage(user string, self bool, index int) *UserPage {
posts := ttbp.GetPostsForUser(user) posts := ttbp.GetPostsForUser(user)
list := []string{} list := [][]vaxis.Segment{}
for _, post := range posts { for _, post := range posts {
list = append(list, fmt.Sprintf( list = append(list, []vaxis.Segment{{Text: fmt.Sprintf(
"%s (%d words)", "%s (%d words)",
post.Date.Format("2006-01-02"), post.Date.Format("2006-01-02"),
post.Words, post.Words,
)) )}})
} }
return &UserPage{ return &UserPage{

View File

@ -13,11 +13,11 @@ import (
type List struct { type List struct {
index int index int
items []string items [][]vaxis.Segment
offset int offset int
} }
func NewList(items []string, index int) List { func NewList(items [][]vaxis.Segment, index int) List {
return List{ return List{
items: items, items: items,
index: index, index: index,
@ -32,7 +32,7 @@ func (m *List) Draw(win vaxis.Window) {
m.offset = m.index m.offset = m.index
} }
defaultStyle := vaxis.Style{} defaultStyle := vaxis.Style{Attribute: vaxis.AttrNone}
selectedStyle := vaxis.Style{Attribute: vaxis.AttrReverse} selectedStyle := vaxis.Style{Attribute: vaxis.AttrReverse}
index := m.index - m.offset index := m.index - m.offset
@ -43,7 +43,15 @@ func (m *List) Draw(win vaxis.Window) {
} else { } else {
style = defaultStyle style = defaultStyle
} }
win.Println(i, vaxis.Segment{Text: fmt.Sprintf(" %-"+strconv.Itoa(win.Width-2)+"s", subject), Style: style}) var styledSubject []vaxis.Segment
length := 0
for _, chunk := range subject {
length += len(chunk.Text)
styledSubject = append(styledSubject, vaxis.Segment{Text: chunk.Text, Style: vaxis.Style{Attribute: style.Attribute | chunk.Style.Attribute}})
}
styledSubject = append([]vaxis.Segment{{Text: " ", Style: style}}, styledSubject...)
styledSubject = append(styledSubject, vaxis.Segment{Text: fmt.Sprintf("%"+strconv.Itoa(win.Width-length-2)+"s", ""), Style: style})
win.Println(i, styledSubject...)
} }
} }
@ -83,15 +91,23 @@ func (m *List) PageUp(win vaxis.Window) {
} }
func (m *List) Items() []string { func (m *List) Items() []string {
return m.items var items []string
for _, item := range m.items {
var text string
for _, chunk := range item {
text += chunk.Text
}
items = append(items, text)
}
return items
} }
func (m *List) SetItems(items []string) { func (m *List) SetItems(items [][]vaxis.Segment) {
m.items = items m.items = items
m.index = min(len(items)-1, m.index) m.index = min(len(items)-1, m.index)
} }
func (m *List) SetItem(index int, item string) { func (m *List) SetItem(index int, item []vaxis.Segment) {
m.items[index] = item m.items[index] = item
} }