比较提交

...

14 次代码提交
0.1.0 ... trunk

作者 SHA1 备注 提交日期
46439d2488
0.3.0 2025-04-29 05:57:46 +03:00
c29a7c1594
add gopher publishing 2025-04-29 05:57:26 +03:00
350727cd58
add hotkey menu navigation
Closes: #13
2025-04-29 05:56:42 +03:00
9d4053da0b
use birthtime instead of mtime 2025-04-01 01:27:07 +03:00
6dd7d573b8
0.2.0 2025-01-20 14:43:22 +02:00
462d18850e
remove stray unpublishes from html dir 2025-01-20 14:40:16 +02:00
5a7db89b47
ui: clean up doubling 2025-01-20 14:36:14 +02:00
e8125b419f
add symlink to html 2025-01-12 14:48:39 +02:00
50cb60cdc4
add configurable pager 2025-01-12 14:26:30 +02:00
7e4456450c
add html rendering 2025-01-10 21:10:45 +02:00
5868ae2f13
0.1.1 2025-01-09 05:05:55 +02:00
251c250a8d
default to nano and warn if $EDITOR not set 2025-01-09 05:04:00 +02:00
491adc5290
use 755 for config dir for ttbprc readability 2025-01-09 04:58:02 +02:00
0e00aeaf45
save position of list 2025-01-09 04:55:06 +02:00
共有 24 个文件被更改,包括 559 次插入171 次删除

查看文件

@ -1,2 +1,2 @@
name neofeels
version 0.1.0
version 0.3.0

查看文件

@ -1,5 +1,35 @@
# Changelog
## [0.3.0] - 2025-04-29
### Added
- add gopher support
- shortcuts to list items in menu and management
### Fixed
- use birthtime instead of mtime
## [0.2.0] - 2025-01-20
### Added
- add html rendering
- add configurable pager
### Fixed
- fix hacky workaround for doubled inputs
- symlink html directory
- remove stray unpublishes from html dir
## [0.1.1] - 2025-01-09
- use 755 for config dir
- save position of previous list when going back
- default to nano and warn if no $EDITOR set
## [0.1.0] - 2025-01-08
initial release: fully supports everything in ttbp, in a backwards-compatible

查看文件

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

查看文件

@ -25,19 +25,19 @@ type Backups struct {
func NewBackups() *Backups {
os.MkdirAll(ttbp.PathUserBackups, 0700)
backups, _ := os.ReadDir(ttbp.PathUserBackups)
list := []string{}
list := [][]vaxis.Segment{}
for _, backup := range backups {
timestamp, err := time.Parse("feels-backup-20060102-150405.tar.gz", backup.Name())
if err != nil {
continue
}
// 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{
title,
ui.NewList(list),
ui.NewList(list, 0),
"↑↓/kj move ↵ enter q return",
"choose a backup to restore",
backups,
@ -61,7 +61,7 @@ func (backups *Backups) Event(state *ui.State, event vaxis.Event) (processed boo
i, _ := strconv.Atoi(key.String())
backups.list.SetIndex(i)
case "q", "h", "Left":
ui.ViewChange <- NewManagement()
ui.ViewChange <- NewManagement(3)
case "Enter", "l", "Right":
if len(backups.list.Items()) > 0 {
backups.LoadBackup(state)

查看文件

@ -23,19 +23,19 @@ func NewBrowse() *Browse {
posts = append(posts, ttbp.GetPostsForUser(user.Name)...)
}
posts = ttbp.SortPostsByRecent(posts)
list := []string{}
list := [][]vaxis.Segment{}
for _, post := range posts {
list = append(list, fmt.Sprintf(
list = append(list, []vaxis.Segment{{Text: fmt.Sprintf(
"~%-15s %s (%d words)",
post.Author,
post.LastEdited.Format("2006-01-02 15:04"),
post.Words,
))
)}})
}
return &Browse{
title,
ui.NewList(list),
ui.NewList(list, 0),
"↑↓/kj move ↵ enter q return",
posts,
}
@ -58,7 +58,7 @@ func (browse *Browse) Event(state *ui.State, event vaxis.Event) (processed bool)
i, _ := strconv.Atoi(key.String())
browse.list.SetIndex(i)
case "q", "h", "Left":
ui.ViewChange <- NewMainMenu()
ui.ViewChange <- NewMainMenu(3)
case "Enter", "l", "Right":
showPost(state, browse.posts[browse.list.Index()])
}

查看文件

@ -23,18 +23,18 @@ type Bury struct {
func NewBury() *Bury {
user, _ := user.Current()
posts := ttbp.GetPostsForUser(user.Username)
list := []string{}
list := [][]vaxis.Segment{}
for _, post := range posts {
list = append(list, fmt.Sprintf(
list = append(list, []vaxis.Segment{{Text: fmt.Sprintf(
"%s (%d words)",
post.Date.Format("2006-01-02"),
post.Words,
))
)}})
}
return &Bury{
title,
ui.NewList(list),
ui.NewList(list, 0),
"↑↓/kj move ↵ enter q return",
posts,
}
@ -57,7 +57,7 @@ func (bury *Bury) Event(state *ui.State, event vaxis.Event) (processed bool) {
i, _ := strconv.Atoi(key.String())
bury.list.SetIndex(i)
case "q", "h", "Left":
ui.ViewChange <- NewManagement()
ui.ViewChange <- NewManagement(4)
case "Enter", "l", "Right":
if len(bury.list.Items()) > 0 {
bury.Confirmation(state, func() {

查看文件

@ -18,10 +18,12 @@ type Config struct {
descriptions []string
}
var configList = []string{
"publish to html",
"publish to gopher",
"default to nopub",
var configList = [][]vaxis.Segment{
[]vaxis.Segment{{Text: "pager"}},
[]vaxis.Segment{{Text: "publish to html"}},
[]vaxis.Segment{{Text: "publish to gopher"}},
[]vaxis.Segment{{Text: "default to nopub"}},
[]vaxis.Segment{{Text: "default to html"}},
}
func NewConfig() *Config {
@ -31,10 +33,14 @@ func NewConfig() *Config {
}
return &Config{
title,
ui.NewList(configList),
ui.NewList(configList, 0),
"↑↓/kj move ↵ enter q return",
cfg,
[]string{
`which pager do you want to use to display feels?
here you can add additional pre-processing to your pager. leaving this option
unset defaults to $PAGER, and if that isn't set, neofeels falls back to less.`,
`do you want to publish your feels online?
if yes, your feels will be published to a directory of your choice in your
@ -52,6 +58,10 @@ visible from your gopherhole, and will be purged from your gophermap on your
next entry update.`,
`should your posts automatically show up on your world-visible pages, such as
html and gopher? you can change this behaviour on a per-post basis after the
fact. changes to this setting will not be made retroactively.`,
`should your posts automatically be parsed as html in the neofeels reader?
this is not related to the html publishing option, and only applies to the
terminal reader. you can change this behaviour on a per-post basis after the
fact. changes to this setting will not be made retroactively.`,
`in which directory under public_html should your blog reside? for example,
"blog" will make it visible under https://tilde.town/~you/blog.`,
@ -75,23 +85,21 @@ func (config *Config) Event(state *ui.State, event vaxis.Event) (processed bool)
case "0", "1":
i, _ := strconv.Atoi(key.String())
config.list.SetIndex(i)
case "2", "3":
if config.config.Publishing {
i, _ := strconv.Atoi(key.String())
config.list.SetIndex(i)
}
case "q", "h", "Left":
ui.ViewChange <- NewMainMenu()
ui.ViewChange <- NewMainMenu(6)
case "Enter", "l", "Right", "Space":
switch config.list.Index() {
case 0:
config.config.Publishing = !config.config.Publishing
config.config.Pager = config.changePager(state)
case 1:
config.config.Gopher = !config.config.Gopher
config.config.Publishing = !config.config.Publishing
case 2:
config.config.Nopub = !config.config.Nopub
config.config.Gopher = !config.config.Gopher
case 3:
config.config.Nopub = !config.config.Nopub
case 4:
config.config.HTML = !config.config.HTML
case 5:
config.config.PublishDir = config.changePublishDir(state)
}
config.config.Write()
@ -104,7 +112,7 @@ func (config *Config) Event(state *ui.State, event vaxis.Event) (processed bool)
func (config *Config) Draw(state *ui.State) {
if config.config.Publishing {
config.list.SetItems(append(configList, "html publish directory"))
config.list.SetItems(append(configList, []vaxis.Segment{{Text: "html publish directory"}}))
} else {
config.list.SetItems(configList)
}
@ -116,17 +124,19 @@ func (config *Config) Draw(state *ui.State) {
Column: win.Width/2 - 21,
Row: win.Height/2 - 2,
Width: 28,
Height: 4,
Height: 6,
})
win.New(win.Width/2-40, win.Height/2+3, 80, 10).Print(vaxis.Segment{Text: config.descriptions[config.list.Index()]})
win.New(win.Width/2-15, win.Height/2+13, 30, 1).Print(vaxis.Segment{Text: config.help})
win.New(win.Width/2-40, win.Height/2+5, 80, 10).Print(vaxis.Segment{Text: config.descriptions[config.list.Index()]})
win.New(win.Width/2-15, win.Height/2+15, 30, 1).Print(vaxis.Segment{Text: config.help})
// drawing the current selected options
win.New(win.Width/2+7, win.Height/2-2, 14, 1).Print(vaxis.Segment{Text: fmt.Sprintf(" %-12v", config.config.Publishing)})
win.New(win.Width/2+7, win.Height/2-1, 14, 1).Print(vaxis.Segment{Text: fmt.Sprintf(" %-12v", config.config.Gopher)})
win.New(win.Width/2+7, win.Height/2, 14, 1).Print(vaxis.Segment{Text: fmt.Sprintf(" %-12v", config.config.Nopub)})
win.New(win.Width/2+7, win.Height/2-2, 40, 1).Print(vaxis.Segment{Text: fmt.Sprintf(" %-12v", config.config.Pager)})
win.New(win.Width/2+7, win.Height/2-1, 14, 1).Print(vaxis.Segment{Text: fmt.Sprintf(" %-12v", config.config.Publishing)})
win.New(win.Width/2+7, win.Height/2, 14, 1).Print(vaxis.Segment{Text: fmt.Sprintf(" %-12v", config.config.Gopher)})
win.New(win.Width/2+7, win.Height/2+1, 14, 1).Print(vaxis.Segment{Text: fmt.Sprintf(" %-12v", config.config.Nopub)})
win.New(win.Width/2+7, win.Height/2+2, 14, 1).Print(vaxis.Segment{Text: fmt.Sprintf(" %-12v", config.config.HTML)})
if config.config.Publishing {
win.New(win.Width/2+7, win.Height/2+1, 14, 1).Print(vaxis.Segment{Text: fmt.Sprintf(" %-12v", config.config.PublishDir)})
win.New(win.Width/2+7, win.Height/2+3, 14, 1).Print(vaxis.Segment{Text: fmt.Sprintf(" %-12v", config.config.PublishDir)})
}
}
@ -139,7 +149,7 @@ func (config *Config) changePublishDir(state *ui.State) string {
Vx: win.Vx,
Parent: &win,
Column: win.Width/2 + 9,
Row: win.Height/2 + 1,
Row: win.Height/2 + 2,
Width: 12,
Height: 1,
})
@ -158,7 +168,7 @@ func (config *Config) changePublishDir(state *ui.State) string {
Vx: win.Vx,
Parent: nil,
Column: win.Width/2 + 9,
Row: win.Height/2 + 1,
Row: win.Height/2 + 2,
Width: 12,
Height: 1,
})
@ -167,3 +177,41 @@ func (config *Config) changePublishDir(state *ui.State) string {
return ""
}
func (config *Config) changePager(state *ui.State) string {
ti := textinput.New()
ti.SetContent(config.config.Pager)
config.Draw(state)
win := state.Window()
ti.Draw(vaxis.Window{
Vx: win.Vx,
Parent: &win,
Column: win.Width/2 + 9,
Row: win.Height/2 - 2,
Width: 40,
Height: 1,
})
for ev := range win.Vx.Events() {
switch ev := ev.(type) {
case vaxis.Key:
switch ev.String() {
case "Ctrl+c", "Esc", "Enter":
state.HideCursor()
return ti.String()
}
}
ti.Update(ev)
config.Draw(state)
ti.Draw(vaxis.Window{
Vx: win.Vx,
Parent: nil,
Column: win.Width/2 + 9,
Row: win.Height/2 - 2,
Width: 40,
Height: 1,
})
state.Render()
}
return ""
}

查看文件

@ -31,7 +31,7 @@ func (credits *Credits) Event(state *ui.State, event vaxis.Event) (processed boo
case "Ctrl+c", "Ctrl+d":
close(ui.Quit)
case "Enter", "q", "h", "l", "Left", "Right":
ui.ViewChange <- NewMainMenu()
ui.ViewChange <- NewMainMenu(7)
}
processed = true
}

查看文件

@ -22,18 +22,18 @@ type Delete struct {
func NewDelete() *Delete {
user, _ := user.Current()
posts := ttbp.GetPostsForUser(user.Username)
list := []string{}
list := [][]vaxis.Segment{}
for _, post := range posts {
list = append(list, fmt.Sprintf(
list = append(list, []vaxis.Segment{{Text: fmt.Sprintf(
"%s (%d words)",
post.Date.Format("2006-01-02"),
post.Words,
))
)}})
}
return &Delete{
title,
ui.NewList(list),
ui.NewList(list, 0),
"↑↓/kj move ↵ enter q return",
posts,
}
@ -56,7 +56,7 @@ func (delete *Delete) Event(state *ui.State, event vaxis.Event) (processed bool)
i, _ := strconv.Atoi(key.String())
delete.list.SetIndex(i)
case "q", "h", "Left":
ui.ViewChange <- NewManagement()
ui.ViewChange <- NewManagement(5)
case "Enter", "l", "Right":
if len(delete.list.Items()) > 0 {
delete.Confirmation(state, func() {

查看文件

@ -1,6 +1,8 @@
package app
import (
"bufio"
"fmt"
"os"
"os/exec"
@ -42,25 +44,36 @@ func (graffiti *Graffiti) Event(state *ui.State, event vaxis.Event) (processed b
case "Ctrl+c", "Ctrl+d":
close(ui.Quit)
case "q", "h", "Left":
ui.ViewChange <- NewMainMenu()
ui.ViewChange <- NewMainMenu(5)
case "Enter", "l", "Right":
if ttbp.GraffitiFree() {
os.Create(ttbp.PathWallLock)
editGraffiti(state)
os.Remove(ttbp.PathWallLock)
}
ui.ViewChange <- NewMainMenu()
ui.ViewChange <- NewMainMenu(5)
}
processed = true
}
win := state.Window()
win.New(win.Width/2-10, win.Height/2-8, 20, 5).Print(vaxis.Segment{Text: graffiti.title})
win.New(win.Width/2-40, win.Height/2-2, 80, 9).Print(vaxis.Segment{Text: graffiti.content})
win.New(win.Width/2-40, win.Height/2-2, 81, 9).Print(vaxis.Segment{Text: graffiti.content})
win.New(win.Width/2-9, win.Height/2+8, 18, 1).Print(vaxis.Segment{Text: graffiti.help})
return
}
func editGraffiti(state *ui.State) {
// if $EDITOR isn't set, warn about it, and use nano
editor := os.ExpandEnv(os.Getenv("EDITOR"))
if editor == "" {
editor = "nano"
state.Suspend()
fmt.Print("$EDITOR not found, using nano. press ↵ to continue")
input := bufio.NewScanner(os.Stdin)
input.Scan()
state.Resume()
}
state.HideCursor()
vt := term.New()
vt.TERM = os.Getenv("TERM")
@ -84,9 +97,6 @@ func editGraffiti(state *ui.State) {
continue
}
// for some reason vaxis doubles all events for Press/Release so this just ignores releases
if key, ok := ev.(vaxis.Key); ok && key.EventType == vaxis.EventPress {
vt.Update(ev)
}
vt.Update(ev)
}
}

查看文件

@ -20,19 +20,44 @@ type Management struct {
help string
}
func NewManagement() *Management {
func NewManagement(index int) *Management {
return &Management{
title,
ui.NewList([]string{
"read over feels",
"modify feels publishing",
"backup your feels",
"import a feels backup",
"bury some feels",
"delete feels by day",
"purge all feels",
"wipe feels account",
}),
ui.NewList([][]vaxis.Segment{
{
{Text: "r", Style: vaxis.Style{Attribute: vaxis.AttrReverse}},
{Text: "ead over feels"},
},
{
{Text: "m", Style: vaxis.Style{Attribute: vaxis.AttrReverse}},
{Text: "odify feels publishing"},
},
{
{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),
"↑↓/kj move ↵ enter q return",
}
}
@ -50,16 +75,32 @@ func (management *Management) Event(state *ui.State, event vaxis.Event) (process
management.list.End()
case "Home", "g":
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":
i, _ := strconv.Atoi(key.String())
management.list.SetIndex(i)
case "q", "h", "Left":
ui.ViewChange <- NewMainMenu()
ui.ViewChange <- NewMainMenu(1)
case "Enter", "l", "Right":
switch management.list.Index() {
case 0:
user, _ := user.Current()
ui.ViewChange <- NewUserPage(user.Username, true)
ui.ViewChange <- NewUserPage(user.Username, true, 0)
case 1:
ui.ViewChange <- NewPublishing()
case 2:

查看文件

@ -1,6 +1,8 @@
package app
import (
"bufio"
"fmt"
"os"
"os/exec"
"path"
@ -23,22 +25,54 @@ const title = ` ___ __
/ _/__ ___ / /__
/ _/ -_) -_) (_-<
/_/ \__/\__/_/___/
neofeels 0.1.0`
neofeels 0.3.0`
func NewMainMenu() *MainMenu {
func NewMainMenu(index int) *MainMenu {
return &MainMenu{
title,
ui.NewList([]string{
"record some feels",
"manage your feels",
"check out your neighbors",
"browse global feels",
"visit your subscriptions",
"scribble some graffiti",
"change your settings",
"see credits",
"read documentation",
}),
ui.NewList([][]vaxis.Segment{
{
{Text: "r", Style: vaxis.Style{Attribute: vaxis.AttrReverse}},
{Text: "ecord some feels"},
},
{
{Text: "m", Style: vaxis.Style{Attribute: vaxis.AttrReverse}},
{Text: "anage your feels"},
},
{
{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),
"↑↓/kj move ↵ enter q exit",
}
}
@ -56,6 +90,24 @@ func (menu *MainMenu) Event(state *ui.State, event vaxis.Event) (processed bool)
menu.list.End()
case "Home", "g":
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":
i, _ := strconv.Atoi(key.String())
menu.list.SetIndex(i)
@ -64,13 +116,13 @@ func (menu *MainMenu) Event(state *ui.State, event vaxis.Event) (processed bool)
case 0:
newFeels(state)
case 1:
ui.ViewChange <- NewManagement()
ui.ViewChange <- NewManagement(0)
case 2:
ui.ViewChange <- NewNeighbors()
ui.ViewChange <- NewNeighbors(0)
case 3:
ui.ViewChange <- NewBrowse()
case 4:
ui.ViewChange <- NewSubscriptions()
ui.ViewChange <- NewSubscriptions(0)
case 5:
ui.ViewChange <- NewGraffiti()
case 6:
@ -121,21 +173,29 @@ func showManpage(state *ui.State) {
continue
}
// for some reason vaxis doubles all events for Press/Release so this just ignores releases
if key, ok := ev.(vaxis.Key); ok && key.EventType == vaxis.EventPress {
vt.Update(ev)
}
vt.Update(ev)
}
}
func newFeels(state *ui.State) {
// if $EDITOR isn't set, warn about it, and use nano
editor := os.ExpandEnv(os.Getenv("EDITOR"))
if editor == "" {
editor = "nano"
state.Suspend()
fmt.Print("$EDITOR not found, using nano. press ↵ to continue")
input := bufio.NewScanner(os.Stdin)
input.Scan()
state.Resume()
}
state.HideCursor()
vt := term.New()
vt.TERM = os.Getenv("TERM")
vt.Attach(state.PostEvent())
vt.Focus()
now := time.Now()
err := vt.Start(exec.Command(os.ExpandEnv(os.Getenv("EDITOR")), path.Join(ttbp.PathUserEntries, now.Format("20060102")+".txt")))
err := vt.Start(exec.Command(editor, path.Join(ttbp.PathUserEntries, now.Format("20060102")+".txt")))
if err != nil {
panic(err)
}
@ -156,9 +216,6 @@ func newFeels(state *ui.State) {
continue
}
// for some reason vaxis doubles all events for Press/Release so this just ignores releases
if key, ok := ev.(vaxis.Key); ok && key.EventType == vaxis.EventPress {
vt.Update(ev)
}
vt.Update(ev)
}
}

查看文件

@ -18,17 +18,17 @@ type Neighbors struct {
subscriptions *ttbp.Subscriptions
}
func NewNeighbors() *Neighbors {
func NewNeighbors(index int) *Neighbors {
users := ttbp.SortUsersByRecent(ttbp.GetUsers())
subscriptions := ttbp.GetSubscriptions()
list := []string{}
list := [][]vaxis.Segment{}
for _, user := range users {
list = append(list, formatNeighbor(user, subscriptions))
list = append(list, []vaxis.Segment{{Text: formatNeighbor(user, subscriptions)}})
}
return &Neighbors{
title,
ui.NewList(list),
ui.NewList(list, index),
"↑↓/kj move ↵ enter s subscribe q return",
users,
subscriptions,
@ -70,7 +70,7 @@ func (neighbors *Neighbors) Event(state *ui.State, event vaxis.Event) (processed
i, _ := strconv.Atoi(key.String())
neighbors.list.SetIndex(i)
case "q", "h", "Left":
ui.ViewChange <- NewMainMenu()
ui.ViewChange <- NewMainMenu(2)
case "s":
user := neighbors.neighbors[neighbors.list.Index()]
if neighbors.subscriptions.IsSubscribed(user) {
@ -79,9 +79,9 @@ func (neighbors *Neighbors) Event(state *ui.State, event vaxis.Event) (processed
neighbors.subscriptions.Subscribe(user)
}
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":
ui.ViewChange <- NewUserPage(neighbors.neighbors[neighbors.list.Index()].Name, false)
ui.ViewChange <- NewUserPage(neighbors.neighbors[neighbors.list.Index()].Name, false, neighbors.list.Index())
}
processed = true
}

查看文件

@ -40,7 +40,7 @@ func (posted *Posted) Event(state *ui.State, event vaxis.Event) (processed bool)
case "Ctrl+c", "Ctrl+d":
close(ui.Quit)
case "Enter", "q", "h", "l", "Left", "Right":
ui.ViewChange <- NewMainMenu()
ui.ViewChange <- NewMainMenu(0)
}
processed = true
}

查看文件

@ -20,28 +20,31 @@ type Publishing struct {
func NewPublishing() *Publishing {
user, _ := user.Current()
posts := ttbp.GetPostsForUser(user.Username)
list := []string{}
list := [][]vaxis.Segment{}
for _, post := range posts {
list = append(list, formatPublishing(post))
list = append(list, []vaxis.Segment{{Text: formatPublishing(post)}})
}
return &Publishing{
title,
ui.NewList(list),
"↑↓/kj move ↵ enter q return",
ui.NewList(list, 0),
"↑↓/kj move n nopub m html q return",
posts,
}
}
func formatPublishing(post ttbp.Post) string {
nopub := ""
status := ""
if post.Nopub {
nopub = "(nopub)"
status += "(nopub) "
}
if post.HTML {
status += "(html)"
}
return fmt.Sprintf(
"%s %s",
post.Date.Format("2006-01-02"),
nopub,
status,
)
}
@ -63,12 +66,18 @@ func (publishing *Publishing) Event(state *ui.State, event vaxis.Event) (process
publishing.list.SetIndex(i)
case "q", "h", "Left":
ttbp.Publish()
ui.ViewChange <- NewManagement()
case "Enter", "l", "Right":
ui.ViewChange <- NewManagement(1)
case "n":
if len(publishing.list.Items()) > 0 {
publishing.posts[publishing.list.Index()].Nopub = !publishing.posts[publishing.list.Index()].Nopub
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":
if len(publishing.list.Items()) > 0 {
publishing.posts[publishing.list.Index()].HTML = !publishing.posts[publishing.list.Index()].HTML
ttbp.ToggleHTML(publishing.posts[publishing.list.Index()].Date)
publishing.list.SetItem(publishing.list.Index(), []vaxis.Segment{{Text: formatPublishing(publishing.posts[publishing.list.Index()])}})
}
}
processed = true
@ -83,10 +92,10 @@ func (publishing *Publishing) Draw(state *ui.State) {
publishing.list.Draw(vaxis.Window{
Vx: win.Vx,
Parent: nil,
Column: win.Width/2 - 14,
Column: win.Width/2 - 15,
Row: win.Height/2 - 2,
Width: 28,
Width: 30,
Height: 10,
})
win.New(win.Width/2-15, win.Height/2+9, 30, 1).Print(vaxis.Segment{Text: publishing.help})
win.New(win.Width/2-19, win.Height/2+9, 38, 1).Print(vaxis.Segment{Text: publishing.help})
}

查看文件

@ -16,21 +16,21 @@ type Subscriptions struct {
subscriptions *ttbp.Subscriptions
}
func NewSubscriptions() *Subscriptions {
func NewSubscriptions(index int) *Subscriptions {
users := ttbp.SortUsersByRecent(ttbp.GetUsers())
subscriptions := ttbp.GetSubscriptions()
list := []string{}
list := [][]vaxis.Segment{}
neighbors := []ttbp.User{}
for _, user := range users {
if subscriptions.IsSubscribed(user) {
list = append(list, formatNeighbor(user, subscriptions))
list = append(list, []vaxis.Segment{{Text: formatNeighbor(user, subscriptions)}})
neighbors = append(neighbors, user)
}
}
return &Subscriptions{
title,
ui.NewList(list),
ui.NewList(list, index),
"↑↓/kj move ↵ enter s subscribe q return",
neighbors,
subscriptions,
@ -55,7 +55,7 @@ func (subscriptions *Subscriptions) Event(state *ui.State, event vaxis.Event) (p
subscriptions.list.SetIndex(i)
case "q", "h", "Left":
subscriptions.subscriptions.Write()
ui.ViewChange <- NewMainMenu()
ui.ViewChange <- NewMainMenu(4)
case "s":
if len(subscriptions.list.Items()) > 0 {
user := subscriptions.neighbors[subscriptions.list.Index()]
@ -64,12 +64,12 @@ func (subscriptions *Subscriptions) Event(state *ui.State, event vaxis.Event) (p
} else {
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":
if len(subscriptions.list.Items()) > 0 {
subscriptions.subscriptions.Write()
ui.ViewChange <- NewUserPage(subscriptions.neighbors[subscriptions.list.Index()].Name, false)
ui.ViewChange <- NewUserPage(subscriptions.neighbors[subscriptions.list.Index()].Name, false, subscriptions.list.Index())
}
}
processed = true

查看文件

@ -9,6 +9,7 @@ import (
"git.sr.ht/~rockorager/vaxis"
"git.sr.ht/~rockorager/vaxis/widgets/term"
"git.tilde.town/nbsp/neofeels/config"
"git.tilde.town/nbsp/neofeels/ttbp"
"git.tilde.town/nbsp/neofeels/ui"
)
@ -19,25 +20,27 @@ type UserPage struct {
help string
posts []ttbp.Post
self bool
index int // which index on the previous page to return to?
}
func NewUserPage(user string, self bool) *UserPage {
func NewUserPage(user string, self bool, index int) *UserPage {
posts := ttbp.GetPostsForUser(user)
list := []string{}
list := [][]vaxis.Segment{}
for _, post := range posts {
list = append(list, fmt.Sprintf(
list = append(list, []vaxis.Segment{{Text: fmt.Sprintf(
"%s (%d words)",
post.Date.Format("2006-01-02"),
post.Words,
))
)}})
}
return &UserPage{
title,
ui.NewList(list),
ui.NewList(list, 0),
"↑↓/kj move ↵ enter q return",
posts,
self,
index,
}
}
@ -59,9 +62,9 @@ func (user *UserPage) Event(state *ui.State, event vaxis.Event) (processed bool)
user.list.SetIndex(i)
case "q", "h", "Left":
if user.self {
ui.ViewChange <- NewManagement()
ui.ViewChange <- NewManagement(0)
} else {
ui.ViewChange <- NewNeighbors()
ui.ViewChange <- NewNeighbors(user.index)
}
case "Enter", "l", "Right":
showPost(state, user.posts[user.list.Index()])
@ -92,11 +95,27 @@ func showPost(state *ui.State, post ttbp.Post) {
vt.TERM = os.Getenv("TERM")
vt.Attach(state.PostEvent())
vt.Focus()
pager := os.ExpandEnv(os.Getenv("PAGER"))
cfg, err := config.Read()
if err != nil {
panic(err)
}
pager := os.ExpandEnv(cfg.Pager)
if cfg.Pager == "" {
pager = os.ExpandEnv(os.Getenv("PAGER"))
}
if pager == "" {
pager = "less"
}
err := vt.Start(exec.Command(pager, path.Join("/home", post.Author, ".ttbp/entries", post.Date.Format("20060102")+".txt")))
prepro := "cat"
if post.HTML {
prepro = "lynx -dump -force_html"
}
cmd := fmt.Sprintf("%s %s | %s", prepro, path.Join("/home", post.Author, ".ttbp/entries", post.Date.Format("20060102")+".txt"), pager)
err = vt.Start(exec.Command("sh", "-c", cmd))
if err != nil {
panic(err)
}
@ -114,9 +133,6 @@ func showPost(state *ui.State, post ttbp.Post) {
continue
}
// for some reason vaxis doubles all events for Press/Release so this just ignores releases
if key, ok := ev.(vaxis.Key); ok && key.EventType == vaxis.EventPress {
vt.Update(ev)
}
vt.Update(ev)
}
}

查看文件

@ -13,6 +13,8 @@ type Config struct {
PublishDir string `json:"publish dir"`
Publishing bool `json:"publishing"`
Rainbows bool `json:"rainbows"` // we don't care about this
HTML bool `json:"html"`
Pager string `json:"pager"`
}
var Default = &Config{
@ -22,6 +24,8 @@ var Default = &Config{
PublishDir: "blog",
Publishing: false,
Rainbows: false,
HTML: false,
Pager: "less",
}
func Read() (config *Config, err error) {

1
go.mod
查看文件

@ -4,6 +4,7 @@ go 1.23.4
require (
git.sr.ht/~rockorager/vaxis v0.11.0
github.com/djherbis/times v1.6.0
github.com/dustin/go-humanize v1.0.1
github.com/yuin/goldmark v1.4.13
)

3
go.sum
查看文件

@ -6,6 +6,8 @@ github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
@ -43,6 +45,7 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=

查看文件

@ -39,7 +39,7 @@ press ↵ to set up an account, or Ctrl+c to quit. you can always come back late
user, _ := user.Current()
header := strings.ReplaceAll(header, "%USER%", user.Username)
header = strings.ReplaceAll(header, "%DATETIME%", time.Now().Format(time.DateTime))
os.MkdirAll(ttbp.PathUserConfig, 0700)
os.MkdirAll(ttbp.PathUserConfig, 0755)
os.MkdirAll(ttbp.PathUserEntries, 0700)
os.WriteFile(path.Join(ttbp.PathUserConfig, "header.txt"), []byte(header), 0644)
os.WriteFile(path.Join(ttbp.PathUserConfig, "footer.txt"), footer, 0644)
@ -53,7 +53,7 @@ func main() {
initializePrompt()
}
state, err := ui.New(app.NewMainMenu())
state, err := ui.New(app.NewMainMenu(0))
if err != nil {
panic(err)
}

查看文件

@ -13,28 +13,32 @@ import (
"time"
"git.tilde.town/nbsp/neofeels/config"
"github.com/djherbis/times"
"github.com/yuin/goldmark"
)
var (
PathVar = "/var/global/ttbp"
PathVarWWW = path.Join(PathVar, "www")
PathLive = "https://tilde.town/~"
PathUserFile = path.Join(PathVar, "users.txt")
PathGraff = path.Join(PathVar, "graffiti")
PathWall = path.Join(PathGraff, "wall.txt")
PathWallLock = path.Join(PathGraff, ".lock")
PathUser = os.Getenv("HOME")
PathUserFeels = path.Join(PathUser, ".ttbp")
PathUserHTML = path.Join(PathUser, "public_html")
PathUserConfig = path.Join(PathUserFeels, "config")
PathUserEntries = path.Join(PathUserFeels, "entries")
PathUserBuried = path.Join(PathUserFeels, "buried")
PathUserBackups = path.Join(PathUserFeels, "backups")
PathUserWWW = path.Join(PathUserFeels, "www")
PathUserRc = path.Join(PathUserConfig, "ttbprc")
PathUserNopub = path.Join(PathUserConfig, "nopub")
PathUserSubs = path.Join(PathUserConfig, "subs")
PathVar = "/var/global/ttbp"
PathVarWWW = path.Join(PathVar, "www")
PathLive = "https://tilde.town/~"
PathUserFile = path.Join(PathVar, "users.txt")
PathGraff = path.Join(PathVar, "graffiti")
PathWall = path.Join(PathGraff, "wall.txt")
PathWallLock = path.Join(PathGraff, ".lock")
PathUser = os.Getenv("HOME")
PathUserFeels = path.Join(PathUser, ".ttbp")
PathUserHTML = path.Join(PathUser, "public_html")
PathUserGopherhole = path.Join(PathUser, "public_gopher", "feels")
PathUserConfig = path.Join(PathUserFeels, "config")
PathUserEntries = path.Join(PathUserFeels, "entries")
PathUserBuried = path.Join(PathUserFeels, "buried")
PathUserBackups = path.Join(PathUserFeels, "backups")
PathUserWWW = path.Join(PathUserFeels, "www")
PathUserGopher = path.Join(PathUserFeels, "gopher")
PathUserRc = path.Join(PathUserConfig, "ttbprc")
PathUserNopub = path.Join(PathUserConfig, "nopub")
PathUserHTMLRender = path.Join(PathUserConfig, "html")
PathUserSubs = path.Join(PathUserConfig, "subs")
)
type User struct {
@ -64,17 +68,24 @@ func GetUsers() (users []User) {
}
// get last published file
entries, err := os.ReadDir(path.Join("/home", user.Name(), ".ttbp/entries"))
entriesDir := path.Join("/home", user.Name(), ".ttbp/entries")
entries, err := os.ReadDir(entriesDir)
if err != nil {
continue
}
var lastPublished time.Time = *new(time.Time)
if len(entries) > 0 {
info, err := entries[len(entries)-1].Info()
file, err := os.Open(path.Join(entriesDir, entries[len(entries)-1].Name()))
if err != nil {
continue
}
lastPublished = info.ModTime()
defer file.Close()
timespec, err := times.StatFile(file)
if err != nil {
continue
}
lastPublished = timespec.BirthTime()
}
users = append(users, User{
@ -103,6 +114,7 @@ type Post struct {
Words int
Author string
Nopub bool
HTML bool
}
func GetPostsForUser(user string) (posts []Post) {
@ -110,6 +122,7 @@ func GetPostsForUser(user string) (posts []Post) {
if err != nil {
return
}
nopubFile, err := os.OpenFile(PathUserNopub, os.O_RDONLY|os.O_CREATE, 0644)
if err != nil {
return
@ -120,6 +133,18 @@ func GetPostsForUser(user string) (posts []Post) {
for nopubScanner.Scan() {
nopubs = append(nopubs, nopubScanner.Text())
}
htmlFile, err := os.OpenFile(PathUserHTMLRender, os.O_RDONLY|os.O_CREATE, 0644)
if err != nil {
return
}
defer htmlFile.Close()
var htmls []string
htmlScanner := bufio.NewScanner(htmlFile)
for htmlScanner.Scan() {
htmls = append(htmls, htmlScanner.Text())
}
for _, post := range postFiles {
// retrieve date of file
// assume file ends in .txt
@ -140,7 +165,7 @@ func GetPostsForUser(user string) (posts []Post) {
}
// get modtime of file
stat, err := file.Stat()
timespec, err := times.StatFile(file)
if err != nil {
continue
}
@ -154,12 +179,22 @@ func GetPostsForUser(user string) (posts []Post) {
}
}
// see if file is in html
html := false
for _, name := range htmls {
if name == post.Name() {
html = true
break
}
}
posts = append([]Post{Post{
Author: user,
Date: fileDate,
LastEdited: stat.ModTime(),
LastEdited: timespec.BirthTime(),
Words: count,
Nopub: nopub,
HTML: html,
}}, posts...)
}
}
@ -302,15 +337,82 @@ func ToggleNopub(t time.Time) {
writer.Flush()
}
func NewHTML(t time.Time) {
cfg, err := config.Read()
if err != nil || (!cfg.HTML) {
return
}
dateString := t.Format("20060102.txt")
file, err := os.OpenFile(PathUserHTMLRender, os.O_RDWR|os.O_CREATE, 0600)
if err != nil {
return
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
if scanner.Text() == dateString {
return
}
}
writer := bufio.NewWriter(file)
writer.WriteString(dateString)
writer.Flush()
}
func ToggleHTML(t time.Time) {
dateString := t.Format("20060102.txt")
htmls, err := os.ReadFile(PathUserHTMLRender)
if err != nil {
return
}
lines := strings.Split(strings.TrimSpace(string(htmls)), "\n")
newLines := []string{}
exists := false
for _, line := range lines {
if line == dateString {
exists = true
} else {
newLines = append(newLines, line)
}
}
if !exists {
newLines = append(newLines, dateString)
}
file, err := os.Create(PathUserHTMLRender)
if err != nil {
return
}
defer file.Close()
writer := bufio.NewWriter(file)
for _, line := range newLines {
if line == "" {
continue
}
_, err := writer.WriteString(line + "\n")
if err != nil {
return
}
}
writer.Flush()
}
func Publish() {
cfg, err := config.Read()
if err != nil {
return // TODO: expose this error to the user
}
if cfg.Publishing {
os.RemoveAll(PathUserWWW) // remove all post and start over
if _, err := os.Stat(PathUserWWW); os.IsNotExist(err) {
os.MkdirAll(PathUserWWW, 0700)
os.Symlink(path.Join(PathUserConfig, "style.css"), path.Join(PathUserWWW, "style.css"))
os.Symlink(PathUserWWW, path.Join(PathUserHTML, cfg.PublishDir))
}
file, err := os.Create(path.Join(PathUserWWW, "index.html"))
defer file.Close()
@ -336,7 +438,49 @@ func Publish() {
writer.WriteString(string(footer))
writer.Flush()
}
// TODO: gopher
if cfg.Gopher {
os.RemoveAll(PathUserGopher) // remove all posts and start over
if _, err := os.Stat(PathUserGopher); os.IsNotExist(err) {
os.MkdirAll(PathUserGopher, 0755)
os.Symlink(PathUserGopher, PathUserGopherhole)
}
file, err := os.Create(path.Join(PathUserGopher, "gophermap"))
defer file.Close()
if err != nil {
return
}
writer := bufio.NewWriter(file)
user, _ := user.Current()
writer.WriteString(fmt.Sprintf(`
welcome to %s's gopherfeels.
.:: .::
.: .::
.:.: .: .:: .:: .:: .::::
.:: .: .:: .: .:: .::.::
.:: .::::: .::.::::: .:: .:: .:::
.:: .: .: .:: .::
.:: .:::: .:::: .:::.:: .::
this file is automatically generated by ttbp.
0(about ttbp)%s/~endorphant/ttbp.txt%stilde.town%s70
1(back to user's home)%s/~%s
entries:
`, user.Username, "\t", "\t", "\t", "\t", user.Username))
for _, post := range GetPostsForUser(user.Username) {
if !post.Nopub && post.Words > 0 {
dateString := post.Date.Format("20060102.txt")
os.Symlink(path.Join(PathUserEntries, dateString), path.Join(PathUserGopher, dateString))
writer.WriteString(fmt.Sprintf("0%s\t%s\n", post.Date.Format("2006-01-02"), dateString))
}
}
writer.Flush()
}
}
func Unpublish() {
@ -348,6 +492,10 @@ func Unpublish() {
os.RemoveAll(PathUserWWW)
os.RemoveAll(path.Join(PathUserHTML, cfg.PublishDir))
}
if cfg.Gopher {
os.RemoveAll(PathUserGopher)
os.RemoveAll(PathUserGopherhole)
}
}
func writePage(post Post, header, footer []byte) {

查看文件

@ -13,13 +13,14 @@ import (
type List struct {
index int
items []string
items [][]vaxis.Segment
offset int
}
func NewList(items []string) List {
func NewList(items [][]vaxis.Segment, index int) List {
return List{
items: items,
index: index,
}
}
@ -31,7 +32,7 @@ func (m *List) Draw(win vaxis.Window) {
m.offset = m.index
}
defaultStyle := vaxis.Style{}
defaultStyle := vaxis.Style{Attribute: vaxis.AttrNone}
selectedStyle := vaxis.Style{Attribute: vaxis.AttrReverse}
index := m.index - m.offset
@ -42,7 +43,15 @@ func (m *List) Draw(win vaxis.Window) {
} else {
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...)
}
}
@ -82,15 +91,23 @@ func (m *List) PageUp(win vaxis.Window) {
}
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.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
}

查看文件

@ -25,8 +25,7 @@ type State struct {
func New(view View) (state State, err error) {
vx, err := vaxis.New(vaxis.Options{
DisableMouse: false,
CSIuBitMask: vaxis.CSIuDisambiguate | vaxis.CSIuReportEvents | vaxis.CSIuAlternateKeys | vaxis.CSIuAllKeys | vaxis.CSIuAssociatedText,
DisableMouse: true,
})
if err != nil {
return
@ -83,3 +82,11 @@ func (state *State) HideCursor() {
func (state *State) Window() vaxis.Window {
return state.vx.Window()
}
func (state *State) Suspend() {
state.vx.Suspend()
}
func (state *State) Resume() {
state.vx.Resume()
}