Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
46439d2488 | |||
c29a7c1594 | |||
350727cd58 | |||
9d4053da0b | |||
6dd7d573b8 | |||
462d18850e | |||
5a7db89b47 | |||
e8125b419f | |||
50cb60cdc4 | |||
7e4456450c |
24
CHANGELOG.md
24
CHANGELOG.md
@ -1,5 +1,29 @@
|
|||||||
# Changelog
|
# 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
|
## [0.1.1] - 2025-01-09
|
||||||
|
|
||||||
- use 755 for config dir
|
- use 755 for config dir
|
||||||
|
5
Makefile
5
Makefile
@ -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
|
|
||||||
|
|
||||||
|
@ -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{
|
||||||
|
@ -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{
|
||||||
|
@ -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{
|
||||||
|
@ -18,10 +18,12 @@ type Config struct {
|
|||||||
descriptions []string
|
descriptions []string
|
||||||
}
|
}
|
||||||
|
|
||||||
var configList = []string{
|
var configList = [][]vaxis.Segment{
|
||||||
"publish to html",
|
[]vaxis.Segment{{Text: "pager"}},
|
||||||
"publish to gopher",
|
[]vaxis.Segment{{Text: "publish to html"}},
|
||||||
"default to nopub",
|
[]vaxis.Segment{{Text: "publish to gopher"}},
|
||||||
|
[]vaxis.Segment{{Text: "default to nopub"}},
|
||||||
|
[]vaxis.Segment{{Text: "default to html"}},
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConfig() *Config {
|
func NewConfig() *Config {
|
||||||
@ -35,6 +37,10 @@ func NewConfig() *Config {
|
|||||||
"↑↓/kj move ↵ enter q return",
|
"↑↓/kj move ↵ enter q return",
|
||||||
cfg,
|
cfg,
|
||||||
[]string{
|
[]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?
|
`do you want to publish your feels online?
|
||||||
|
|
||||||
if yes, your feels will be published to a directory of your choice in your
|
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.`,
|
next entry update.`,
|
||||||
`should your posts automatically show up on your world-visible pages, such as
|
`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
|
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.`,
|
fact. changes to this setting will not be made retroactively.`,
|
||||||
`in which directory under public_html should your blog reside? for example,
|
`in which directory under public_html should your blog reside? for example,
|
||||||
"blog" will make it visible under https://tilde.town/~you/blog.`,
|
"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":
|
case "0", "1":
|
||||||
i, _ := strconv.Atoi(key.String())
|
i, _ := strconv.Atoi(key.String())
|
||||||
config.list.SetIndex(i)
|
config.list.SetIndex(i)
|
||||||
case "2", "3":
|
|
||||||
if config.config.Publishing {
|
|
||||||
|
|
||||||
i, _ := strconv.Atoi(key.String())
|
|
||||||
config.list.SetIndex(i)
|
|
||||||
}
|
|
||||||
case "q", "h", "Left":
|
case "q", "h", "Left":
|
||||||
ui.ViewChange <- NewMainMenu(6)
|
ui.ViewChange <- NewMainMenu(6)
|
||||||
case "Enter", "l", "Right", "Space":
|
case "Enter", "l", "Right", "Space":
|
||||||
switch config.list.Index() {
|
switch config.list.Index() {
|
||||||
case 0:
|
case 0:
|
||||||
config.config.Publishing = !config.config.Publishing
|
config.config.Pager = config.changePager(state)
|
||||||
case 1:
|
case 1:
|
||||||
config.config.Gopher = !config.config.Gopher
|
config.config.Publishing = !config.config.Publishing
|
||||||
case 2:
|
case 2:
|
||||||
config.config.Nopub = !config.config.Nopub
|
config.config.Gopher = !config.config.Gopher
|
||||||
case 3:
|
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.PublishDir = config.changePublishDir(state)
|
||||||
}
|
}
|
||||||
config.config.Write()
|
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) {
|
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)
|
||||||
}
|
}
|
||||||
@ -116,17 +124,19 @@ func (config *Config) Draw(state *ui.State) {
|
|||||||
Column: win.Width/2 - 21,
|
Column: win.Width/2 - 21,
|
||||||
Row: win.Height/2 - 2,
|
Row: win.Height/2 - 2,
|
||||||
Width: 28,
|
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-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+13, 30, 1).Print(vaxis.Segment{Text: config.help})
|
win.New(win.Width/2-15, win.Height/2+15, 30, 1).Print(vaxis.Segment{Text: config.help})
|
||||||
|
|
||||||
// drawing the current selected options
|
// 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-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.Gopher)})
|
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.Nopub)})
|
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 {
|
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,
|
Vx: win.Vx,
|
||||||
Parent: &win,
|
Parent: &win,
|
||||||
Column: win.Width/2 + 9,
|
Column: win.Width/2 + 9,
|
||||||
Row: win.Height/2 + 1,
|
Row: win.Height/2 + 2,
|
||||||
Width: 12,
|
Width: 12,
|
||||||
Height: 1,
|
Height: 1,
|
||||||
})
|
})
|
||||||
@ -158,7 +168,7 @@ func (config *Config) changePublishDir(state *ui.State) string {
|
|||||||
Vx: win.Vx,
|
Vx: win.Vx,
|
||||||
Parent: nil,
|
Parent: nil,
|
||||||
Column: win.Width/2 + 9,
|
Column: win.Width/2 + 9,
|
||||||
Row: win.Height/2 + 1,
|
Row: win.Height/2 + 2,
|
||||||
Width: 12,
|
Width: 12,
|
||||||
Height: 1,
|
Height: 1,
|
||||||
})
|
})
|
||||||
@ -167,3 +177,41 @@ func (config *Config) changePublishDir(state *ui.State) string {
|
|||||||
|
|
||||||
return ""
|
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 ""
|
||||||
|
}
|
||||||
|
@ -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{
|
||||||
|
@ -57,7 +57,7 @@ func (graffiti *Graffiti) Event(state *ui.State, event vaxis.Event) (processed b
|
|||||||
}
|
}
|
||||||
win := state.Window()
|
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-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})
|
win.New(win.Width/2-9, win.Height/2+8, 18, 1).Print(vaxis.Segment{Text: graffiti.help})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -97,9 +97,6 @@ func editGraffiti(state *ui.State) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// for some reason vaxis doubles all events for Press/Release so this just ignores releases
|
vt.Update(ev)
|
||||||
if key, ok := ev.(vaxis.Key); ok && key.EventType == vaxis.EventPress {
|
|
||||||
vt.Update(ev)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
82
app/menu.go
82
app/menu.go
@ -25,21 +25,53 @@ const title = ` ___ __
|
|||||||
/ _/__ ___ / /__
|
/ _/__ ___ / /__
|
||||||
/ _/ -_) -_) (_-<
|
/ _/ -_) -_) (_-<
|
||||||
/_/ \__/\__/_/___/
|
/_/ \__/\__/_/___/
|
||||||
neofeels 0.1.1`
|
neofeels 0.3.0`
|
||||||
|
|
||||||
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)
|
||||||
@ -123,10 +173,7 @@ func showManpage(state *ui.State) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// for some reason vaxis doubles all events for Press/Release so this just ignores releases
|
vt.Update(ev)
|
||||||
if key, ok := ev.(vaxis.Key); ok && key.EventType == vaxis.EventPress {
|
|
||||||
vt.Update(ev)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,9 +216,6 @@ func newFeels(state *ui.State) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// for some reason vaxis doubles all events for Press/Release so this just ignores releases
|
vt.Update(ev)
|
||||||
if key, ok := ev.(vaxis.Key); ok && key.EventType == vaxis.EventPress {
|
|
||||||
vt.Update(ev)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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())
|
||||||
}
|
}
|
||||||
|
@ -20,28 +20,31 @@ 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{
|
||||||
title,
|
title,
|
||||||
ui.NewList(list, 0),
|
ui.NewList(list, 0),
|
||||||
"↑↓/kj move ↵ enter q return",
|
"↑↓/kj move n nopub m html q return",
|
||||||
posts,
|
posts,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatPublishing(post ttbp.Post) string {
|
func formatPublishing(post ttbp.Post) string {
|
||||||
nopub := ""
|
status := ""
|
||||||
if post.Nopub {
|
if post.Nopub {
|
||||||
nopub = "(nopub)"
|
status += "(nopub) "
|
||||||
|
}
|
||||||
|
if post.HTML {
|
||||||
|
status += "(html)"
|
||||||
}
|
}
|
||||||
return fmt.Sprintf(
|
return fmt.Sprintf(
|
||||||
"%s %s",
|
"%s %s",
|
||||||
post.Date.Format("2006-01-02"),
|
post.Date.Format("2006-01-02"),
|
||||||
nopub,
|
status,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,11 +67,17 @@ func (publishing *Publishing) Event(state *ui.State, event vaxis.Event) (process
|
|||||||
case "q", "h", "Left":
|
case "q", "h", "Left":
|
||||||
ttbp.Publish()
|
ttbp.Publish()
|
||||||
ui.ViewChange <- NewManagement(1)
|
ui.ViewChange <- NewManagement(1)
|
||||||
case "Enter", "l", "Right":
|
case "n":
|
||||||
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":
|
||||||
|
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
|
processed = true
|
||||||
@ -83,10 +92,10 @@ func (publishing *Publishing) Draw(state *ui.State) {
|
|||||||
publishing.list.Draw(vaxis.Window{
|
publishing.list.Draw(vaxis.Window{
|
||||||
Vx: win.Vx,
|
Vx: win.Vx,
|
||||||
Parent: nil,
|
Parent: nil,
|
||||||
Column: win.Width/2 - 14,
|
Column: win.Width/2 - 15,
|
||||||
Row: win.Height/2 - 2,
|
Row: win.Height/2 - 2,
|
||||||
Width: 28,
|
Width: 30,
|
||||||
Height: 10,
|
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})
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
32
app/user.go
32
app/user.go
@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"git.sr.ht/~rockorager/vaxis"
|
"git.sr.ht/~rockorager/vaxis"
|
||||||
"git.sr.ht/~rockorager/vaxis/widgets/term"
|
"git.sr.ht/~rockorager/vaxis/widgets/term"
|
||||||
|
"git.tilde.town/nbsp/neofeels/config"
|
||||||
"git.tilde.town/nbsp/neofeels/ttbp"
|
"git.tilde.town/nbsp/neofeels/ttbp"
|
||||||
"git.tilde.town/nbsp/neofeels/ui"
|
"git.tilde.town/nbsp/neofeels/ui"
|
||||||
)
|
)
|
||||||
@ -24,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{
|
||||||
@ -94,11 +95,27 @@ func showPost(state *ui.State, post ttbp.Post) {
|
|||||||
vt.TERM = os.Getenv("TERM")
|
vt.TERM = os.Getenv("TERM")
|
||||||
vt.Attach(state.PostEvent())
|
vt.Attach(state.PostEvent())
|
||||||
vt.Focus()
|
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 == "" {
|
if pager == "" {
|
||||||
pager = "less"
|
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 {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -116,9 +133,6 @@ func showPost(state *ui.State, post ttbp.Post) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// for some reason vaxis doubles all events for Press/Release so this just ignores releases
|
vt.Update(ev)
|
||||||
if key, ok := ev.(vaxis.Key); ok && key.EventType == vaxis.EventPress {
|
|
||||||
vt.Update(ev)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,8 @@ type Config struct {
|
|||||||
PublishDir string `json:"publish dir"`
|
PublishDir string `json:"publish dir"`
|
||||||
Publishing bool `json:"publishing"`
|
Publishing bool `json:"publishing"`
|
||||||
Rainbows bool `json:"rainbows"` // we don't care about this
|
Rainbows bool `json:"rainbows"` // we don't care about this
|
||||||
|
HTML bool `json:"html"`
|
||||||
|
Pager string `json:"pager"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var Default = &Config{
|
var Default = &Config{
|
||||||
@ -22,6 +24,8 @@ var Default = &Config{
|
|||||||
PublishDir: "blog",
|
PublishDir: "blog",
|
||||||
Publishing: false,
|
Publishing: false,
|
||||||
Rainbows: false,
|
Rainbows: false,
|
||||||
|
HTML: false,
|
||||||
|
Pager: "less",
|
||||||
}
|
}
|
||||||
|
|
||||||
func Read() (config *Config, err error) {
|
func Read() (config *Config, err error) {
|
||||||
|
1
go.mod
1
go.mod
@ -4,6 +4,7 @@ go 1.23.4
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
git.sr.ht/~rockorager/vaxis v0.11.0
|
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/dustin/go-humanize v1.0.1
|
||||||
github.com/yuin/goldmark v1.4.13
|
github.com/yuin/goldmark v1.4.13
|
||||||
)
|
)
|
||||||
|
3
go.sum
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/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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
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=
|
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-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-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-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.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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||||
|
196
ttbp/ttbp.go
196
ttbp/ttbp.go
@ -13,28 +13,32 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.tilde.town/nbsp/neofeels/config"
|
"git.tilde.town/nbsp/neofeels/config"
|
||||||
|
"github.com/djherbis/times"
|
||||||
"github.com/yuin/goldmark"
|
"github.com/yuin/goldmark"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
PathVar = "/var/global/ttbp"
|
PathVar = "/var/global/ttbp"
|
||||||
PathVarWWW = path.Join(PathVar, "www")
|
PathVarWWW = path.Join(PathVar, "www")
|
||||||
PathLive = "https://tilde.town/~"
|
PathLive = "https://tilde.town/~"
|
||||||
PathUserFile = path.Join(PathVar, "users.txt")
|
PathUserFile = path.Join(PathVar, "users.txt")
|
||||||
PathGraff = path.Join(PathVar, "graffiti")
|
PathGraff = path.Join(PathVar, "graffiti")
|
||||||
PathWall = path.Join(PathGraff, "wall.txt")
|
PathWall = path.Join(PathGraff, "wall.txt")
|
||||||
PathWallLock = path.Join(PathGraff, ".lock")
|
PathWallLock = path.Join(PathGraff, ".lock")
|
||||||
PathUser = os.Getenv("HOME")
|
PathUser = os.Getenv("HOME")
|
||||||
PathUserFeels = path.Join(PathUser, ".ttbp")
|
PathUserFeels = path.Join(PathUser, ".ttbp")
|
||||||
PathUserHTML = path.Join(PathUser, "public_html")
|
PathUserHTML = path.Join(PathUser, "public_html")
|
||||||
PathUserConfig = path.Join(PathUserFeels, "config")
|
PathUserGopherhole = path.Join(PathUser, "public_gopher", "feels")
|
||||||
PathUserEntries = path.Join(PathUserFeels, "entries")
|
PathUserConfig = path.Join(PathUserFeels, "config")
|
||||||
PathUserBuried = path.Join(PathUserFeels, "buried")
|
PathUserEntries = path.Join(PathUserFeels, "entries")
|
||||||
PathUserBackups = path.Join(PathUserFeels, "backups")
|
PathUserBuried = path.Join(PathUserFeels, "buried")
|
||||||
PathUserWWW = path.Join(PathUserFeels, "www")
|
PathUserBackups = path.Join(PathUserFeels, "backups")
|
||||||
PathUserRc = path.Join(PathUserConfig, "ttbprc")
|
PathUserWWW = path.Join(PathUserFeels, "www")
|
||||||
PathUserNopub = path.Join(PathUserConfig, "nopub")
|
PathUserGopher = path.Join(PathUserFeels, "gopher")
|
||||||
PathUserSubs = path.Join(PathUserConfig, "subs")
|
PathUserRc = path.Join(PathUserConfig, "ttbprc")
|
||||||
|
PathUserNopub = path.Join(PathUserConfig, "nopub")
|
||||||
|
PathUserHTMLRender = path.Join(PathUserConfig, "html")
|
||||||
|
PathUserSubs = path.Join(PathUserConfig, "subs")
|
||||||
)
|
)
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
@ -64,17 +68,24 @@ func GetUsers() (users []User) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get last published file
|
// 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 {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var lastPublished time.Time = *new(time.Time)
|
var lastPublished time.Time = *new(time.Time)
|
||||||
if len(entries) > 0 {
|
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 {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
lastPublished = info.ModTime()
|
defer file.Close()
|
||||||
|
timespec, err := times.StatFile(file)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lastPublished = timespec.BirthTime()
|
||||||
}
|
}
|
||||||
|
|
||||||
users = append(users, User{
|
users = append(users, User{
|
||||||
@ -103,6 +114,7 @@ type Post struct {
|
|||||||
Words int
|
Words int
|
||||||
Author string
|
Author string
|
||||||
Nopub bool
|
Nopub bool
|
||||||
|
HTML bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPostsForUser(user string) (posts []Post) {
|
func GetPostsForUser(user string) (posts []Post) {
|
||||||
@ -110,6 +122,7 @@ func GetPostsForUser(user string) (posts []Post) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
nopubFile, err := os.OpenFile(PathUserNopub, os.O_RDONLY|os.O_CREATE, 0644)
|
nopubFile, err := os.OpenFile(PathUserNopub, os.O_RDONLY|os.O_CREATE, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -120,6 +133,18 @@ func GetPostsForUser(user string) (posts []Post) {
|
|||||||
for nopubScanner.Scan() {
|
for nopubScanner.Scan() {
|
||||||
nopubs = append(nopubs, nopubScanner.Text())
|
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 {
|
for _, post := range postFiles {
|
||||||
// retrieve date of file
|
// retrieve date of file
|
||||||
// assume file ends in .txt
|
// assume file ends in .txt
|
||||||
@ -140,7 +165,7 @@ func GetPostsForUser(user string) (posts []Post) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get modtime of file
|
// get modtime of file
|
||||||
stat, err := file.Stat()
|
timespec, err := times.StatFile(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
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{
|
posts = append([]Post{Post{
|
||||||
Author: user,
|
Author: user,
|
||||||
Date: fileDate,
|
Date: fileDate,
|
||||||
LastEdited: stat.ModTime(),
|
LastEdited: timespec.BirthTime(),
|
||||||
Words: count,
|
Words: count,
|
||||||
Nopub: nopub,
|
Nopub: nopub,
|
||||||
|
HTML: html,
|
||||||
}}, posts...)
|
}}, posts...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -302,15 +337,82 @@ func ToggleNopub(t time.Time) {
|
|||||||
writer.Flush()
|
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() {
|
func Publish() {
|
||||||
cfg, err := config.Read()
|
cfg, err := config.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return // TODO: expose this error to the user
|
return // TODO: expose this error to the user
|
||||||
}
|
}
|
||||||
if cfg.Publishing {
|
if cfg.Publishing {
|
||||||
|
os.RemoveAll(PathUserWWW) // remove all post and start over
|
||||||
if _, err := os.Stat(PathUserWWW); os.IsNotExist(err) {
|
if _, err := os.Stat(PathUserWWW); os.IsNotExist(err) {
|
||||||
os.MkdirAll(PathUserWWW, 0700)
|
os.MkdirAll(PathUserWWW, 0700)
|
||||||
os.Symlink(path.Join(PathUserConfig, "style.css"), path.Join(PathUserWWW, "style.css"))
|
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"))
|
file, err := os.Create(path.Join(PathUserWWW, "index.html"))
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
@ -336,7 +438,49 @@ func Publish() {
|
|||||||
writer.WriteString(string(footer))
|
writer.WriteString(string(footer))
|
||||||
writer.Flush()
|
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() {
|
func Unpublish() {
|
||||||
@ -348,6 +492,10 @@ func Unpublish() {
|
|||||||
os.RemoveAll(PathUserWWW)
|
os.RemoveAll(PathUserWWW)
|
||||||
os.RemoveAll(path.Join(PathUserHTML, cfg.PublishDir))
|
os.RemoveAll(path.Join(PathUserHTML, cfg.PublishDir))
|
||||||
}
|
}
|
||||||
|
if cfg.Gopher {
|
||||||
|
os.RemoveAll(PathUserGopher)
|
||||||
|
os.RemoveAll(PathUserGopherhole)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func writePage(post Post, header, footer []byte) {
|
func writePage(post Post, header, footer []byte) {
|
||||||
|
30
ui/list.go
30
ui/list.go
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
3
ui/ui.go
3
ui/ui.go
@ -25,8 +25,7 @@ type State struct {
|
|||||||
|
|
||||||
func New(view View) (state State, err error) {
|
func New(view View) (state State, err error) {
|
||||||
vx, err := vaxis.New(vaxis.Options{
|
vx, err := vaxis.New(vaxis.Options{
|
||||||
DisableMouse: false,
|
DisableMouse: true,
|
||||||
CSIuBitMask: vaxis.CSIuDisambiguate | vaxis.CSIuReportEvents | vaxis.CSIuAlternateKeys | vaxis.CSIuAllKeys | vaxis.CSIuAssociatedText,
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
Loading…
x
Reference in New Issue
Block a user