Add things

This commit is contained in:
Martin Pander
2026-02-04 13:13:04 +01:00
parent 474bb3dc07
commit 02fa2e503a
15 changed files with 1214 additions and 126 deletions

View File

@ -0,0 +1,174 @@
package detailsviewer
import (
"log/slog"
"tasksquire/common"
"tasksquire/taskwarrior"
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/glamour"
"github.com/charmbracelet/lipgloss"
)
// DetailsViewer is a reusable component for displaying task details
type DetailsViewer struct {
common *common.Common
viewport viewport.Model
task *taskwarrior.Task
focused bool
width int
height int
}
// New creates a new DetailsViewer component
func New(com *common.Common) *DetailsViewer {
return &DetailsViewer{
common: com,
viewport: viewport.New(0, 0),
focused: false,
}
}
// SetTask updates the task to display
func (d *DetailsViewer) SetTask(task *taskwarrior.Task) {
d.task = task
d.updateContent()
}
// Focus sets the component to focused state (for future interactivity)
func (d *DetailsViewer) Focus() {
d.focused = true
}
// Blur sets the component to blurred state
func (d *DetailsViewer) Blur() {
d.focused = false
}
// IsFocused returns whether the component is focused
func (d *DetailsViewer) IsFocused() bool {
return d.focused
}
// SetSize implements common.Component
func (d *DetailsViewer) SetSize(width, height int) {
d.width = width
d.height = height
// Account for border and padding (4 chars horizontal, 4 lines vertical)
d.viewport.Width = max(width-4, 0)
d.viewport.Height = max(height-4, 0)
// Refresh content with new width
d.updateContent()
}
// Init implements tea.Model
func (d *DetailsViewer) Init() tea.Cmd {
return nil
}
// Update implements tea.Model
func (d *DetailsViewer) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
d.viewport, cmd = d.viewport.Update(msg)
return d, cmd
}
// View implements tea.Model
func (d *DetailsViewer) View() string {
// Title bar
titleStyle := lipgloss.NewStyle().
Bold(true).
Foreground(lipgloss.Color("252"))
helpStyle := lipgloss.NewStyle().
Foreground(lipgloss.Color("240"))
header := lipgloss.JoinHorizontal(
lipgloss.Left,
titleStyle.Render("Details"),
" ",
helpStyle.Render("(↑/↓ scroll, v/ESC close)"),
)
// Container style
containerStyle := lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("240")).
Padding(0, 1).
Width(d.width).
Height(d.height)
// Optional: highlight border when focused (for future interactivity)
if d.focused {
containerStyle = containerStyle.
BorderForeground(lipgloss.Color("86"))
}
content := lipgloss.JoinVertical(
lipgloss.Left,
header,
d.viewport.View(),
)
return containerStyle.Render(content)
}
// updateContent refreshes the viewport content based on current task
func (d *DetailsViewer) updateContent() {
if d.task == nil {
d.viewport.SetContent("(No task selected)")
return
}
detailsValue := ""
if details, ok := d.task.Udas["details"]; ok && details != nil {
detailsValue = details.(string)
}
if detailsValue == "" {
d.viewport.SetContent("(No details for this task)")
return
}
// Render markdown with glamour
renderer, err := glamour.NewTermRenderer(
glamour.WithAutoStyle(),
glamour.WithWordWrap(d.viewport.Width),
)
if err != nil {
slog.Error("failed to create markdown renderer", "error", err)
// Fallback to plain text
wrapped := lipgloss.NewStyle().
Width(d.viewport.Width).
Render(detailsValue)
d.viewport.SetContent(wrapped)
d.viewport.GotoTop()
return
}
rendered, err := renderer.Render(detailsValue)
if err != nil {
slog.Error("failed to render markdown", "error", err)
// Fallback to plain text
wrapped := lipgloss.NewStyle().
Width(d.viewport.Width).
Render(detailsValue)
d.viewport.SetContent(wrapped)
d.viewport.GotoTop()
return
}
d.viewport.SetContent(rendered)
d.viewport.GotoTop()
}
// Helper function
func max(a, b int) int {
if a > b {
return a
}
return b
}

View File

@ -103,6 +103,19 @@ func New(
key.WithHelp("i", "filter"),
)
// Disable the quit key binding - we don't want Esc to quit the list
// Esc should only cancel filtering mode
l.KeyMap.Quit = key.NewBinding(
key.WithKeys(), // No keys bound
key.WithHelp("", ""),
)
// Also disable force quit
l.KeyMap.ForceQuit = key.NewBinding(
key.WithKeys(), // No keys bound
key.WithHelp("", ""),
)
p := &Picker{
common: c,
list: l,