Files
lazysecrets/internal/ui/panes/fields.go
T
keb 56d3d25c9c feat: link each changelog entry to its PR and commit (#11)
Like Immich's and RomM's release notes: every changelog entry now links to its pull request and its commit, in `CHANGELOG.md` and in the release notes on Gitea:

```
- Search with fzf's scoring and query syntax ([#11](…/pulls/11)) ([65dc2d7](…/commit/65dc2d7…))
```

- `cliff.toml`: the entry template adds the short commit hash as a link; a postprocessor turns the squash commit's `(#N)` into a link to the PR.
- The app shows release notes as text (`U`, and the status pane's changelog to come), so `Release.Notes()` turns Markdown links back into their text: `Search (#11) (65dc2d7)`.

Checked with `git cliff --unreleased` on main: every entry since v0.1.0 gets both links. Test for the link stripping.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Karl-Einar Bergstrøm <karl-einarb@outlook.com>
Reviewed-on: #11
2026-10-11 11:20:19 +02:00

77 lines
2.1 KiB
Go

package panes
import (
"fmt"
"github.com/charmbracelet/x/ansi"
"git.kebcloud.no/keb/lazysecrets/internal/ui/styles"
)
// Fields is the fourth pane for anything but a secret (an account, and
// what describes vaults, scopes and files): labelled values, one per row,
// a label on the first of several values, to move through, filter and
// copy from. It's a List, so / filters it like the other panes. Secrets
// have Detail, for revealing and versions.
type Fields struct {
List[Field]
shown bool
}
// Field is a row: a value under a label. Info rows (loading, errors)
// can't be copied; Err ones are errors. A row can also be opened: URL in
// the browser (o), Path in the editor (e), or Provider signed in to (L).
type Field struct {
Label, Value string
Info, Err bool
URL, Path, Provider string
}
func NewFields() Fields {
l := NewList("", Item[Field]{
ID: func(f Field) string { return f.Label + "\x00" + f.Value },
Filter: func(f Field) string { return f.Value },
// "/tenant" finds the tenant's rows.
FilterExtra: func(f Field) string { return f.Label },
Render: func(st styles.Styles, f Field, width int) string {
value := f.Value
switch {
case f.Err:
value = st.Error.Render(value)
case f.Info:
value = st.Muted.Render(value)
}
return ansi.Truncate(st.Label.Render(fmt.Sprintf("%-10s", f.Label))+" "+value, width, "…")
},
})
l.Empty = "nothing to show"
return Fields{List: l}
}
// Show shows fields under title, instead of the secret pane. Another
// title (another account or vault) starts over, without a filter.
func (s *Fields) Show(title string, fields []Field) {
if title != s.Title {
s.Reset()
s.Title = title
}
s.shown = true
s.SetItems(fields)
}
// Hide gives the fourth pane back to the secret pane.
func (s *Fields) Hide() { s.shown = false }
// Shown reports whether the fourth pane shows Fields.
func (s Fields) Shown() bool { return s.shown }
// Selected is the field under the cursor, unless it's an info row.
func (s Fields) Selected() (Field, bool) {
f, ok := s.List.Selected()
if !ok || f.Info {
return Field{}, false
}
return f, true
}