Ajout de la génération de QR codes de contact (vCard)
This commit is contained in:
1 parent
636296e154
commit
fb93aff837
3 files changed
+110
-9
No files matched your search
@@ -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
@@ -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
@@ -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>
|
||||||
|
|||||||
Reference in new issue
Block a user