diff --git a/index.html b/index.html index 62ca4c6..ddfb1d7 100644 --- a/index.html +++ b/index.html @@ -361,6 +361,7 @@ +
@@ -434,6 +435,50 @@
+ +
@@ -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);