feat(tasks): repeating tasks (daily, weekly, monthly, yearly)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m28s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m28s
- recurrence on tasks, set in the detail dialog; 🔁 on rows and Kanban cards - Completing a repeating task (detail, list, Kanban drag to done, note checkbox) creates the next one: title, tags, priority, notes, list, assignees and rule copied, subtasks reset, first open column, due date one interval later (month end clamped); pushed live as TASK_CREATED - Each task spawns at most once (re-open + complete makes no duplicate) - Export/import keep the rule (Obsidian Tasks "🔁 every week", JSON field) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0e9fd1b3ad
commit
588c679c6a
16 files changed
+376
-19
No files matched your search
@@ -60,9 +60,9 @@ type importDoc struct {
|
||||
}
|
||||
|
||||
type importTask struct {
|
||||
content, priority, due, notes string
|
||||
done bool
|
||||
subtasks []domain.SubTask
|
||||
content, priority, due, notes, recurrence string
|
||||
done bool
|
||||
subtasks []domain.SubTask
|
||||
}
|
||||
|
||||
type importTaskList struct {
|
||||
@@ -393,7 +393,7 @@ func PlanMindSiftJSONImport(data []byte) (*ImportPlan, error) {
|
||||
plan.TaskLists = append(plan.TaskLists, importTaskList{title: tl.Title, icon: tl.Icon})
|
||||
}
|
||||
for _, t := range exp.Thoughts {
|
||||
task := importTask{content: t.Content, priority: t.Priority, due: t.DueDate, notes: t.Notes, done: t.IsCompleted, subtasks: t.Subtasks}
|
||||
task := importTask{content: t.Content, priority: t.Priority, due: t.DueDate, notes: t.Notes, done: t.IsCompleted, subtasks: t.Subtasks, recurrence: t.Recurrence}
|
||||
switch {
|
||||
case t.Status == domain.StatusInbox:
|
||||
plan.Inbox = append(plan.Inbox, task)
|
||||
@@ -506,6 +506,10 @@ func (s *Store) importTaskInto(userID, listID string, t importTask, asTask bool)
|
||||
if _, err := s.UpdateThoughtTriage(userID, th.ID, "keep", "task", "", t.due, listID); err != nil {
|
||||
return fmt.Errorf("Aufgabe %q: %w", content, err)
|
||||
}
|
||||
if validRecurrence(t.recurrence) && t.recurrence != "" {
|
||||
// Imported as already handled: completing a restored task must not create a second successor
|
||||
_, _ = s.db.Exec(`UPDATE thoughts SET recurrence = ?, recurrence_spawned = ? WHERE id = ?`, t.recurrence, boolInt(t.done), th.ID)
|
||||
}
|
||||
if t.done || len(t.subtasks) > 0 {
|
||||
var done *bool
|
||||
if t.done {
|
||||
@@ -754,6 +758,7 @@ func MarkdownToBlocks(md string) []domain.DocumentBlock {
|
||||
}
|
||||
|
||||
var (
|
||||
recurRe = regexp.MustCompile(`(?i)[ \t]*🔁[ \t]*every[ \t]+(day|week|month|year)\b`)
|
||||
dueRe = regexp.MustCompile(`[ \t]*📅[ \t]*(\d{4}-\d{2}-\d{2})`)
|
||||
priorityRe = regexp.MustCompile(`[ \t]*(🔺|⏫|🔼|🔽|⏬)`)
|
||||
)
|
||||
@@ -790,6 +795,10 @@ func parseTaskFile(body string) []importTask {
|
||||
if c := checkboxRe.FindStringSubmatch(m[2]); c != nil {
|
||||
t.done, t.content = c[1] != " ", c[2]
|
||||
}
|
||||
if r := recurRe.FindStringSubmatch(t.content); r != nil {
|
||||
t.recurrence = map[string]string{"day": "daily", "week": "weekly", "month": "monthly", "year": "yearly"}[strings.ToLower(r[1])]
|
||||
t.content = recurRe.ReplaceAllString(t.content, "")
|
||||
}
|
||||
if d := dueRe.FindStringSubmatch(t.content); d != nil {
|
||||
t.due = d[1]
|
||||
t.content = dueRe.ReplaceAllString(t.content, "")
|
||||
|
||||
Reference in new issue
Block a user