Fase 3 v1: new qr types, svg export, clipboard, frame text, batch CSV

New static qr types:
- App Link: Android intent:// links (with package + optional browser
  fallback) or a generic custom-scheme URI. iOS Universal Links need no
  special encoding (they're just plain https:// URLs).
- Bluetooth: device name + MAC address. Purely informational, since unlike
  WIFI:/vCard there's no OS-native "scan to pair" convention.

SVG export: already worked (format whitelist/dropdown existed since Fase 1),
verified rather than reimplemented.

Copy-to-clipboard button next to the download button on both qr list tables,
using the Clipboard API against a fetched blob.

Optional frame text label rendered below the qr code via GD after
generation (raster formats only, no-op for svg/eps).

Batch CSV upload (batch_qrcode.php): filename,link rows create dynamic qr
codes with sane defaults, downloadable as a zip. Required refactoring
Qrcode-intchil.php's generation path (previously always redirected/exited
via failure()/success(), which can't run in a loop) into a private
renderAndStore() that throws instead, shared by addQrcode() and the new
addQrcodeBatch(). Qrcode.php's addQrcodeBatch() is a separate, deliberately
duplicated implementation instead, since its generation logic is small
enough that duplication carries less risk than refactoring the working
external-API code path.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-09 00:03:01 +02:00
parent b5ef4ac0cb
commit f7d2de355e
14 changed files with 698 additions and 21 deletions
+21 -1
View File
@@ -499,4 +499,24 @@
return false;
});
})(jQuery)
})(jQuery)
// Copy a qr code image straight to the clipboard (Fase 3 UX feature).
document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('.copy-qr-btn').forEach(function (btn) {
btn.addEventListener('click', async function () {
const icon = btn.querySelector('i');
const originalClass = icon.className;
try {
const response = await fetch(btn.getAttribute('data-qr-src'));
const blob = await response.blob();
await navigator.clipboard.write([new ClipboardItem({ [blob.type]: blob })]);
icon.className = 'fa fa-check';
setTimeout(function () { icon.className = originalClass; }, 1500);
} catch (err) {
alert('Could not copy this image to the clipboard (your browser may not support this image format for clipboard access): ' + err.message);
}
});
});
});