Ajout de la génération de QR codes de contact (vCard)

This commit is contained in:
florian committed 2026-08-27 15:35:27 +02:00
1 parent 636296e154
commit fb93aff837
3 files changed
+110 -9

No files matched your search

+1
View File
@@ -12,6 +12,7 @@ L'encodage et le rendu du QR code se font **intégralement dans le navigateur**
- Encodage de texte libre ou d'URL en QR code, côté client, via [qrcode-generator](https://github.com/kazuhikoarase/qrcode-generator) - Encodage de texte libre ou d'URL en QR code, côté client, via [qrcode-generator](https://github.com/kazuhikoarase/qrcode-generator)
- Génération de QR codes de connexion Wi-Fi (SSID, mot de passe, sécurité WPA/WPA2/WPA3/WEP/ouvert, réseau masqué) — scannables directement par l'appareil photo du téléphone - Génération de QR codes de connexion Wi-Fi (SSID, mot de passe, sécurité WPA/WPA2/WPA3/WEP/ouvert, réseau masqué) — scannables directement par l'appareil photo du téléphone
- Génération de QR codes de contact (vCard) : nom, téléphone, email, organisation, fonction, site web — importables directement dans le répertoire
- Niveaux de correction d'erreur configurables (L / M / Q / H) - Niveaux de correction d'erreur configurables (L / M / Q / H)
- Couleurs de modules et de fond personnalisables - Couleurs de modules et de fond personnalisables
- Superposition d'un logo au centre du QR code (force automatiquement la correction d'erreur au niveau H pour rester scannable) - Superposition d'un logo au centre du QR code (force automatiquement la correction d'erreur au niveau H pour rester scannable)
+69 -8
View File
@@ -4,12 +4,20 @@
const modeEl = document.getElementById("qr-mode"); const modeEl = document.getElementById("qr-mode");
const modeTextWrap = document.getElementById("mode-text"); const modeTextWrap = document.getElementById("mode-text");
const modeWifiWrap = document.getElementById("mode-wifi"); const modeWifiWrap = document.getElementById("mode-wifi");
const modeVcardWrap = document.getElementById("mode-vcard");
const textEl = document.getElementById("qr-text"); const textEl = document.getElementById("qr-text");
const wifiSsidEl = document.getElementById("wifi-ssid"); const wifiSsidEl = document.getElementById("wifi-ssid");
const wifiEncryptionEl = document.getElementById("wifi-encryption"); const wifiEncryptionEl = document.getElementById("wifi-encryption");
const wifiPasswordEl = document.getElementById("wifi-password"); const wifiPasswordEl = document.getElementById("wifi-password");
const wifiPasswordField = document.getElementById("wifi-password-field"); const wifiPasswordField = document.getElementById("wifi-password-field");
const wifiHiddenEl = document.getElementById("wifi-hidden"); const wifiHiddenEl = document.getElementById("wifi-hidden");
const vcardFirstnameEl = document.getElementById("vcard-firstname");
const vcardLastnameEl = document.getElementById("vcard-lastname");
const vcardPhoneEl = document.getElementById("vcard-phone");
const vcardEmailEl = document.getElementById("vcard-email");
const vcardOrgEl = document.getElementById("vcard-org");
const vcardTitleEl = document.getElementById("vcard-title");
const vcardWebsiteEl = document.getElementById("vcard-website");
const cellSizeEl = document.getElementById("qr-cell-size"); const cellSizeEl = document.getElementById("qr-cell-size");
const correctionEl = document.getElementById("qr-correction"); const correctionEl = document.getElementById("qr-correction");
const fgColorEl = document.getElementById("qr-fg-color"); const fgColorEl = document.getElementById("qr-fg-color");
@@ -68,10 +76,40 @@
return payload; return payload;
} }
function escapeVcardField(value) {
return String(value).replace(/([\\;,])/g, "\\$1").replace(/\n/g, "\\n");
}
function buildVcardPayload() {
const firstName = vcardFirstnameEl.value.trim();
const lastName = vcardLastnameEl.value.trim();
const phone = vcardPhoneEl.value.trim();
const email = vcardEmailEl.value.trim();
const org = vcardOrgEl.value.trim();
const title = vcardTitleEl.value.trim();
const website = vcardWebsiteEl.value.trim();
if (!firstName && !lastName) return "";
const fullName = [firstName, lastName].filter(Boolean).join(" ");
let payload = "BEGIN:VCARD\nVERSION:3.0\n";
payload += `N:${escapeVcardField(lastName)};${escapeVcardField(firstName)};;;\n`;
payload += `FN:${escapeVcardField(fullName)}\n`;
if (org) payload += `ORG:${escapeVcardField(org)}\n`;
if (title) payload += `TITLE:${escapeVcardField(title)}\n`;
if (phone) payload += `TEL;TYPE=CELL:${escapeVcardField(phone)}\n`;
if (email) payload += `EMAIL:${escapeVcardField(email)}\n`;
if (website) payload += `URL:${escapeVcardField(website)}\n`;
payload += "END:VCARD";
return payload;
}
function updateModeVisibility() { function updateModeVisibility() {
const isWifi = modeEl.value === "wifi"; const mode = modeEl.value;
modeTextWrap.style.display = isWifi ? "none" : "block"; modeTextWrap.style.display = mode === "text" ? "block" : "none";
modeWifiWrap.style.display = isWifi ? "block" : "none"; modeWifiWrap.style.display = mode === "wifi" ? "block" : "none";
modeVcardWrap.style.display = mode === "vcard" ? "block" : "none";
wifiPasswordField.style.display = wifiEncryptionEl.value === "nopass" ? "none" : "block"; wifiPasswordField.style.display = wifiEncryptionEl.value === "nopass" ? "none" : "block";
} }
@@ -87,18 +125,41 @@
el.addEventListener("change", render); el.addEventListener("change", render);
}); });
[
vcardFirstnameEl,
vcardLastnameEl,
vcardPhoneEl,
vcardEmailEl,
vcardOrgEl,
vcardTitleEl,
vcardWebsiteEl,
].forEach((el) => {
el.addEventListener("input", render);
});
function render() { function render() {
const isWifi = modeEl.value === "wifi"; const mode = modeEl.value;
const text = isWifi ? buildWifiPayload() : textEl.value; let text;
if (mode === "wifi") {
text = buildWifiPayload();
} else if (mode === "vcard") {
text = buildVcardPayload();
} else {
text = textEl.value;
}
statusEl.classList.remove("error"); statusEl.classList.remove("error");
if (!text) { if (!text) {
ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.clearRect(0, 0, canvas.width, canvas.height);
canvas.width = 280; canvas.width = 280;
canvas.height = 280; canvas.height = 280;
statusEl.textContent = isWifi if (mode === "wifi") {
? "Renseignez au moins le nom du réseau (SSID) pour générer le QR code." statusEl.textContent = "Renseignez au moins le nom du réseau (SSID) pour générer le QR code.";
: "Saisissez un texte ou une URL pour générer le QR code."; } else if (mode === "vcard") {
statusEl.textContent = "Renseignez au moins un prénom ou un nom pour générer le QR code.";
} else {
statusEl.textContent = "Saisissez un texte ou une URL pour générer le QR code.";
}
return; return;
} }
+40 -1
View File
@@ -9,7 +9,7 @@
<body> <body>
<header> <header>
<h1>QR Code <span>Generator</span></h1> <h1>QR Code <span>Generator</span></h1>
<p>Générez des QR codes pour du texte, des liens ou une connexion Wi-Fi, avec logo optionnel.</p> <p>Générez des QR codes pour du texte, des liens, une connexion Wi-Fi ou une carte de contact, avec logo optionnel.</p>
<span class="badge">🔒 100% local — rien n'est envoyé à un serveur</span> <span class="badge">🔒 100% local — rien n'est envoyé à un serveur</span>
</header> </header>
@@ -22,6 +22,7 @@
<select id="qr-mode"> <select id="qr-mode">
<option value="text" selected>Texte / URL</option> <option value="text" selected>Texte / URL</option>
<option value="wifi">Wi-Fi</option> <option value="wifi">Wi-Fi</option>
<option value="vcard">Contact (vCard)</option>
</select> </select>
</div> </div>
@@ -56,6 +57,44 @@
<p class="hint">Scanné avec l'appareil photo du téléphone, ce QR code connecte directement l'appareil au réseau Wi-Fi, sans saisir le mot de passe.</p> <p class="hint">Scanné avec l'appareil photo du téléphone, ce QR code connecte directement l'appareil au réseau Wi-Fi, sans saisir le mot de passe.</p>
</div> </div>
<div id="mode-vcard" style="display:none;">
<div class="grid">
<div class="field">
<label for="vcard-firstname">Prénom</label>
<input type="text" id="vcard-firstname" placeholder="Jean" />
</div>
<div class="field">
<label for="vcard-lastname">Nom</label>
<input type="text" id="vcard-lastname" placeholder="Dupont" />
</div>
</div>
<div class="grid">
<div class="field">
<label for="vcard-phone">Téléphone</label>
<input type="text" id="vcard-phone" placeholder="+33 6 12 34 56 78" />
</div>
<div class="field">
<label for="vcard-email">Email</label>
<input type="text" id="vcard-email" placeholder="jean.dupont@example.com" />
</div>
</div>
<div class="grid">
<div class="field">
<label for="vcard-org">Organisation</label>
<input type="text" id="vcard-org" placeholder="Mon Entreprise" />
</div>
<div class="field">
<label for="vcard-title">Fonction</label>
<input type="text" id="vcard-title" placeholder="Développeur" />
</div>
</div>
<div class="field">
<label for="vcard-website">Site web</label>
<input type="text" id="vcard-website" placeholder="https://example.com" />
</div>
<p class="hint">Scanné avec l'appareil photo du téléphone, ce QR code propose d'ajouter directement le contact au répertoire.</p>
</div>
<div class="grid"> <div class="grid">
<div class="field"> <div class="field">
<label for="qr-cell-size">Taille des modules (px)</label> <label for="qr-cell-size">Taille des modules (px)</label>