Compare commits

..

8 Commits

Author SHA1 Message Date
elpatron 4e257a09f0 update: Footer-Kontakt auf Mailto-Link umstellen
Verlinkt den Namen in der Credit-Zeile direkt mit der Kontaktadresse per mailto, damit der Kontakt aus dem Footer unmittelbar erreichbar ist.

Made-with: Cursor
2026-04-25 14:18:03 +02:00
elpatron b0eaaf7694 chore: ZIP-Artefakt entfernen und Ignore-Regel ergänzen
Entfernt die versehentlich versionierte ZIP-Datei aus dem Repository und ergänzt eine generelle Ignore-Regel für ZIP-Dateien. Aktualisiert zusätzlich die Footer-Referenz auf Proxmox.

Made-with: Cursor
2026-04-25 14:16:44 +02:00
elpatron 30872ad780 update: vCard placeholders updated for organization and position fields
Changed the placeholder text in the vCard input fields to reflect new example values, enhancing user experience and clarity in the form.
2026-04-25 14:12:32 +02:00
elpatron 11d4586ea7 fix: vCard-Mode bei allen vCard-URL-Parametern erkennen
Erweitert die Auto-Erkennung für vCard-Links, damit auch Links mit nur Titel, Website, Adresse oder Notiz automatisch in den vCard-Modus wechseln. So funktionieren geteilte vCard-URLs konsistent ohne manuelle Moduswahl.

Made-with: Cursor
2026-04-25 14:09:36 +02:00
elpatron 319ee44aae ux: vCard-Eingabe mit Platzhaltern und Live-Vorschau verbessern
Ergänzt aussagekräftige Beispiel-Platzhalter im vCard-Formular und zeigt den generierten vCard-Text als Live-Vorschau im vCard-Modus an. Das erleichtert die Eingabe und macht das spätere QR-Ergebnis sofort nachvollziehbar.

Made-with: Cursor
2026-04-25 14:08:49 +02:00
elpatron 0ca6e891cc feat: vCard-QR-Modus mit Kontaktformular ergänzen
Erweitert den Generator um einen vCard-Modus inklusive eigener Eingabemaske, Validierung und vCard-3.0-Erzeugung, damit Kontakte direkt per QR geteilt werden können. Ergänzt außerdem Share-URL-Parameter und den Info-Text für den neuen Modus.

Made-with: Cursor
2026-04-25 14:07:43 +02:00
elpatron 7790fe6dda Plausible Analytics einbinden und CSP/Texte anpassen
Made-with: Cursor
2026-04-16 12:52:11 +02:00
elpatron 8dac716c28 docs: README um Termin-URL-Parameter und Beispiel ergänzen
- mode und event* Parameter dokumentiert
- Beispiel-Link für Kalender-QR
- Teilen-Hinweis um Termin-Daten erweitert

Made-with: Cursor
2026-04-16 09:00:57 +02:00
4 changed files with 313 additions and 12 deletions
+1
View File
@@ -0,0 +1 @@
*.zip
+16 -5
View File
@@ -7,7 +7,7 @@ Dieses Projekt basiert auf dem Code von https://qr.alster.space/. Er wurde um ve
## Features
- Vollständig clientseitig (keine Server-Kommunikation)
- URL-Parameter für alle Einstellungen
- URL-Parameter für alle Einstellungen (inkl. Modus Text/WLAN/Termin)
- Anpassbare Größen und Farben
- Verschiedene Fehlerkorrektur-Level
- Download-Funktion
@@ -69,9 +69,15 @@ Die App bietet spezielle Eingabefelder für WiFi-Daten:
Die Anwendung unterstützt folgende URL-Parameter:
- `text` - Text oder URL für den QR-Code
- `ssid` - WiFi SSID
- `password` - WiFi Passwort
- `mode` - Inhaltstyp: `text`, `wifi` oder `event` (Termin/Kalender). Ohne `mode` wird bei vorhandenen Termin-Feldern automatisch der Termin-Modus gewählt.
- `text` - Text oder URL für den QR-Code (Modus Text)
- `ssid` - WiFi SSID (Modus WLAN)
- `password` - WiFi Passwort (Modus WLAN)
- `eventTitle` - Titel des Termins (Modus `event`)
- `eventStart` - Beginn im Format `YYYY-MM-DDTHH:mm` (lokale Ortszeit Europe/Berlin, z.B. `2026-04-16T14:30`)
- `eventEnd` - Ende (optional), gleiches Format wie `eventStart`
- `eventLocation` - Ort (optional)
- `eventDescription` - Beschreibung (optional)
- `size` - Größe (128, 256, 512, 1024)
- `errorCorrection` - Fehlerkorrektur (L, M, Q, H)
- `foreground` - Vordergrundfarbe (Hex-Code)
@@ -92,6 +98,11 @@ http://localhost:8080/?ssid=MeinWLAN&password=MeinPasswort123
http://localhost:8080/?ssid=OffenesWLAN
```
**Termin (Kalender, iCal im QR):**
```
http://localhost:8080/?mode=event&eventTitle=Team-Meeting&eventStart=2026-04-16T14:30&eventEnd=2026-04-16T15:30
```
**Achtung:** Das WLAN-Passwort ist im Link im Klartext sichtbar!
## WiFi QR-Code Format
@@ -133,7 +144,7 @@ Das Smartphone erkennt automatisch, dass es sich um WiFi-Daten handelt und biete
### Teilen-Funktion
Mit dem Button "Teilen" kann ein Link mit allen aktuellen Einstellungen (inkl. WiFi-Daten) in die Zwischenablage kopiert werden. Dieser Link kann weitergegeben werden und öffnet die App direkt mit den gewählten Einstellungen.
Mit dem Button "Teilen" kann ein Link mit allen aktuellen Einstellungen (inkl. WiFi- oder Termin-Daten) in die Zwischenablage kopiert werden. Dieser Link kann weitergegeben werden und öffnet die App direkt mit den gewählten Einstellungen.
**Achtung:** Wenn ein WLAN-Passwort eingegeben ist, wird dieses im Link im Klartext übertragen!
+74 -5
View File
@@ -3,9 +3,10 @@
<html lang="de"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; object-src 'none';">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' https://plausible.elpatron.me; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' https://plausible.elpatron.me; object-src 'none';">
<title>QR ohne Schnickschnack</title>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><rect width='32' height='32' fill='white' rx='6' ry='6'/><rect x='4' y='4' width='8' height='8' fill='black'/><rect x='20' y='4' width='8' height='8' fill='black'/><rect x='4' y='20' width='8' height='8' fill='black'/><rect x='14' y='14' width='4' height='4' fill='black'/></svg>">
<script defer data-domain="qr.elpatron.me" src="https://plausible.elpatron.me/js/script.js"></script>
<script src="./assets/qrious.min.js"></script>
<script src="./main.js"></script>
<!--
@@ -347,6 +348,26 @@
color: #6b7280;
margin-top: 4px;
}
.vcard-preview-label {
margin: 4px 0 6px 0;
color: #374151;
font-weight: 500;
}
.vcard-preview {
margin: 0 0 15px 0;
padding: 10px;
border: 1px solid #e5e7eb;
border-radius: 6px;
background: #f9fafb;
color: #374151;
font-size: 12px;
line-height: 1.45;
white-space: pre-wrap;
word-break: break-word;
min-height: 80px;
}
</style>
</head>
<body>
@@ -360,6 +381,7 @@
<option value="text">Text oder URL</option>
<option value="wifi">WLAN</option>
<option value="event">Termin (Kalender)</option>
<option value="vcard">vCard (Kontakt)</option>
</select>
<div id="section-text" class="content-section">
@@ -433,6 +455,53 @@
<textarea id="event-description" placeholder="" rows="3"></textarea>
</div>
<div id="section-vcard" class="content-section" style="display: none;">
<div class="options-row">
<div class="options-col">
<label for="vcard-first-name">Vorname:</label>
<input type="text" id="vcard-first-name" placeholder="Max" autocomplete="off">
</div>
<div class="options-col">
<label for="vcard-last-name">Nachname:</label>
<input type="text" id="vcard-last-name" placeholder="Mustermann" autocomplete="off">
</div>
</div>
<div class="options-row">
<div class="options-col">
<label for="vcard-org">Organisation:</label>
<input type="text" id="vcard-org" placeholder="Knorrlabs Inc." autocomplete="off">
</div>
<div class="options-col">
<label for="vcard-title">Position (optional):</label>
<input type="text" id="vcard-title" placeholder="Master Of Desaster" autocomplete="off">
</div>
</div>
<div class="options-row">
<div class="options-col">
<label for="vcard-phone">Telefon (optional):</label>
<input type="tel" id="vcard-phone" placeholder="+49 30 1234567" autocomplete="off">
</div>
<div class="options-col">
<label for="vcard-email">E-Mail (optional):</label>
<input type="email" id="vcard-email" placeholder="max@beispiel.de" autocomplete="off">
</div>
</div>
<label for="vcard-website">Webseite (optional):</label>
<input type="url" id="vcard-website" placeholder="https://beispiel.de" autocomplete="off">
<label for="vcard-address">Adresse (optional):</label>
<textarea id="vcard-address" placeholder="Musterstraße 1, 10115 Berlin" rows="2"></textarea>
<label for="vcard-note">Notiz (optional):</label>
<textarea id="vcard-note" placeholder="Erreichbar Mo-Fr 9-17 Uhr" rows="2"></textarea>
<p class="vcard-preview-label">vCard-Vorschau:</p>
<pre id="vcard-preview" class="vcard-preview" aria-live="polite">Bitte mindestens Vorname, Nachname oder Organisation eingeben.</pre>
</div>
<div class="options-row">
<div class="options-col">
<label for="size">QR Code Gr&ouml;sse:</label>
@@ -496,7 +565,7 @@
<p>Not today.</p>
<p>Rather than bemoan the fact that there's no easy way to get QR ohne Schnickschnak without the low-key parasitic monetization, I found a library (qrious) and put together just enough Javascript code to generate QR codes in the browser. No ads, no trackers, no sending your data to someone else's server for who-knows-what-reason.</p>
<p>Rather than bemoan the fact that there's no easy way to get QR ohne Schnickschnak without the low-key parasitic monetization, I found a library (qrious) and put together just enough Javascript code to generate QR codes in the browser. No ads; page views are counted with self-hosted Plausible (privacy-friendly, no cookies). Your QR content is not sent anywhere.</p>
<p>Go ahead and inspect the page source. Save it to your computer, copy it, remix it, whatever you want.</p>
@@ -507,13 +576,13 @@
<p>Be excellent to each other.</p>
<h3>Modi</h3>
<p>Unter <strong>QR-Inhalt</strong> wählst du, was kodiert wird: freier Text oder URL, WLAN-Zugangsdaten (WIFI-QR) oder einen <strong>Kalendertermin</strong>. Im Modus „Termin“ erzeugt der QR-Code einen Standard-iCalendar-Eintrag (VEVENT), den viele Smartphones beim Scannen in die Kalender-App übernehmen. Datumseingabe als <strong>TT/MM/JJJJ</strong>, Uhrzeit im <strong>24-Stunden-Format</strong>; die Zeitzone ist <strong>Europe/Berlin</strong>. Es wird nichts an einen Server gesendet.</p>
<p>Unter <strong>QR-Inhalt</strong> wählst du, was kodiert wird: freier Text oder URL, WLAN-Zugangsdaten (WIFI-QR), ein <strong>Kalendertermin</strong> oder eine <strong>vCard</strong>. Im Modus „Termin“ erzeugt der QR-Code einen Standard-iCalendar-Eintrag (VEVENT), den viele Smartphones beim Scannen in die Kalender-App übernehmen. Datumseingabe als <strong>TT/MM/JJJJ</strong>, Uhrzeit im <strong>24-Stunden-Format</strong>; die Zeitzone ist <strong>Europe/Berlin</strong>. Im Modus „vCard“ wird ein Kontakt im vCard-Format (3.0) erzeugt, den viele Geräte direkt als Kontakt speichern. Deine Eingaben für den QR-Code werden nicht an einen Server gesendet.</p>
</div>
</div>
<div class="footer">
Dieser QR-Code-Generator funktioniert vollständig in deinem Browser. Es werden keine Daten an einen Server gesendet. <i>Quellcode und README bei <a href="https://gitea.elpatron.me/elpatron/QR-Code-Generator" target="_blank">Gitea</a></i>.
<p class="credit">Made with ❤️ and 🍪 by Markus. Self hosted on <a href="https://unraid.net" target="_blank">Unraid</a> for <a href="https://medisoftware.de" target="_blank">medisoftware</a>. Credits: <a href="https://qr.alster.space/" target="_blank">alsternerd</a></p>
Dieser QR-Code-Generator funktioniert vollständig in deinem Browser; QR-Inhalte werden nicht hochgeladen. Seitenaufrufe werden mit selbst gehostetem Plausible aggregiert (ohne Cookies). <i>Quellcode und README bei <a href="https://gitea.elpatron.me/elpatron/QR-Code-Generator" target="_blank">Gitea</a></i>.
<p class="credit">Made with ❤️ and 🍪 by <a href="mailto:elpatron+qr@mailbox.org">Markus F.J. Busche</a>. Self hosted on <a href="https://www.proxmox.com/" target="_blank">Proxmox</a>. Credits: <a href="https://qr.alster.space/" target="_blank">alsternerd</a></p>
</div>
</body></html>
+222 -2
View File
@@ -6,6 +6,7 @@ document.addEventListener('DOMContentLoaded', function() {
const sectionText = document.getElementById('section-text');
const sectionWifi = document.getElementById('section-wifi');
const sectionEvent = document.getElementById('section-event');
const sectionVcard = document.getElementById('section-vcard');
const wifiSsidInput = document.getElementById('wifi-ssid');
const wifiPasswordInput = document.getElementById('wifi-password');
const eventTitleInput = document.getElementById('event-title');
@@ -17,6 +18,15 @@ document.addEventListener('DOMContentLoaded', function() {
const eventEndMinuteInput = document.getElementById('event-end-minute');
const eventLocationInput = document.getElementById('event-location');
const eventDescriptionInput = document.getElementById('event-description');
const vcardFirstNameInput = document.getElementById('vcard-first-name');
const vcardLastNameInput = document.getElementById('vcard-last-name');
const vcardOrgInput = document.getElementById('vcard-org');
const vcardTitleInput = document.getElementById('vcard-title');
const vcardPhoneInput = document.getElementById('vcard-phone');
const vcardEmailInput = document.getElementById('vcard-email');
const vcardWebsiteInput = document.getElementById('vcard-website');
const vcardAddressInput = document.getElementById('vcard-address');
const vcardNoteInput = document.getElementById('vcard-note');
const sizeSelect = document.getElementById('size');
const errorCorrectionSelect = document.getElementById('errorCorrection');
const foregroundColor = document.getElementById('foreground');
@@ -32,10 +42,12 @@ document.addEventListener('DOMContentLoaded', function() {
const shareBtn = document.getElementById('share');
const shareHint = document.getElementById('share-hint');
const eventBerlinPreview = document.getElementById('event-berlin-preview');
const vcardPreview = document.getElementById('vcard-preview');
const MAX_EVENT_TITLE = 2000;
const MAX_EVENT_LOCATION = 1000;
const MAX_EVENT_DESCRIPTION = 8000;
const MAX_VCARD_FIELD = 500;
// Title Easter egg
let titleClickCount = 0;
@@ -85,6 +97,7 @@ document.addEventListener('DOMContentLoaded', function() {
sectionText.style.display = mode === 'text' ? 'block' : 'none';
sectionWifi.style.display = mode === 'wifi' ? 'block' : 'none';
sectionEvent.style.display = mode === 'event' ? 'block' : 'none';
sectionVcard.style.display = mode === 'vcard' ? 'block' : 'none';
}
contentModeSelect.addEventListener('change', function() {
@@ -97,6 +110,8 @@ document.addEventListener('DOMContentLoaded', function() {
wifiSsidInput.focus();
} else if (this.value === 'event') {
eventTitleInput.focus();
} else if (this.value === 'vcard') {
vcardFirstNameInput.focus();
}
});
@@ -117,7 +132,16 @@ document.addEventListener('DOMContentLoaded', function() {
eventEndMinuteInput.value !== '' ||
eventLocationInput.value.trim().length > 0 ||
eventDescriptionInput.value.trim().length > 0;
if (hasText || hasWifi || hasEvent) {
const hasVCard = vcardFirstNameInput.value.trim().length > 0 ||
vcardLastNameInput.value.trim().length > 0 ||
vcardOrgInput.value.trim().length > 0 ||
vcardTitleInput.value.trim().length > 0 ||
vcardPhoneInput.value.trim().length > 0 ||
vcardEmailInput.value.trim().length > 0 ||
vcardWebsiteInput.value.trim().length > 0 ||
vcardAddressInput.value.trim().length > 0 ||
vcardNoteInput.value.trim().length > 0;
if (hasText || hasWifi || hasEvent || hasVCard) {
clearTextBtn.style.display = 'block';
} else {
clearTextBtn.style.display = 'none';
@@ -141,6 +165,15 @@ document.addEventListener('DOMContentLoaded', function() {
eventEndMinuteInput.value = '';
eventLocationInput.value = '';
eventDescriptionInput.value = '';
vcardFirstNameInput.value = '';
vcardLastNameInput.value = '';
vcardOrgInput.value = '';
vcardTitleInput.value = '';
vcardPhoneInput.value = '';
vcardEmailInput.value = '';
vcardWebsiteInput.value = '';
vcardAddressInput.value = '';
vcardNoteInput.value = '';
contentModeSelect.value = 'text';
toggleContentSections();
updateBerlinPreview();
@@ -230,6 +263,19 @@ document.addEventListener('DOMContentLoaded', function() {
generateQRCode();
toggleClearButton();
});
function onVcardInput() {
generateQRCode();
toggleClearButton();
}
vcardFirstNameInput.addEventListener('input', onVcardInput);
vcardLastNameInput.addEventListener('input', onVcardInput);
vcardOrgInput.addEventListener('input', onVcardInput);
vcardTitleInput.addEventListener('input', onVcardInput);
vcardPhoneInput.addEventListener('input', onVcardInput);
vcardEmailInput.addEventListener('input', onVcardInput);
vcardWebsiteInput.addEventListener('input', onVcardInput);
vcardAddressInput.addEventListener('input', onVcardInput);
vcardNoteInput.addEventListener('input', onVcardInput);
// Download QR code as image
downloadBtn.addEventListener('click', downloadQRCode);
@@ -286,7 +332,7 @@ document.addEventListener('DOMContentLoaded', function() {
let explicitModeFromUrl = false;
if (urlParams.has('mode')) {
const m = urlParams.get('mode');
if (m === 'text' || m === 'wifi' || m === 'event') {
if (m === 'text' || m === 'wifi' || m === 'event' || m === 'vcard') {
contentModeSelect.value = m;
explicitModeFromUrl = true;
}
@@ -336,6 +382,45 @@ document.addEventListener('DOMContentLoaded', function() {
if (urlParams.has('eventDescription')) {
eventDescriptionInput.value = sanitizeUrlString(urlParams.get('eventDescription'), MAX_EVENT_DESCRIPTION);
}
const hasVcardParams = urlParams.has('vcardFirstName') ||
urlParams.has('vcardLastName') ||
urlParams.has('vcardOrg') ||
urlParams.has('vcardTitle') ||
urlParams.has('vcardPhone') ||
urlParams.has('vcardEmail') ||
urlParams.has('vcardWebsite') ||
urlParams.has('vcardAddress') ||
urlParams.has('vcardNote');
if (!explicitModeFromUrl && !hasEventParams && hasVcardParams) {
contentModeSelect.value = 'vcard';
}
if (urlParams.has('vcardFirstName')) {
vcardFirstNameInput.value = sanitizeUrlString(urlParams.get('vcardFirstName'), MAX_VCARD_FIELD);
}
if (urlParams.has('vcardLastName')) {
vcardLastNameInput.value = sanitizeUrlString(urlParams.get('vcardLastName'), MAX_VCARD_FIELD);
}
if (urlParams.has('vcardOrg')) {
vcardOrgInput.value = sanitizeUrlString(urlParams.get('vcardOrg'), MAX_VCARD_FIELD);
}
if (urlParams.has('vcardTitle')) {
vcardTitleInput.value = sanitizeUrlString(urlParams.get('vcardTitle'), MAX_VCARD_FIELD);
}
if (urlParams.has('vcardPhone')) {
vcardPhoneInput.value = sanitizeUrlString(urlParams.get('vcardPhone'), MAX_VCARD_FIELD);
}
if (urlParams.has('vcardEmail')) {
vcardEmailInput.value = sanitizeUrlString(urlParams.get('vcardEmail'), MAX_VCARD_FIELD);
}
if (urlParams.has('vcardWebsite')) {
vcardWebsiteInput.value = sanitizeUrlString(urlParams.get('vcardWebsite'), MAX_VCARD_FIELD);
}
if (urlParams.has('vcardAddress')) {
vcardAddressInput.value = sanitizeUrlString(urlParams.get('vcardAddress'), MAX_VCARD_FIELD);
}
if (urlParams.has('vcardNote')) {
vcardNoteInput.value = sanitizeUrlString(urlParams.get('vcardNote'), MAX_VCARD_FIELD);
}
if (urlParams.has('size')) {
const sizeValue = urlParams.get('size');
@@ -555,6 +640,74 @@ document.addEventListener('DOMContentLoaded', function() {
return lines.join('\r\n');
}
function escapeVCardText(str) {
return String(str)
.replace(/\r\n/g, '\n')
.replace(/\r/g, '')
.replace(/\\/g, '\\\\')
.replace(/;/g, '\\;')
.replace(/,/g, '\\,')
.replace(/\n/g, '\\n');
}
function buildVCard(opts) {
const firstName = opts.firstName.trim();
const lastName = opts.lastName.trim();
const org = opts.org.trim();
const title = opts.title.trim();
const phone = opts.phone.trim();
const email = opts.email.trim();
const website = opts.website.trim();
const address = opts.address.trim();
const note = opts.note.trim();
const displayName = [firstName, lastName].filter(Boolean).join(' ').trim() || org;
const lines = [
'BEGIN:VCARD',
'VERSION:3.0',
'N:' + escapeVCardText(lastName) + ';' + escapeVCardText(firstName) + ';;;',
'FN:' + escapeVCardText(displayName)
];
if (org) {
lines.push('ORG:' + escapeVCardText(org));
}
if (title) {
lines.push('TITLE:' + escapeVCardText(title));
}
if (phone) {
lines.push('TEL;TYPE=CELL:' + escapeVCardText(phone));
}
if (email) {
lines.push('EMAIL;TYPE=INTERNET:' + escapeVCardText(email));
}
if (website) {
lines.push('URL:' + escapeVCardText(website));
}
if (address) {
lines.push('ADR;TYPE=WORK:;;' + escapeVCardText(address) + ';;;;');
}
if (note) {
lines.push('NOTE:' + escapeVCardText(note));
}
lines.push('END:VCARD');
return lines.join('\r\n');
}
function updateVCardPreview(text) {
if (!vcardPreview) {
return;
}
if (contentModeSelect.value !== 'vcard') {
vcardPreview.textContent = '';
return;
}
if (text && String(text).trim()) {
vcardPreview.textContent = String(text);
return;
}
vcardPreview.textContent = 'Bitte mindestens Vorname, Nachname oder Organisation eingeben.';
}
function generateQRCode() {
const mode = contentModeSelect.value;
const text = textInput.value.trim();
@@ -574,6 +727,9 @@ document.addEventListener('DOMContentLoaded', function() {
// Clear previous error
errorMessage.textContent = '';
downloadBtn.style.display = 'none';
if (mode !== 'vcard') {
updateVCardPreview('');
}
let qrText = '';
@@ -663,6 +819,40 @@ document.addEventListener('DOMContentLoaded', function() {
description: desc.slice(0, MAX_EVENT_DESCRIPTION)
});
textInput.value = qrText;
toggleClearButton();
} else if (mode === 'vcard') {
const firstName = vcardFirstNameInput.value.slice(0, MAX_VCARD_FIELD);
const lastName = vcardLastNameInput.value.slice(0, MAX_VCARD_FIELD);
const org = vcardOrgInput.value.slice(0, MAX_VCARD_FIELD);
const title = vcardTitleInput.value.slice(0, MAX_VCARD_FIELD);
const phone = vcardPhoneInput.value.slice(0, MAX_VCARD_FIELD);
const email = vcardEmailInput.value.slice(0, MAX_VCARD_FIELD);
const website = vcardWebsiteInput.value.slice(0, MAX_VCARD_FIELD);
const address = vcardAddressInput.value.slice(0, MAX_VCARD_FIELD);
const note = vcardNoteInput.value.slice(0, MAX_VCARD_FIELD);
const hasIdentity = firstName.trim() || lastName.trim() || org.trim();
if (!hasIdentity) {
updateVCardPreview('');
errorMessage.textContent = 'Bitte geben Sie mindestens Vorname, Nachname oder Organisation an';
qrcodeCanvas.style.display = 'none';
qrcodeImg.style.display = 'none';
return;
}
qrText = buildVCard({
firstName: firstName,
lastName: lastName,
org: org,
title: title,
phone: phone,
email: email,
website: website,
address: address,
note: note
});
updateVCardPreview(qrText);
textInput.value = qrText;
toggleClearButton();
} else if (mode === 'wifi') {
@@ -688,6 +878,8 @@ document.addEventListener('DOMContentLoaded', function() {
errorMessage.textContent = 'Bitte geben Sie Text/URL oder WLAN-Daten ein';
} else if (mode === 'event') {
errorMessage.textContent = 'Termin konnte nicht erzeugt werden';
} else if (mode === 'vcard') {
errorMessage.textContent = 'vCard konnte nicht erzeugt werden';
} else {
errorMessage.textContent = 'Bitte geben Sie Text oder eine URL ein';
}
@@ -827,6 +1019,34 @@ document.addEventListener('DOMContentLoaded', function() {
if (eventDescriptionInput.value.trim()) {
params.set('eventDescription', eventDescriptionInput.value.trim());
}
} else if (mode === 'vcard') {
if (vcardFirstNameInput.value.trim()) {
params.set('vcardFirstName', vcardFirstNameInput.value.trim());
}
if (vcardLastNameInput.value.trim()) {
params.set('vcardLastName', vcardLastNameInput.value.trim());
}
if (vcardOrgInput.value.trim()) {
params.set('vcardOrg', vcardOrgInput.value.trim());
}
if (vcardTitleInput.value.trim()) {
params.set('vcardTitle', vcardTitleInput.value.trim());
}
if (vcardPhoneInput.value.trim()) {
params.set('vcardPhone', vcardPhoneInput.value.trim());
}
if (vcardEmailInput.value.trim()) {
params.set('vcardEmail', vcardEmailInput.value.trim());
}
if (vcardWebsiteInput.value.trim()) {
params.set('vcardWebsite', vcardWebsiteInput.value.trim());
}
if (vcardAddressInput.value.trim()) {
params.set('vcardAddress', vcardAddressInput.value.trim());
}
if (vcardNoteInput.value.trim()) {
params.set('vcardNote', vcardNoteInput.value.trim());
}
} else if (mode === 'wifi') {
if (wifiSsid) {
params.set('ssid', wifiSsid);