Dateien nach "frontend/src" hochladen
This commit is contained in:
@@ -2,38 +2,66 @@
|
|||||||
import { useState, useEffect, useMemo } from 'react';
|
import { useState, useEffect, useMemo } from 'react';
|
||||||
import { api, S } from './lib.js';
|
import { api, S } from './lib.js';
|
||||||
|
|
||||||
// Minimaler iCal-Parser (VEVENT Felder)
|
// iCal-Parser – unterstützt gefaltete Zeilen, TZID, VALUE=DATE
|
||||||
function parseIcal(text) {
|
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 events = [];
|
||||||
const blocks = text.split('BEGIN:VEVENT');
|
const blocks = text.split('BEGIN:VEVENT');
|
||||||
for (let i = 1; i < blocks.length; i++) {
|
for (let i = 1; i < blocks.length; i++) {
|
||||||
const b = blocks[i];
|
const end = blocks[i].indexOf('END:VEVENT');
|
||||||
const get = (key) => {
|
const block = end >= 0 ? blocks[i].slice(0, end) : blocks[i];
|
||||||
const m = b.match(new RegExp(`${key}(?:;[^:]*)?:([^\r\n]+)`, 'i'));
|
const p = getProps(block);
|
||||||
return m ? m[1].trim() : '';
|
|
||||||
};
|
const startObj = parseDate(p['DTSTART']);
|
||||||
const parseDate = (s) => {
|
const endObj = parseDate(p['DTEND'], true) || startObj;
|
||||||
if (!s) return null;
|
if (!startObj || isNaN(startObj.date)) continue;
|
||||||
// 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;
|
|
||||||
events.push({
|
events.push({
|
||||||
summary: get('SUMMARY') || '(kein Titel)',
|
summary: (p['SUMMARY']?.val || '(kein Titel)').replace(/\\n/g,'\n').replace(/\\,/g,','),
|
||||||
location: get('LOCATION') || '',
|
location: (p['LOCATION']?.val || '').replace(/\\n/g,'\n').replace(/\\,/g,','),
|
||||||
dtstart, dtend,
|
dtstart: startObj.date,
|
||||||
allDay: (get('DTSTART') || '').replace(/^[^:]*:/,'').length === 8,
|
dtend: endObj.date,
|
||||||
|
allDay: startObj.allDay,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return events;
|
return events;
|
||||||
|
|||||||
Reference in New Issue
Block a user