Some checks are pending
E2E / Playwright e2e (push) Waiting to run
- 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.
123 lines
3.0 KiB
TypeScript
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
|
|
}
|