feat(tui): add link picker and navigation history
CI / test (pull_request) Successful in 2m18s

Press [ in detail view to open link picker showing all [[links]]
in the current entry. Enter follows a link, resolving by title
then body substring. Navigation history stack enables esc to pop
back through followed links before returning to list.

Adds Store.ResolveLink() for non-transactional link resolution
from the TUI layer.
This commit is contained in:
2026-05-21 14:03:09 -04:00
parent 8426c2fbc1
commit 2684eb1d24
6 changed files with 201 additions and 0 deletions
+65
View File
@@ -0,0 +1,65 @@
package tui
import (
"strings"
"github.com/lerko/nib/internal/link"
)
type linkPickerModel struct {
links []string
cursor int
}
func newLinkPicker(body string) linkPickerModel {
return linkPickerModel{
links: link.ExtractLinks(body),
}
}
func (lp linkPickerModel) selected() string {
if len(lp.links) == 0 || lp.cursor >= len(lp.links) {
return ""
}
return lp.links[lp.cursor]
}
func (lp linkPickerModel) update(key string) linkPickerModel {
switch key {
case "up", "k":
if lp.cursor > 0 {
lp.cursor--
}
case "down", "j":
if lp.cursor < len(lp.links)-1 {
lp.cursor++
}
}
return lp
}
func (lp linkPickerModel) view(width int) string {
var b strings.Builder
b.WriteString(titleStyle.Render("follow link") + "\n\n")
if len(lp.links) == 0 {
b.WriteString(hintDescStyle.Render(" no [[links]] in this entry"))
b.WriteString("\n\n")
b.WriteString(helpStyle.Render("esc:back"))
return b.String()
}
for i, lt := range lp.links {
label := "[[" + lt + "]]"
if i == lp.cursor {
b.WriteString(selectedItemStyle.Render(" " + label))
} else {
b.WriteString(listItemStyle.Render(label))
}
b.WriteString("\n")
}
b.WriteString("\n")
b.WriteString(helpStyle.Render("enter:follow esc:back"))
return b.String()
}