diff --git a/index.html b/index.html
index 62ca4c6..ddfb1d7 100644
--- a/index.html
+++ b/index.html
@@ -361,6 +361,7 @@
Text oder URL
WLAN
Termin (Kalender)
+ vCard (Kontakt)
+
+
+
+
+
+
+
Webseite (optional):
+
+
+
Adresse (optional):
+
+
+
Notiz (optional):
+
+
+
QR Code Grösse:
@@ -508,7 +553,7 @@
Be excellent to each other.
Modi
-
Unter QR-Inhalt wählst du, was kodiert wird: freier Text oder URL, WLAN-Zugangsdaten (WIFI-QR) oder einen Kalendertermin . Im Modus „Termin“ erzeugt der QR-Code einen Standard-iCalendar-Eintrag (VEVENT), den viele Smartphones beim Scannen in die Kalender-App übernehmen. Datumseingabe als TT/MM/JJJJ , Uhrzeit im 24-Stunden-Format ; die Zeitzone ist Europe/Berlin . Deine Eingaben für den QR-Code werden nicht an einen Server gesendet.
+
Unter QR-Inhalt wählst du, was kodiert wird: freier Text oder URL, WLAN-Zugangsdaten (WIFI-QR), ein Kalendertermin oder eine vCard . Im Modus „Termin“ erzeugt der QR-Code einen Standard-iCalendar-Eintrag (VEVENT), den viele Smartphones beim Scannen in die Kalender-App übernehmen. Datumseingabe als TT/MM/JJJJ , Uhrzeit im 24-Stunden-Format ; die Zeitzone ist Europe/Berlin . 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.
diff --git a/main.js b/main.js
index 6c93e16..9788bbc 100644
--- a/main.js
+++ b/main.js
@@ -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');
@@ -36,6 +46,7 @@ document.addEventListener('DOMContentLoaded', function() {
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 +96,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 +109,8 @@ document.addEventListener('DOMContentLoaded', function() {
wifiSsidInput.focus();
} else if (this.value === 'event') {
eventTitleInput.focus();
+ } else if (this.value === 'vcard') {
+ vcardFirstNameInput.focus();
}
});
@@ -117,7 +131,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 +164,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 +262,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 +331,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 +381,41 @@ 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('vcardPhone') ||
+ urlParams.has('vcardEmail');
+ 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 +635,59 @@ 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 generateQRCode() {
const mode = contentModeSelect.value;
const text = textInput.value.trim();
@@ -663,6 +796,38 @@ 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) {
+ 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
+ });
textInput.value = qrText;
toggleClearButton();
} else if (mode === 'wifi') {
@@ -688,6 +853,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 +994,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);