Add syncing

This commit is contained in:
Martin Pander
2026-02-03 16:04:47 +01:00
parent 2e33893e29
commit 44ddbc0f47
12 changed files with 480 additions and 72 deletions

View File

@ -40,9 +40,6 @@ func NewTimeEditorPage(com *common.Common, interval *timewarrior.Interval) *Time
// Extract project from tags if it exists
projects := com.TW.GetProjects()
selectedProject, remainingTags := extractProjectFromTags(interval.Tags, projects)
if selectedProject == "" && len(projects) > 0 {
selectedProject = projects[0] // Default to first project (required)
}
// Create project picker with onCreate support for new projects
projectItemProvider := func() []list.Item {
@ -66,18 +63,23 @@ func NewTimeEditorPage(com *common.Common, interval *timewarrior.Interval) *Time
}
}
opts := []picker.PickerOption{
picker.WithOnCreate(projectOnCreate),
}
if selectedProject != "" {
opts = append(opts, picker.WithDefaultValue(selectedProject))
} else {
opts = append(opts, picker.WithFilterByDefault(true))
}
projectPicker := picker.New(
com,
"Project",
projectItemProvider,
projectOnSelect,
picker.WithOnCreate(projectOnCreate),
picker.WithFilterByDefault(true),
opts...,
)
projectPicker.SetSize(50, 10) // Compact size for inline use
if selectedProject != "" {
projectPicker.SelectItemByFilterValue(selectedProject)
}
// Create start timestamp editor
startEditor := timestampeditor.New(com).
@ -129,8 +131,14 @@ func (p *TimeEditorPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case timeEditorProjectSelectedMsg:
// Update selected project
p.selectedProject = msg.project
// Blur current field (project picker)
p.blurCurrentField()
// Advance to tags field
p.currentField = 1
// Refresh tag autocomplete with filtered combinations
cmds = append(cmds, p.updateTagSuggestions())
// Focus tags input
cmds = append(cmds, p.focusCurrentField())
return p, tea.Batch(cmds...)
case tea.KeyMsg:
@ -144,18 +152,33 @@ func (p *TimeEditorPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return model, BackCmd
case key.Matches(msg, p.common.Keymap.Ok):
// Don't save if the project picker is focused - let it handle Enter
if p.currentField != 0 {
// Save and exit
p.saveInterval()
model, err := p.common.PopPage()
if err != nil {
slog.Error("page stack empty")
return nil, tea.Quit
}
return model, tea.Batch(tea.Batch(cmds...), refreshIntervals)
// Handle Enter based on current field
if p.currentField == 0 {
// Project picker - let it handle Enter (will trigger projectSelectedMsg)
break
}
// If picker is focused, let it handle the key below
if p.currentField == 1 {
// Tags field
if p.tagsInput.HasSuggestions() {
// Let autocomplete handle suggestion selection
break
}
// Tags confirmed without suggestions - advance to start timestamp
p.blurCurrentField()
p.currentField = 2
cmds = append(cmds, p.focusCurrentField())
return p, tea.Batch(cmds...)
}
// For all other fields (2-4: start, end, adjust), save and exit
p.saveInterval()
model, err := p.common.PopPage()
if err != nil {
slog.Error("page stack empty")
return nil, tea.Quit
}
return model, tea.Batch(tea.Batch(cmds...), refreshIntervals)
case key.Matches(msg, p.common.Keymap.Next):
// Move to next field
@ -347,15 +370,16 @@ func (p *TimeEditorPage) saveInterval() {
// Add project to tags if not already present
if p.selectedProject != "" {
projectTag := "project:" + p.selectedProject
projectExists := false
for _, tag := range tags {
if tag == p.selectedProject {
if tag == projectTag {
projectExists = true
break
}
}
if !projectExists {
tags = append([]string{p.selectedProject}, tags...) // Prepend project
tags = append([]string{projectTag}, tags...) // Prepend project tag
}
}
@ -415,11 +439,15 @@ func extractProjectFromTags(tags []string, projects []string) (string, []string)
var remaining []string
for _, tag := range tags {
if foundProject == "" && projectSet[tag] {
foundProject = tag // First matching project
} else {
remaining = append(remaining, tag)
// Check if this tag is a project tag (format: "project:projectname")
if strings.HasPrefix(tag, "project:") {
projectName := strings.TrimPrefix(tag, "project:")
if foundProject == "" && projectSet[projectName] {
foundProject = projectName // First matching project
continue // Don't add to remaining tags
}
}
remaining = append(remaining, tag)
}
return foundProject, remaining
@ -432,6 +460,8 @@ func filterTagCombinationsByProject(combinations []string, project string) []str
return combinations
}
projectTag := "project:" + project
var filtered []string
for _, combo := range combinations {
// Parse the combination into individual tags
@ -439,11 +469,11 @@ func filterTagCombinationsByProject(combinations []string, project string) []str
// Check if project exists in this combination
for _, tag := range tags {
if tag == project {
if tag == projectTag {
// Found the project - now remove it from display
var displayTags []string
for _, t := range tags {
if t != project {
if t != projectTag {
displayTags = append(displayTags, t)
}
}