ultisuite-client/lib/agenda/agenda-ical-parser.ts
R3D347HR4Y ad1370ea7e
Some checks are pending
E2E / Playwright e2e (push) Waiting to run
feat: enhance configuration and add new demo layouts
- Introduced turbopack alias for canvas in next.config.mjs.
- Updated package.json scripts for development and branding tasks.
- Added new dependencies for Tiptap extensions.
- Implemented new demo layouts for agenda, contacts, drive, and mail applications.
- Enhanced globals.css for improved theming and splash screen animations.
- Added OAuth callback handling for drive mounts.
- Updated layout components to integrate new demo shells and improve structure.
2026-06-12 19:10:24 +02:00

123 lines
3.0 KiB
TypeScript

import { parseICSDate } from "@/lib/agenda/agenda-date"
import type { AgendaApiEvent } from "@/lib/agenda/agenda-types"
function unfoldIcsLines(raw: string): string[] {
const physical = raw.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n")
const lines: string[] = []
for (const line of physical) {
if ((line.startsWith(" ") || line.startsWith("\t")) && lines.length > 0) {
lines[lines.length - 1] += line.slice(1)
} else {
lines.push(line)
}
}
return lines
}
function parseIcsProperty(line: string): { name: string; value: string } | null {
const idx = line.indexOf(":")
if (idx <= 0) return null
const rawName = line.slice(0, idx)
const value = line.slice(idx + 1).trim()
const name = rawName.split(";")[0]?.toUpperCase() ?? ""
if (!name) return null
return { name, value }
}
function isAllDayProperty(rawName: string, value: string): boolean {
if (/VALUE=DATE/i.test(rawName)) return true
return /^\d{8}$/.test(value)
}
function extractVEventBlocks(lines: string[]): string[][] {
const blocks: string[][] = []
let current: string[] | null = null
for (const line of lines) {
const upper = line.trim().toUpperCase()
if (upper === "BEGIN:VEVENT") {
current = []
continue
}
if (upper === "END:VEVENT") {
if (current) blocks.push(current)
current = null
continue
}
if (current) current.push(line)
}
return blocks
}
function parseVEventBlock(lines: string[], index: number): AgendaApiEvent | null {
let uid = ""
let summary = ""
let description = ""
let location = ""
let start = ""
let end = ""
let allDay = false
for (const line of lines) {
const idx = line.indexOf(":")
if (idx <= 0) continue
const rawName = line.slice(0, idx)
const value = line.slice(idx + 1).trim()
const prop = parseIcsProperty(line)
if (!prop) continue
switch (prop.name) {
case "UID":
uid = value
break
case "SUMMARY":
summary = value
break
case "DESCRIPTION":
description = value.replace(/\\n/g, "\n").replace(/\\,/g, ",")
break
case "LOCATION":
location = value.replace(/\\,/g, ",")
break
case "DTSTART":
start = value
allDay = isAllDayProperty(rawName, value)
break
case "DTEND":
end = value
break
default:
break
}
}
if (!start) return null
if (!uid) uid = `ext-${index}-${start}`
return {
uid,
summary: summary || "(Sans titre)",
description,
location,
start,
end: end || start,
all_day: allDay,
path: uid,
}
}
/** Parse un flux ICS en événements API agenda. */
export function parseIcsFeed(raw: string): AgendaApiEvent[] {
const lines = unfoldIcsLines(raw)
const blocks = extractVEventBlocks(lines)
const events: AgendaApiEvent[] = []
blocks.forEach((block, index) => {
const event = parseVEventBlock(block, index)
if (!event) return
if (!parseICSDate(event.start)) return
events.push(event)
})
return events
}