fix(tasks): reset description checklist when a recurring task recurs (#2941)

This commit is contained in:
Tink 2026-06-19 16:54:20 +02:00 committed by GitHub
parent adf031128e
commit 767ce3bc7e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 58 additions and 0 deletions

View File

@ -1747,6 +1747,20 @@ func setTaskDatesFromCurrentDateRepeat(oldTask, newTask *Task) {
newTask.Done = false
}
var (
checklistTiptapCheckedRegex = regexp.MustCompile(`(data-checked=")true(")`)
checklistInputCheckedRegex = regexp.MustCompile(`(<input[^>]*type=["']checkbox["'][^>]*?)\s+checked(?:=["'][^"']*["'])?`)
)
// resetDescriptionChecklist unchecks every checklist item in a TipTap HTML description
// (descriptions are always stored as HTML, never markdown) without touching other content,
// so a recurring task's next occurrence does not inherit checked items.
func resetDescriptionChecklist(description string) string {
description = checklistTiptapCheckedRegex.ReplaceAllString(description, "${1}false${2}")
description = checklistInputCheckedRegex.ReplaceAllString(description, "$1")
return description
}
// This helper function updates the reminders, doneAt, start, end and due dates of the *old* task
// and saves the new values in the newTask object.
// We make a few assumptions here:
@ -1766,6 +1780,11 @@ func updateDone(oldTask *Task, newTask *Task) (updateDoneAt bool) {
setTaskDatesDefault(oldTask, newTask)
}
// A recurring task reopens for its next occurrence, so its checklist starts fresh.
if oldTask.isRepeating() && !newTask.Done {
newTask.Description = resetDescriptionChecklist(newTask.Description)
}
newTask.DoneAt = time.Now()
}

View File

@ -986,6 +986,45 @@ func TestUpdateDone(t *testing.T) {
assert.False(t, newTask.Done)
})
})
t.Run("reset checklist on recurrence", func(t *testing.T) {
const checked = `before<ul data-type="taskList"><li data-checked="true" data-type="taskItem"><label><input type="checkbox" checked="checked"><span></span></label><div><p>Item</p></li></ul>after`
const unchecked = `before<ul data-type="taskList"><li data-checked="false" data-type="taskItem"><label><input type="checkbox"><span></span></label><div><p>Item</p></li></ul>after`
oldTask := &Task{
Done: false,
RepeatAfter: 8600,
DueDate: time.Unix(1550000000, 0),
}
newTask := &Task{
Done: true,
Description: checked,
}
updateDone(oldTask, newTask)
assert.False(t, newTask.Done)
assert.True(t, newTask.DueDate.After(oldTask.DueDate))
assert.Equal(t, unchecked, newTask.Description)
})
t.Run("non-recurring description untouched", func(t *testing.T) {
const checked = `before<ul data-type="taskList"><li data-checked="true" data-type="taskItem"><label><input type="checkbox" checked="checked"><span></span></label><div><p>Item</p></li></ul>after`
oldTask := &Task{
Done: false,
RepeatAfter: 0,
RepeatMode: TaskRepeatModeDefault,
DueDate: time.Unix(1550000000, 0),
}
newTask := &Task{
Done: true,
Description: checked,
}
updateDone(oldTask, newTask)
assert.True(t, newTask.Done)
assert.Equal(t, checked, newTask.Description)
})
})
}