feat: refine task and countdown interactions
ci / gitleaks (push) Successful in 9s
ci / docker (push) Successful in 3m31s

This commit is contained in:
2026-09-17 09:32:22 +08:00
parent bdf2a53fa9
commit 1571c3946f
14 changed files with 388 additions and 31 deletions
+20 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'
import { applyMarkdownFormat, buildTaskRecurrencePayload, buildTaskRrule, classifyTaskForToday, defaultTaskDueAt, filterTasks, groupTaskTree, isSameTaskSortTier, mergeReorderedSubset, moveItemWithinScope, parseTaskRecurrence, parseTaskRrule, renderMarkdown, toDateTimeLocal } from './task-utils'
import { applyMarkdownFormat, buildTaskDueDraft, buildTaskRecurrencePayload, buildTaskRrule, classifyTaskForToday, defaultTaskDueAt, filterTasks, groupTaskTree, isSameTaskSortTier, mergeReorderedSubset, moveItemWithinScope, parseTaskDueDraft, parseTaskRecurrence, parseTaskRrule, renderMarkdown, toDateTimeLocal } from './task-utils'
type SearchTask = {
id: string
@@ -136,4 +136,23 @@ describe('task utilities', () => {
process.env.TZ = original
expect(toDateTimeLocal(null)).toBe('')
})
it('parses date-only and timed task deadlines without inventing a time', () => {
const original = process.env.TZ
process.env.TZ = 'Asia/Shanghai'
expect(parseTaskDueDraft('2026-09-05T15:59:00Z', false)).toEqual({ date: '2026-09-05', hasTime: false, time: '12:00' })
expect(parseTaskDueDraft('2026-09-05T12:30:00Z', true)).toEqual({ date: '2026-09-05', hasTime: true, time: '20:30' })
expect(parseTaskDueDraft(null, true)).toEqual({ date: '', hasTime: false, time: '12:00' })
process.env.TZ = original
})
it('builds date-only, timed, toggled and cleared task deadlines in local time', () => {
const original = process.env.TZ
process.env.TZ = 'Asia/Shanghai'
expect(buildTaskDueDraft({ date: '2026-09-05', hasTime: false, time: '08:15' })).toEqual({ due_at: '2026-09-05T15:59:00.000Z', due_has_time: false })
expect(buildTaskDueDraft({ date: '2026-09-05', hasTime: true, time: '08:15' })).toEqual({ due_at: '2026-09-05T00:15:00.000Z', due_has_time: true })
expect(buildTaskDueDraft({ date: '2026-09-05', hasTime: true, time: '' })).toEqual({ due_at: '2026-09-05T15:59:00.000Z', due_has_time: false })
expect(buildTaskDueDraft({ date: '', hasTime: true, time: '08:15' })).toEqual({ due_at: null, due_has_time: false })
process.env.TZ = original
})
})
+16
View File
@@ -217,6 +217,22 @@ export function defaultTaskDueAt(now = new Date()) {
return `${year}-${month}-${day}`
}
export type TaskDueDraft = { date: string; hasTime: boolean; time: string }
export function parseTaskDueDraft(value: string | null | undefined, dueHasTime: boolean): TaskDueDraft {
const local = toDateTimeLocal(value)
if (!local) return { date: '', hasTime: false, time: '12:00' }
const [date, time = '12:00'] = local.split('T')
return { date, hasTime: dueHasTime, time: dueHasTime ? time : '12:00' }
}
export function buildTaskDueDraft(draft: TaskDueDraft) {
if (!draft.date) return { due_at: null, due_has_time: false }
const hasTime = draft.hasTime && Boolean(draft.time)
const local = `${draft.date}T${hasTime ? draft.time : '23:59'}`
return { due_at: fromDateTimeLocal(local), due_has_time: hasTime }
}
export function toDateTimeLocal(value: string | null | undefined) {
if (!value) return ''
const date = new Date(value)