Compare commits

...

11 Commits
0.1.0 ... trunk

Author SHA1 Message Date
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 changed files with 339 additions and 113 deletions

1
.nanpa/grid-lid-come.kdl Normal file
View File

@ -0,0 +1 @@
patch type="fixed" "use birthtime instead of mtime"

View File

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

View File

@ -1,5 +1,24 @@
# Changelog
## [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

View File

@ -37,7 +37,7 @@ func NewBackups() *Backups {
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)

View File

@ -35,7 +35,7 @@ func NewBrowse() *Browse {
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()])
}

View File

@ -34,7 +34,7 @@ func NewBury() *Bury {
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() {

View File

@ -19,9 +19,11 @@ type Config struct {
}
var configList = []string{
"pager",
"publish to html",
"publish to gopher",
"default to nopub",
"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()
@ -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 ""
}

View File

@ -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
}

View File

@ -33,7 +33,7 @@ func NewDelete() *Delete {
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() {

View File

@ -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)
}
}

View File

@ -20,7 +20,7 @@ type Management struct {
help string
}
func NewManagement() *Management {
func NewManagement(index int) *Management {
return &Management{
title,
ui.NewList([]string{
@ -32,7 +32,7 @@ func NewManagement() *Management {
"delete feels by day",
"purge all feels",
"wipe feels account",
}),
}, index),
"↑↓/kj move ↵ enter q return",
}
}
@ -54,12 +54,12 @@ func (management *Management) Event(state *ui.State, event vaxis.Event) (process
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:

View File

@ -1,6 +1,8 @@
package app
import (
"bufio"
"fmt"
"os"
"os/exec"
"path"
@ -23,9 +25,9 @@ const title = ` ___ __
/ _/__ ___ / /__
/ _/ -_) -_) (_-<
/_/ \__/\__/_/___/
neofeels 0.1.0`
neofeels 0.2.0`
func NewMainMenu() *MainMenu {
func NewMainMenu(index int) *MainMenu {
return &MainMenu{
title,
ui.NewList([]string{
@ -38,7 +40,7 @@ func NewMainMenu() *MainMenu {
"change your settings",
"see credits",
"read documentation",
}),
}, index),
"↑↓/kj move ↵ enter q exit",
}
}
@ -64,13 +66,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 +123,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 +166,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)
}
}

View File

@ -18,7 +18,7 @@ type Neighbors struct {
subscriptions *ttbp.Subscriptions
}
func NewNeighbors() *Neighbors {
func NewNeighbors(index int) *Neighbors {
users := ttbp.SortUsersByRecent(ttbp.GetUsers())
subscriptions := ttbp.GetSubscriptions()
list := []string{}
@ -28,7 +28,7 @@ func NewNeighbors() *Neighbors {
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) {
@ -81,7 +81,7 @@ func (neighbors *Neighbors) Event(state *ui.State, event vaxis.Event) (processed
neighbors.subscriptions.Write()
neighbors.list.SetItem(neighbors.list.Index(), 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
}

View File

@ -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
}

View File

@ -27,21 +27,24 @@ func NewPublishing() *Publishing {
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,13 +66,19 @@ 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()]))
}
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(), 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})
}

View File

@ -16,7 +16,7 @@ type Subscriptions struct {
subscriptions *ttbp.Subscriptions
}
func NewSubscriptions() *Subscriptions {
func NewSubscriptions(index int) *Subscriptions {
users := ttbp.SortUsersByRecent(ttbp.GetUsers())
subscriptions := ttbp.GetSubscriptions()
list := []string{}
@ -30,7 +30,7 @@ func NewSubscriptions() *Subscriptions {
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()]
@ -69,7 +69,7 @@ func (subscriptions *Subscriptions) Event(state *ui.State, event vaxis.Event) (p
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

View File

@ -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,9 +20,10 @@ 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{}
for _, post := range posts {
@ -34,10 +36,11 @@ func NewUserPage(user string, self bool) *UserPage {
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)
}
}

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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)
}

View File

@ -13,28 +13,30 @@ 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")
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")
PathUserHTMLRender = path.Join(PathUserConfig, "html")
PathUserSubs = path.Join(PathUserConfig, "subs")
)
type User struct {
@ -64,17 +66,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 +112,7 @@ type Post struct {
Words int
Author string
Nopub bool
HTML bool
}
func GetPostsForUser(user string) (posts []Post) {
@ -110,6 +120,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 +131,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 +163,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 +177,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 +335,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()

View File

@ -17,9 +17,10 @@ type List struct {
offset int
}
func NewList(items []string) List {
func NewList(items []string, index int) List {
return List{
items: items,
index: index,
}
}

View File

@ -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()
}