feat: Kalender-Termine als QR (iCal Europe/Berlin) und UX für Datum/Zeit
- Modus Text/WLAN/Termin; VEVENT mit TZID Europe/Berlin und VTIMEZONE - Eingabe: separates Datum und 24h-Uhrzeit; Vorschau TT/MM/JJJJ - URL-Parameter und Teilen für Terminfelder - Docker Compose: Healthcheck für Caddy-Container Made-with: Cursor
This commit is contained in:
@@ -2,8 +2,19 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
const textInput = document.getElementById('text');
|
||||
const clearTextBtn = document.getElementById('clear-text');
|
||||
const contentModeSelect = document.getElementById('content-mode');
|
||||
const sectionText = document.getElementById('section-text');
|
||||
const sectionWifi = document.getElementById('section-wifi');
|
||||
const sectionEvent = document.getElementById('section-event');
|
||||
const wifiSsidInput = document.getElementById('wifi-ssid');
|
||||
const wifiPasswordInput = document.getElementById('wifi-password');
|
||||
const eventTitleInput = document.getElementById('event-title');
|
||||
const eventStartDateInput = document.getElementById('event-start-date');
|
||||
const eventStartTimeInput = document.getElementById('event-start-time');
|
||||
const eventEndDateInput = document.getElementById('event-end-date');
|
||||
const eventEndTimeInput = document.getElementById('event-end-time');
|
||||
const eventLocationInput = document.getElementById('event-location');
|
||||
const eventDescriptionInput = document.getElementById('event-description');
|
||||
const sizeSelect = document.getElementById('size');
|
||||
const errorCorrectionSelect = document.getElementById('errorCorrection');
|
||||
const foregroundColor = document.getElementById('foreground');
|
||||
@@ -18,6 +29,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const titleElement = document.getElementById('title');
|
||||
const shareBtn = document.getElementById('share');
|
||||
const shareHint = document.getElementById('share-hint');
|
||||
const eventBerlinPreview = document.getElementById('event-berlin-preview');
|
||||
|
||||
const MAX_EVENT_TITLE = 2000;
|
||||
const MAX_EVENT_LOCATION = 1000;
|
||||
const MAX_EVENT_DESCRIPTION = 8000;
|
||||
|
||||
// Title Easter egg
|
||||
let titleClickCount = 0;
|
||||
@@ -62,13 +78,42 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
});
|
||||
|
||||
function toggleContentSections() {
|
||||
const mode = contentModeSelect.value;
|
||||
sectionText.style.display = mode === 'text' ? 'block' : 'none';
|
||||
sectionWifi.style.display = mode === 'wifi' ? 'block' : 'none';
|
||||
sectionEvent.style.display = mode === 'event' ? 'block' : 'none';
|
||||
}
|
||||
|
||||
contentModeSelect.addEventListener('change', function() {
|
||||
toggleContentSections();
|
||||
updateBerlinPreview();
|
||||
generateQRCode();
|
||||
if (this.value === 'text') {
|
||||
textInput.focus();
|
||||
} else if (this.value === 'wifi') {
|
||||
wifiSsidInput.focus();
|
||||
} else if (this.value === 'event') {
|
||||
eventTitleInput.focus();
|
||||
}
|
||||
});
|
||||
|
||||
// Set random compliment as default text and select it
|
||||
textInput.value = 'https://medisoftware.de';
|
||||
textInput.select();
|
||||
|
||||
// Show/hide clear button based on text input content
|
||||
// Show/hide clear button based on input content
|
||||
function toggleClearButton() {
|
||||
if (textInput.value.length > 0) {
|
||||
const hasText = textInput.value.length > 0;
|
||||
const hasWifi = wifiSsidInput.value.trim().length > 0 || wifiPasswordInput.value.trim().length > 0;
|
||||
const hasEvent = eventTitleInput.value.trim().length > 0 ||
|
||||
eventStartDateInput.value.length > 0 ||
|
||||
eventStartTimeInput.value.length > 0 ||
|
||||
eventEndDateInput.value.length > 0 ||
|
||||
eventEndTimeInput.value.length > 0 ||
|
||||
eventLocationInput.value.trim().length > 0 ||
|
||||
eventDescriptionInput.value.trim().length > 0;
|
||||
if (hasText || hasWifi || hasEvent) {
|
||||
clearTextBtn.style.display = 'block';
|
||||
} else {
|
||||
clearTextBtn.style.display = 'none';
|
||||
@@ -83,6 +128,16 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
textInput.value = '';
|
||||
wifiSsidInput.value = '';
|
||||
wifiPasswordInput.value = '';
|
||||
eventTitleInput.value = '';
|
||||
eventStartDateInput.value = '';
|
||||
eventStartTimeInput.value = '';
|
||||
eventEndDateInput.value = '';
|
||||
eventEndTimeInput.value = '';
|
||||
eventLocationInput.value = '';
|
||||
eventDescriptionInput.value = '';
|
||||
contentModeSelect.value = 'text';
|
||||
toggleContentSections();
|
||||
updateBerlinPreview();
|
||||
toggleClearButton();
|
||||
textInput.focus();
|
||||
generateQRCode();
|
||||
@@ -91,10 +146,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
// Update clear button visibility when text changes
|
||||
textInput.addEventListener('input', function() {
|
||||
toggleClearButton();
|
||||
// Only generate QR code if no wifi data is being entered
|
||||
const wifiSsid = wifiSsidInput.value.trim();
|
||||
const wifiPassword = wifiPasswordInput.value.trim();
|
||||
if (!wifiSsid && !wifiPassword) {
|
||||
if (contentModeSelect.value === 'text') {
|
||||
generateQRCode();
|
||||
}
|
||||
});
|
||||
@@ -109,22 +161,97 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
// Generate QR code when wifi settings change
|
||||
wifiSsidInput.addEventListener('input', function() {
|
||||
updateTextDisplay();
|
||||
if (contentModeSelect.value === 'wifi') {
|
||||
updateTextDisplay();
|
||||
}
|
||||
generateQRCode();
|
||||
toggleClearButton();
|
||||
});
|
||||
wifiPasswordInput.addEventListener('input', function() {
|
||||
updateTextDisplay();
|
||||
if (contentModeSelect.value === 'wifi') {
|
||||
updateTextDisplay();
|
||||
}
|
||||
generateQRCode();
|
||||
toggleClearButton();
|
||||
});
|
||||
|
||||
eventTitleInput.addEventListener('input', function() {
|
||||
updateBerlinPreview();
|
||||
generateQRCode();
|
||||
toggleClearButton();
|
||||
});
|
||||
function onEventDateTimeInput() {
|
||||
updateBerlinPreview();
|
||||
generateQRCode();
|
||||
toggleClearButton();
|
||||
}
|
||||
eventStartDateInput.addEventListener('input', onEventDateTimeInput);
|
||||
eventStartDateInput.addEventListener('change', onEventDateTimeInput);
|
||||
eventStartTimeInput.addEventListener('input', onEventDateTimeInput);
|
||||
eventStartTimeInput.addEventListener('change', onEventDateTimeInput);
|
||||
eventEndDateInput.addEventListener('input', onEventDateTimeInput);
|
||||
eventEndDateInput.addEventListener('change', onEventDateTimeInput);
|
||||
eventEndTimeInput.addEventListener('input', onEventDateTimeInput);
|
||||
eventEndTimeInput.addEventListener('change', onEventDateTimeInput);
|
||||
eventLocationInput.addEventListener('input', function() {
|
||||
generateQRCode();
|
||||
toggleClearButton();
|
||||
});
|
||||
eventDescriptionInput.addEventListener('input', function() {
|
||||
generateQRCode();
|
||||
toggleClearButton();
|
||||
});
|
||||
|
||||
// Download QR code as image
|
||||
downloadBtn.addEventListener('click', downloadQRCode);
|
||||
|
||||
function sanitizeUrlString(s, maxLen) {
|
||||
if (typeof s !== 'string') {
|
||||
return '';
|
||||
}
|
||||
return s.replace(/\0/g, '').slice(0, maxLen);
|
||||
}
|
||||
|
||||
function isValidDatetimeLocal(s) {
|
||||
return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(s);
|
||||
}
|
||||
|
||||
function combineDateTime(dateStr, timeStr) {
|
||||
if (!dateStr || !timeStr) {
|
||||
return '';
|
||||
}
|
||||
if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) {
|
||||
return '';
|
||||
}
|
||||
if (!/^\d{2}:\d{2}$/.test(timeStr)) {
|
||||
return '';
|
||||
}
|
||||
return dateStr + 'T' + timeStr;
|
||||
}
|
||||
|
||||
function applyCombinedToDateTimeInputs(combined, dateInput, timeInput) {
|
||||
if (!isValidDatetimeLocal(combined)) {
|
||||
return;
|
||||
}
|
||||
const parts = combined.split('T');
|
||||
dateInput.value = parts[0];
|
||||
timeInput.value = parts[1];
|
||||
}
|
||||
|
||||
// Or check for URL parameters if present
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
let explicitModeFromUrl = false;
|
||||
if (urlParams.has('mode')) {
|
||||
const m = urlParams.get('mode');
|
||||
if (m === 'text' || m === 'wifi' || m === 'event') {
|
||||
contentModeSelect.value = m;
|
||||
explicitModeFromUrl = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (urlParams.has('text')) {
|
||||
const safeText = urlParams.get('text');
|
||||
// Nur erlaubte Zeichen (Buchstaben, Zahlen, gängige URL-Zeichen)
|
||||
if (/^[\w\-.:/?&=#%+@!;,]*$/i.test(safeText)) {
|
||||
textInput.value = safeText;
|
||||
}
|
||||
@@ -141,6 +268,33 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
wifiPasswordInput.value = safePassword;
|
||||
}
|
||||
}
|
||||
|
||||
const hasEventParams = urlParams.has('eventTitle') || urlParams.has('eventStart');
|
||||
if (!explicitModeFromUrl && hasEventParams) {
|
||||
contentModeSelect.value = 'event';
|
||||
}
|
||||
if (urlParams.has('eventTitle')) {
|
||||
eventTitleInput.value = sanitizeUrlString(urlParams.get('eventTitle'), MAX_EVENT_TITLE);
|
||||
}
|
||||
if (urlParams.has('eventStart')) {
|
||||
const es = urlParams.get('eventStart');
|
||||
if (isValidDatetimeLocal(es)) {
|
||||
applyCombinedToDateTimeInputs(es, eventStartDateInput, eventStartTimeInput);
|
||||
}
|
||||
}
|
||||
if (urlParams.has('eventEnd')) {
|
||||
const ee = urlParams.get('eventEnd');
|
||||
if (isValidDatetimeLocal(ee)) {
|
||||
applyCombinedToDateTimeInputs(ee, eventEndDateInput, eventEndTimeInput);
|
||||
}
|
||||
}
|
||||
if (urlParams.has('eventLocation')) {
|
||||
eventLocationInput.value = sanitizeUrlString(urlParams.get('eventLocation'), MAX_EVENT_LOCATION);
|
||||
}
|
||||
if (urlParams.has('eventDescription')) {
|
||||
eventDescriptionInput.value = sanitizeUrlString(urlParams.get('eventDescription'), MAX_EVENT_DESCRIPTION);
|
||||
}
|
||||
|
||||
if (urlParams.has('size')) {
|
||||
const sizeValue = urlParams.get('size');
|
||||
if (["128", "256", "512", "1024"].includes(sizeValue)) {
|
||||
@@ -165,13 +319,195 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
backgroundColor.value = bgValue;
|
||||
}
|
||||
}
|
||||
|
||||
toggleContentSections();
|
||||
updateBerlinPreview();
|
||||
generateQRCode();
|
||||
|
||||
function daysInMonth(y, mo) {
|
||||
return new Date(y, mo, 0).getDate();
|
||||
}
|
||||
|
||||
function parseDatetimeLocalValue(s) {
|
||||
const m = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})$/.exec(s);
|
||||
if (!m) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
y: parseInt(m[1], 10),
|
||||
mo: parseInt(m[2], 10),
|
||||
d: parseInt(m[3], 10),
|
||||
h: parseInt(m[4], 10),
|
||||
mi: parseInt(m[5], 10)
|
||||
};
|
||||
}
|
||||
|
||||
function addHoursWall(parts, deltaH) {
|
||||
let y = parts.y;
|
||||
let mo = parts.mo;
|
||||
let d = parts.d;
|
||||
let h = parts.h + deltaH;
|
||||
let mi = parts.mi;
|
||||
while (h >= 24) {
|
||||
h -= 24;
|
||||
d += 1;
|
||||
const dim = daysInMonth(y, mo);
|
||||
if (d > dim) {
|
||||
d = 1;
|
||||
mo += 1;
|
||||
if (mo > 12) {
|
||||
mo = 1;
|
||||
y += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return { y: y, mo: mo, d: d, h: h, mi: mi };
|
||||
}
|
||||
|
||||
function wallTimeToICal(p) {
|
||||
const pad = function(n) { return String(n).padStart(2, '0'); };
|
||||
return String(p.y) + pad(p.mo) + pad(p.d) + 'T' + pad(p.h) + pad(p.mi) + '00';
|
||||
}
|
||||
|
||||
function compareWallTime(a, b) {
|
||||
return (a.y * 1e8 + a.mo * 1e6 + a.d * 1e4 + a.h * 100 + a.mi) -
|
||||
(b.y * 1e8 + b.mo * 1e6 + b.d * 1e4 + b.h * 100 + b.mi);
|
||||
}
|
||||
|
||||
function formatSlashDateTime24hBerlin(p) {
|
||||
const pad = function(n) { return String(n).padStart(2, '0'); };
|
||||
return pad(p.d) + '/' + pad(p.mo) + '/' + p.y + ', ' + pad(p.h) + ':' + pad(p.mi);
|
||||
}
|
||||
|
||||
function updateBerlinPreview() {
|
||||
if (!eventBerlinPreview) {
|
||||
return;
|
||||
}
|
||||
if (contentModeSelect.value !== 'event') {
|
||||
eventBerlinPreview.textContent = '';
|
||||
return;
|
||||
}
|
||||
const startRaw = combineDateTime(eventStartDateInput.value, eventStartTimeInput.value);
|
||||
const endD = eventEndDateInput.value;
|
||||
const endT = eventEndTimeInput.value;
|
||||
let endRaw = '';
|
||||
if (endD && endT) {
|
||||
endRaw = combineDateTime(endD, endT);
|
||||
} else if (endD || endT) {
|
||||
eventBerlinPreview.textContent =
|
||||
'Bitte geben Sie für das Ende entweder beides (Datum und Uhrzeit, 24h) ein oder lassen Sie beides leer.';
|
||||
return;
|
||||
}
|
||||
const sp = parseDatetimeLocalValue(startRaw);
|
||||
if (!sp) {
|
||||
eventBerlinPreview.textContent = '';
|
||||
return;
|
||||
}
|
||||
let endDisplay;
|
||||
if (endRaw) {
|
||||
const ep = parseDatetimeLocalValue(endRaw);
|
||||
if (!ep) {
|
||||
eventBerlinPreview.textContent = 'Beginn: ' + formatSlashDateTime24hBerlin(sp) + ' (Europe/Berlin)';
|
||||
return;
|
||||
}
|
||||
endDisplay = ep;
|
||||
} else {
|
||||
endDisplay = addHoursWall(sp, 1);
|
||||
}
|
||||
eventBerlinPreview.textContent =
|
||||
'Beginn: ' + formatSlashDateTime24hBerlin(sp) +
|
||||
' · Ende: ' + formatSlashDateTime24hBerlin(endDisplay) +
|
||||
' (Europe/Berlin)';
|
||||
}
|
||||
|
||||
function escapeICalText(str) {
|
||||
return String(str)
|
||||
.replace(/\r\n/g, '\n')
|
||||
.replace(/\r/g, '')
|
||||
.replace(/\\/g, '\\\\')
|
||||
.replace(/;/g, '\\;')
|
||||
.replace(/,/g, '\\,')
|
||||
.replace(/\n/g, '\\n');
|
||||
}
|
||||
|
||||
function formatICalUTCStamp() {
|
||||
const d = new Date();
|
||||
const pad = function(n) { return String(n).padStart(2, '0'); };
|
||||
return (
|
||||
d.getUTCFullYear() +
|
||||
pad(d.getUTCMonth() + 1) +
|
||||
pad(d.getUTCDate()) +
|
||||
'T' +
|
||||
pad(d.getUTCHours()) +
|
||||
pad(d.getUTCMinutes()) +
|
||||
pad(d.getUTCSeconds()) +
|
||||
'Z'
|
||||
);
|
||||
}
|
||||
|
||||
function newEventUid() {
|
||||
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
||||
return crypto.randomUUID() + '@qr-generator.local';
|
||||
}
|
||||
return 'evt-' + String(Date.now()) + '-' + String(Math.random()).slice(2) + '@qr-generator.local';
|
||||
}
|
||||
|
||||
function buildICalEvent(opts) {
|
||||
const summary = opts.summary;
|
||||
const dtStart = opts.dtStart;
|
||||
const dtEnd = opts.dtEnd;
|
||||
const location = opts.location;
|
||||
const description = opts.description;
|
||||
|
||||
const vtimezoneBerlin = [
|
||||
'BEGIN:VTIMEZONE',
|
||||
'TZID:Europe/Berlin',
|
||||
'BEGIN:DAYLIGHT',
|
||||
'DTSTART:19810329T020000',
|
||||
'RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU',
|
||||
'TZNAME:CEST',
|
||||
'TZOFFSETFROM:+0100',
|
||||
'TZOFFSETTO:+0200',
|
||||
'END:DAYLIGHT',
|
||||
'BEGIN:STANDARD',
|
||||
'DTSTART:19961027T030000',
|
||||
'RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU',
|
||||
'TZNAME:CET',
|
||||
'TZOFFSETFROM:+0200',
|
||||
'TZOFFSETTO:+0100',
|
||||
'END:STANDARD',
|
||||
'END:VTIMEZONE'
|
||||
].join('\r\n');
|
||||
|
||||
const lines = [
|
||||
'BEGIN:VCALENDAR',
|
||||
'VERSION:2.0',
|
||||
'PRODID:-//qr-generator//DE',
|
||||
'CALSCALE:GREGORIAN',
|
||||
vtimezoneBerlin,
|
||||
'BEGIN:VEVENT',
|
||||
'UID:' + opts.uid,
|
||||
'DTSTAMP:' + opts.dtStamp,
|
||||
'DTSTART;TZID=Europe/Berlin:' + dtStart,
|
||||
'DTEND;TZID=Europe/Berlin:' + dtEnd,
|
||||
'SUMMARY:' + escapeICalText(summary)
|
||||
];
|
||||
if (location && location.trim()) {
|
||||
lines.push('LOCATION:' + escapeICalText(location.trim()));
|
||||
}
|
||||
if (description && description.trim()) {
|
||||
lines.push('DESCRIPTION:' + escapeICalText(description.trim()));
|
||||
}
|
||||
lines.push('END:VEVENT', 'END:VCALENDAR');
|
||||
return lines.join('\r\n');
|
||||
}
|
||||
|
||||
function generateQRCode() {
|
||||
const mode = contentModeSelect.value;
|
||||
const text = textInput.value.trim();
|
||||
const wifiSsid = wifiSsidInput.value.trim();
|
||||
const wifiPassword = wifiPasswordInput.value.trim();
|
||||
const size = parseInt(sizeSelect.value);
|
||||
const size = parseInt(sizeSelect.value, 10);
|
||||
const errorCorrection = errorCorrectionSelect.value.toLowerCase();
|
||||
const fgColor = foregroundColor.value;
|
||||
const bgColor = backgroundColor.value;
|
||||
@@ -186,28 +522,110 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
errorMessage.textContent = '';
|
||||
downloadBtn.style.display = 'none';
|
||||
|
||||
// Determine what to encode
|
||||
let qrText = text;
|
||||
|
||||
// If wifi credentials are provided, generate wifi QR code
|
||||
if (wifiSsid && wifiPassword) {
|
||||
// Wifi QR code format: WIFI:S:<SSID>;T:WPA;P:<password>;;
|
||||
qrText = `WIFI:S:${escapeSpecialChars(wifiSsid)};T:WPA;P:${escapeSpecialChars(wifiPassword)};;`;
|
||||
} else if (wifiSsid && !wifiPassword) {
|
||||
// SSID only (open network)
|
||||
qrText = `WIFI:S:${escapeSpecialChars(wifiSsid)};T:nopass;;`;
|
||||
} else if (!wifiSsid && wifiPassword) {
|
||||
// Password without SSID is invalid
|
||||
errorMessage.textContent = 'Bitte geben Sie eine SSID ein, wenn Sie ein Passwort angeben';
|
||||
qrcodeCanvas.style.display = 'none';
|
||||
qrcodeImg.style.display = 'none';
|
||||
return;
|
||||
let qrText = '';
|
||||
|
||||
if (mode === 'event') {
|
||||
const title = eventTitleInput.value.trim();
|
||||
const startRaw = combineDateTime(eventStartDateInput.value, eventStartTimeInput.value);
|
||||
const endD = eventEndDateInput.value;
|
||||
const endT = eventEndTimeInput.value;
|
||||
const loc = eventLocationInput.value;
|
||||
const desc = eventDescriptionInput.value;
|
||||
|
||||
if (!title) {
|
||||
errorMessage.textContent = 'Bitte geben Sie einen Titel für den Termin ein';
|
||||
qrcodeCanvas.style.display = 'none';
|
||||
qrcodeImg.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
if (!startRaw) {
|
||||
errorMessage.textContent = 'Bitte geben Sie Datum und Uhrzeit (24h) für den Beginn ein (TT/MM/JJJJ)';
|
||||
qrcodeCanvas.style.display = 'none';
|
||||
qrcodeImg.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
if (endD || endT) {
|
||||
if (!endD || !endT) {
|
||||
errorMessage.textContent =
|
||||
'Bitte geben Sie für das Ende sowohl Datum (TT/MM/JJJJ) als auch Uhrzeit (24h) an, oder lassen Sie beides leer';
|
||||
qrcodeCanvas.style.display = 'none';
|
||||
qrcodeImg.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const startParts = parseDatetimeLocalValue(startRaw);
|
||||
if (!startParts) {
|
||||
errorMessage.textContent = 'Ungültige Startzeit (Datum und 24h-Uhrzeit prüfen)';
|
||||
qrcodeCanvas.style.display = 'none';
|
||||
qrcodeImg.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
const dtStart = wallTimeToICal(startParts);
|
||||
|
||||
let endParts;
|
||||
const endRaw = endD && endT ? combineDateTime(endD, endT) : '';
|
||||
if (endRaw) {
|
||||
endParts = parseDatetimeLocalValue(endRaw);
|
||||
if (!endParts) {
|
||||
errorMessage.textContent = 'Ungültige Endzeit';
|
||||
qrcodeCanvas.style.display = 'none';
|
||||
qrcodeImg.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
endParts = addHoursWall(startParts, 1);
|
||||
}
|
||||
|
||||
if (compareWallTime(endParts, startParts) <= 0) {
|
||||
errorMessage.textContent = 'Die Endzeit muss nach dem Beginn liegen (Ortszeit Europe/Berlin)';
|
||||
qrcodeCanvas.style.display = 'none';
|
||||
qrcodeImg.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
const dtEndStr = wallTimeToICal(endParts);
|
||||
|
||||
qrText = buildICalEvent({
|
||||
uid: newEventUid(),
|
||||
dtStamp: formatICalUTCStamp(),
|
||||
summary: title.slice(0, MAX_EVENT_TITLE),
|
||||
dtStart: dtStart,
|
||||
dtEnd: dtEndStr,
|
||||
location: loc.slice(0, MAX_EVENT_LOCATION),
|
||||
description: desc.slice(0, MAX_EVENT_DESCRIPTION)
|
||||
});
|
||||
|
||||
textInput.value = qrText;
|
||||
toggleClearButton();
|
||||
} else if (mode === 'wifi') {
|
||||
if (wifiSsid && wifiPassword) {
|
||||
qrText = 'WIFI:S:' + escapeSpecialChars(wifiSsid) + ';T:WPA;P:' + escapeSpecialChars(wifiPassword) + ';;';
|
||||
} else if (wifiSsid && !wifiPassword) {
|
||||
qrText = 'WIFI:S:' + escapeSpecialChars(wifiSsid) + ';T:nopass;;';
|
||||
} else if (!wifiSsid && wifiPassword) {
|
||||
errorMessage.textContent = 'Bitte geben Sie eine SSID ein, wenn Sie ein Passwort angeben';
|
||||
qrcodeCanvas.style.display = 'none';
|
||||
qrcodeImg.style.display = 'none';
|
||||
return;
|
||||
} else {
|
||||
qrText = text;
|
||||
}
|
||||
} else {
|
||||
qrText = text;
|
||||
}
|
||||
|
||||
// Validate input
|
||||
if (qrText === '') {
|
||||
errorMessage.textContent = 'Bitte geben Sie Text/URL oder Wifi-Daten ein';
|
||||
// Hide both canvas and image
|
||||
if (mode === 'wifi') {
|
||||
errorMessage.textContent = 'Bitte geben Sie Text/URL oder WLAN-Daten ein';
|
||||
} else if (mode === 'event') {
|
||||
errorMessage.textContent = 'Termin konnte nicht erzeugt werden';
|
||||
} else {
|
||||
errorMessage.textContent = 'Bitte geben Sie Text oder eine URL ein';
|
||||
}
|
||||
qrcodeCanvas.style.display = 'none';
|
||||
qrcodeImg.style.display = 'none';
|
||||
return;
|
||||
@@ -270,34 +688,29 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
downloadBtn.textContent = 'Downloaded!';
|
||||
|
||||
// Reset button after a short delay
|
||||
setTimeout(() => {
|
||||
setTimeout(function() {
|
||||
downloadBtn.textContent = originalText;
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// Function to update the text display with current QR code content
|
||||
// Function to update the text display with current QR code content (WLAN)
|
||||
function updateTextDisplay() {
|
||||
const wifiSsid = wifiSsidInput.value.trim();
|
||||
const wifiPassword = wifiPasswordInput.value.trim();
|
||||
|
||||
|
||||
if (wifiSsid && wifiPassword) {
|
||||
// Show wifi QR code format
|
||||
textInput.value = `WIFI:S:${escapeSpecialChars(wifiSsid)};T:WPA;P:${escapeSpecialChars(wifiPassword)};;`;
|
||||
textInput.value = 'WIFI:S:' + escapeSpecialChars(wifiSsid) + ';T:WPA;P:' + escapeSpecialChars(wifiPassword) + ';;';
|
||||
} else if (wifiSsid && !wifiPassword) {
|
||||
// Show open network format
|
||||
textInput.value = `WIFI:S:${escapeSpecialChars(wifiSsid)};T:nopass;;`;
|
||||
} else {
|
||||
// If no wifi data, keep original text input value
|
||||
// Don't change it here to avoid overwriting user input
|
||||
textInput.value = 'WIFI:S:' + escapeSpecialChars(wifiSsid) + ';T:nopass;;';
|
||||
}
|
||||
|
||||
// Update clear button visibility
|
||||
|
||||
toggleClearButton();
|
||||
}
|
||||
|
||||
function updateShareHint() {
|
||||
const mode = contentModeSelect.value;
|
||||
const wifiPassword = wifiPasswordInput.value.trim();
|
||||
if (wifiPassword) {
|
||||
if (mode === 'wifi' && wifiPassword) {
|
||||
shareHint.style.display = 'block';
|
||||
} else {
|
||||
shareHint.style.display = 'none';
|
||||
@@ -307,11 +720,12 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
wifiPasswordInput.addEventListener('input', updateShareHint);
|
||||
wifiSsidInput.addEventListener('input', updateShareHint);
|
||||
textInput.addEventListener('input', updateShareHint);
|
||||
// Initial anzeigen, falls Passwort schon gesetzt
|
||||
contentModeSelect.addEventListener('change', updateShareHint);
|
||||
updateShareHint();
|
||||
|
||||
shareBtn.addEventListener('click', function() {
|
||||
const params = new URLSearchParams();
|
||||
const mode = contentModeSelect.value;
|
||||
const text = textInput.value.trim();
|
||||
const wifiSsid = wifiSsidInput.value.trim();
|
||||
const wifiPassword = wifiPasswordInput.value.trim();
|
||||
@@ -320,23 +734,59 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const fgColor = foregroundColor.value;
|
||||
const bgColor = backgroundColor.value;
|
||||
|
||||
// Entscheide, was kodiert werden soll
|
||||
if (wifiSsid) params.set('ssid', wifiSsid);
|
||||
if (wifiPassword) params.set('password', wifiPassword);
|
||||
if (!wifiSsid && !wifiPassword && text) params.set('text', text);
|
||||
if (size !== '256') params.set('size', size);
|
||||
if (errorCorrection !== 'M') params.set('errorCorrection', errorCorrection);
|
||||
if (fgColor !== '#000000') params.set('foreground', fgColor);
|
||||
if (bgColor !== '#ffffff') params.set('background', bgColor);
|
||||
params.set('mode', mode);
|
||||
|
||||
if (mode === 'event') {
|
||||
if (eventTitleInput.value.trim()) {
|
||||
params.set('eventTitle', eventTitleInput.value.trim());
|
||||
}
|
||||
const shareStart = combineDateTime(eventStartDateInput.value, eventStartTimeInput.value);
|
||||
const shareEnd = combineDateTime(eventEndDateInput.value, eventEndTimeInput.value);
|
||||
if (shareStart) {
|
||||
params.set('eventStart', shareStart);
|
||||
}
|
||||
if (shareEnd) {
|
||||
params.set('eventEnd', shareEnd);
|
||||
}
|
||||
if (eventLocationInput.value.trim()) {
|
||||
params.set('eventLocation', eventLocationInput.value.trim());
|
||||
}
|
||||
if (eventDescriptionInput.value.trim()) {
|
||||
params.set('eventDescription', eventDescriptionInput.value.trim());
|
||||
}
|
||||
} else if (mode === 'wifi') {
|
||||
if (wifiSsid) {
|
||||
params.set('ssid', wifiSsid);
|
||||
}
|
||||
if (wifiPassword) {
|
||||
params.set('password', wifiPassword);
|
||||
}
|
||||
} else {
|
||||
if (text) {
|
||||
params.set('text', text);
|
||||
}
|
||||
}
|
||||
|
||||
if (size !== '256') {
|
||||
params.set('size', size);
|
||||
}
|
||||
if (errorCorrection !== 'M') {
|
||||
params.set('errorCorrection', errorCorrection);
|
||||
}
|
||||
if (fgColor !== '#000000') {
|
||||
params.set('foreground', fgColor);
|
||||
}
|
||||
if (bgColor !== '#ffffff') {
|
||||
params.set('background', bgColor);
|
||||
}
|
||||
|
||||
const url = window.location.origin + window.location.pathname + '?' + params.toString();
|
||||
// In Zwischenablage kopieren
|
||||
navigator.clipboard.writeText(url).then(() => {
|
||||
navigator.clipboard.writeText(url).then(function() {
|
||||
const original = shareBtn.textContent;
|
||||
shareBtn.textContent = 'Link kopiert!';
|
||||
setTimeout(() => { shareBtn.textContent = original; }, 2000);
|
||||
}, () => {
|
||||
setTimeout(function() { shareBtn.textContent = original; }, 2000);
|
||||
}, function() {
|
||||
alert('Konnte Link nicht kopieren.');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user