-
Notifications
You must be signed in to change notification settings - Fork 16
feat: Adds VTODO support #124
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -111,7 +111,8 @@ export function isValidICSContent(icsContent: string): boolean { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const jcalData = ICAL.parse(icsContent); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const comp = new ICAL.Component(jcalData); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const vevents = comp.getAllSubcomponents("vevent"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return vevents.length > 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const vtodos = comp.getAllSubcomponents("vtodo"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return vevents.length > 0 || vtodos.length > 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -405,3 +406,73 @@ export function needsUpdate( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // No differences found | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Processes a VTODO component and converts it to shift data | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param vtodo - The VTODO component from iCal.js | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @returns Shift data object or null if invalid | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export function processTodoToShift(vtodo: ICAL.Component): { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| date: Date; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| startTime: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| endTime: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| title: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| notes: string | null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isAllDay: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uid: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | null { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Get the TODO properties | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const summary = vtodo.getFirstPropertyValue("summary") || "Untitled Task"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const description = vtodo.getFirstPropertyValue("description") || null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const uid = vtodo.getFirstPropertyValue("uid") || crypto.randomUUID(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // VTODO can have DUE (due date) or DTSTART (start date) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const dueDate = vtodo.getFirstPropertyValue("due") as ICAL.Time | null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const startDate = vtodo.getFirstPropertyValue( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "dtstart" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) as ICAL.Time | null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Use due date if available, otherwise use start date | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const taskDate = dueDate || startDate; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!taskDate) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // If no date is set, skip this task | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const isAllDay = taskDate.isDate; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const jsDate = taskDate.toJSDate(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // For tasks, we'll show them as all-day items by default | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // or use specific times if they have them | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let startTime = "00:00"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let endTime = "23:59"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!isAllDay && dueDate) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // If there's a specific due time, show it as ending at that time | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const hours = jsDate.getHours().toString().padStart(2, "0"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const minutes = jsDate.getMinutes().toString().padStart(2, "0"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| endTime = `${hours}:${minutes}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (!isAllDay && startDate) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // If there's a start time but no due time, show it starting at that time | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const hours = jsDate.getHours().toString().padStart(2, "0"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const minutes = jsDate.getMinutes().toString().padStart(2, "0"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| startTime = `${hours}:${minutes}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| endTime = "23:59"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+452
to
+462
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!isAllDay && dueDate) { | |
| // If there's a specific due time, show it as ending at that time | |
| const hours = jsDate.getHours().toString().padStart(2, "0"); | |
| const minutes = jsDate.getMinutes().toString().padStart(2, "0"); | |
| endTime = `${hours}:${minutes}`; | |
| } else if (!isAllDay && startDate) { | |
| // If there's a start time but no due time, show it starting at that time | |
| const hours = jsDate.getHours().toString().padStart(2, "0"); | |
| const minutes = jsDate.getMinutes().toString().padStart(2, "0"); | |
| startTime = `${hours}:${minutes}`; | |
| endTime = "23:59"; | |
| if (!isAllDay) { | |
| if (startDate && dueDate) { | |
| // When both start and due are present, use start for startTime and due for endTime | |
| const startJsDate = startDate.toJSDate(); | |
| const startHours = startJsDate.getHours().toString().padStart(2, "0"); | |
| const startMinutes = startJsDate.getMinutes().toString().padStart(2, "0"); | |
| startTime = `${startHours}:${startMinutes}`; | |
| const dueJsDate = dueDate.toJSDate(); | |
| const endHours = dueJsDate.getHours().toString().padStart(2, "0"); | |
| const endMinutes = dueJsDate.getMinutes().toString().padStart(2, "0"); | |
| endTime = `${endHours}:${endMinutes}`; | |
| } else if (dueDate) { | |
| // If there's a specific due time but no start time, show it as ending at that time | |
| const dueJsDate = dueDate.toJSDate(); | |
| const hours = dueJsDate.getHours().toString().padStart(2, "0"); | |
| const minutes = dueJsDate.getMinutes().toString().padStart(2, "0"); | |
| endTime = `${hours}:${minutes}`; | |
| } else if (startDate) { | |
| // If there's a start time but no due time, show it starting at that time | |
| const startJsDate = startDate.toJSDate(); | |
| const hours = startJsDate.getHours().toString().padStart(2, "0"); | |
| const minutes = startJsDate.getMinutes().toString().padStart(2, "0"); | |
| startTime = `${hours}:${minutes}`; | |
| endTime = "23:59"; | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
VTODOs can have recurrence rules (RRULE) just like VEVENTs, but
processTodoToShift()doesn't handle recurring tasks. The existingexpandRecurringEvents()function works with VEVENT components specifically. Consider whether recurring VTODOs should be supported, and if so, either extendexpandRecurringEvents()to work with VTODOs or create similar expansion logic for tasks. Without this, recurring tasks will only appear once instead of generating multiple shift occurrences.