package tui import ( "fmt" "strings" tea "github.com/charmbracelet/bubbletea" "github.com/lerko/nib/internal/db" "github.com/lerko/nib/internal/display" ) type absorbModel struct { targetID string sources []*db.Entity cursor int offset int height int } func newAbsorbModel(targetID string) absorbModel { return absorbModel{targetID: targetID} } func (a *absorbModel) setSources(entities []*db.Entity) { a.sources = nil for _, e := range entities { if e.ID != a.targetID { a.sources = append(a.sources, e) } } a.cursor = 0 a.offset = 0 } func (a *absorbModel) setHeight(h int) { a.height = h } func (a absorbModel) selectedSource() *db.Entity { if len(a.sources) == 0 || a.cursor >= len(a.sources) { return nil } return a.sources[a.cursor] } func (a absorbModel) update(msg tea.KeyMsg) absorbModel { switch msg.String() { case "up", "k": if a.cursor > 0 { a.cursor-- if a.cursor < a.offset { a.offset = a.cursor } } case "down", "j": if a.cursor < len(a.sources)-1 { a.cursor++ visible := a.visibleCount() if a.cursor >= a.offset+visible { a.offset = a.cursor - visible + 1 } } } return a } func (a absorbModel) view(width int) string { if len(a.sources) == 0 { return statusStyle.Render("no other entities") } var b strings.Builder b.WriteString(titleStyle.Render("absorb into " + display.FormatID(a.targetID))) b.WriteString("\n") b.WriteString(helpStyle.Render("select source to merge")) b.WriteString("\n\n") visible := a.visibleCount() - 4 if visible <= 0 { visible = 10 } end := min(a.offset+visible, len(a.sources)) for i := a.offset; i < end; i++ { e := a.sources[i] line := renderAbsorbSource(e, width-4) if i == a.cursor { b.WriteString(selectedItemStyle.Render(" " + line)) } else { b.WriteString(listItemStyle.Render(line)) } if i < end-1 { b.WriteString("\n") } } return b.String() } func (a absorbModel) visibleCount() int { if a.height <= 0 { return 20 } return a.height } func renderAbsorbSource(e *db.Entity, maxWidth int) string { glyph := glyphStyle.Render(display.DisplayGlyph(e.Glyph, e.CardType)) body := e.Body if e.Title != nil { body = *e.Title } if idx := strings.IndexByte(body, '\n'); idx >= 0 { body = body[:idx] } var tags string if len(e.Tags) > 0 { limit := min(2, len(e.Tags)) tagParts := make([]string, limit) for i := 0; i < limit; i++ { tagParts[i] = tagStyle.Render("#" + e.Tags[i]) } tags = " " + strings.Join(tagParts, " ") } line := fmt.Sprintf("%s %s%s", glyph, body, tags) if maxWidth > 0 && len(stripAnsi(line)) > maxWidth { body = truncate(body, maxWidth-6) line = fmt.Sprintf("%s %s%s", glyph, body, tags) } return line }