Dateien nach "frontend/src" hochladen
This commit is contained in:
@@ -2,38 +2,66 @@
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { api, S } from './lib.js';
|
||||
|
||||
// Minimaler iCal-Parser (VEVENT Felder)
|
||||
function parseIcal(text) {
|
||||
// iCal-Parser – unterstützt gefaltete Zeilen, TZID, VALUE=DATE
|
||||
function parseIcal(raw) {
|
||||
// 1. Gefaltete Zeilen (RFC 5545) zusammenführen
|
||||
const text = raw.replace(/\r\n[ \t]/g, '').replace(/\r/g, '');
|
||||
|
||||
// 2. Alle Properties als Map extrahieren
|
||||
function getProps(block) {
|
||||
const props = {};
|
||||
for (const line of block.split('\n')) {
|
||||
const col = line.indexOf(':');
|
||||
if (col < 0) continue;
|
||||
const namepart = line.slice(0, col).toUpperCase();
|
||||
const val = line.slice(col + 1).trim();
|
||||
// Basisname ohne Parameter
|
||||
const name = namepart.split(';')[0];
|
||||
// TZID-Parameter extrahieren
|
||||
const tzidM = namepart.match(/TZID=([^;:]+)/);
|
||||
props[name] = { val, raw: namepart, tzid: tzidM?.[1] || null };
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
// 3. Datum parsen – Ortszeit wenn TZID gesetzt, UTC wenn Z-Suffix
|
||||
function parseDate(prop, isEnd) {
|
||||
if (!prop) return null;
|
||||
const s = prop.val;
|
||||
if (!s) return null;
|
||||
const allDay = s.length === 8 || prop.raw.includes('VALUE=DATE');
|
||||
if (allDay) {
|
||||
// DTEND bei all-day ist exklusiv → einen Tag abziehen
|
||||
const d = new Date(+s.slice(0,4), +s.slice(4,6)-1, +s.slice(6,8));
|
||||
if (isEnd) d.setDate(d.getDate() - 1);
|
||||
return { date: d, allDay: true };
|
||||
}
|
||||
const utc = s.endsWith('Z');
|
||||
const y=+s.slice(0,4), mo=+s.slice(4,6)-1, d=+s.slice(6,8);
|
||||
const h=+s.slice(9,11)||0, mi=+s.slice(11,13)||0, sec=+s.slice(13,15)||0;
|
||||
const date = utc
|
||||
? new Date(Date.UTC(y,mo,d,h,mi,sec))
|
||||
: new Date(y,mo,d,h,mi,sec); // Ortszeit des Browsers (beste Annäherung)
|
||||
return { date, allDay: false };
|
||||
}
|
||||
|
||||
const events = [];
|
||||
const blocks = text.split('BEGIN:VEVENT');
|
||||
for (let i = 1; i < blocks.length; i++) {
|
||||
const b = blocks[i];
|
||||
const get = (key) => {
|
||||
const m = b.match(new RegExp(`${key}(?:;[^:]*)?:([^\r\n]+)`, 'i'));
|
||||
return m ? m[1].trim() : '';
|
||||
};
|
||||
const parseDate = (s) => {
|
||||
if (!s) return null;
|
||||
// Remove VALUE=DATE: prefix handling
|
||||
const clean = s.replace(/^[^:]*:/,'');
|
||||
if (clean.length === 8) {
|
||||
// all-day: YYYYMMDD
|
||||
return new Date(+clean.slice(0,4), +clean.slice(4,6)-1, +clean.slice(6,8));
|
||||
}
|
||||
// datetime: YYYYMMDDTHHMMSSZ
|
||||
return new Date(
|
||||
Date.UTC(+clean.slice(0,4), +clean.slice(4,6)-1, +clean.slice(6,8),
|
||||
+clean.slice(9,11)||0, +clean.slice(11,13)||0, +clean.slice(13,15)||0)
|
||||
);
|
||||
};
|
||||
const dtstart = parseDate(get('DTSTART'));
|
||||
const dtend = parseDate(get('DTEND')) || dtstart;
|
||||
if (!dtstart || isNaN(dtstart)) continue;
|
||||
const end = blocks[i].indexOf('END:VEVENT');
|
||||
const block = end >= 0 ? blocks[i].slice(0, end) : blocks[i];
|
||||
const p = getProps(block);
|
||||
|
||||
const startObj = parseDate(p['DTSTART']);
|
||||
const endObj = parseDate(p['DTEND'], true) || startObj;
|
||||
if (!startObj || isNaN(startObj.date)) continue;
|
||||
|
||||
events.push({
|
||||
summary: get('SUMMARY') || '(kein Titel)',
|
||||
location: get('LOCATION') || '',
|
||||
dtstart, dtend,
|
||||
allDay: (get('DTSTART') || '').replace(/^[^:]*:/,'').length === 8,
|
||||
summary: (p['SUMMARY']?.val || '(kein Titel)').replace(/\\n/g,'\n').replace(/\\,/g,','),
|
||||
location: (p['LOCATION']?.val || '').replace(/\\n/g,'\n').replace(/\\,/g,','),
|
||||
dtstart: startObj.date,
|
||||
dtend: endObj.date,
|
||||
allDay: startObj.allDay,
|
||||
});
|
||||
}
|
||||
return events;
|
||||
|
||||
Reference in New Issue
Block a user