mirror of
https://github.com/PR0M3TH3AN/Artstr-Studio.git
synced 2026-11-06 05:30:52 +00:00
Add Lightning tips (LNURL-pay → bolt11 → QR)
A self-contained tip flow that works for any author whose kind-0 profile publishes a lud16 Lightning address or lud06 LNURL. Picks an amount, fetches a bolt11 invoice from the LNURL-pay endpoint, renders it as a scannable QR + copyable string. No NIP-57 zap request, no NWC, no WebLN — just the smallest path to actually moving sats. Phase A — LNURL helpers and tip modal: - Vendored qrcode-generator (~20 KB, MIT) as src/vendor/qrcode.min.js so the QR encoder runs offline with no CDN dependency. - lud16ToLnurlpUrl / lud06ToHttpUrl / resolveLnurlPayUrl pick the right endpoint from a profile. - fetchLnurlPayMetadata validates the payRequest tag and surfaces the min/max range; fetchLnurlInvoice requests an amount-locked bolt11. - renderQrInto auto-scales the typeNumber so long invoices still fit. - tipModal: amount-chip picker (21 / 100 / 500 / 1k / 5k / 21k / Custom), inline min/max validation, fetching state, then an invoice view with QR, lightning: deeplink, copyable bolt11, and a New amount / Done row. - Brought to front via the modal z-stack; ESC, backdrop, and × close it. Phase B — Feed card surface: - Every feed card whose author has a resolvable LN address gets a ⚡ Tip button in the actions row. No button when there's no address, so feeds stay clean. Phase C — Design preview surface: - Same conditional ⚡ Tip button in the design preview action row, next to Save JSON and Copy share link. Profile pages now treat the Lightning chip as the entry point — clicking the chip opens the tip modal instead of copying the address. Profiles with an address that fails to resolve fall back to the original copy chip. Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
1 parent
e9b38710d6
commit
e33d15f41f
2 files changed
+331
-4
No files matched your search
+323
-4
@@ -1582,6 +1582,16 @@
|
||||
.profileAvatar { margin-top:-44px; }
|
||||
}
|
||||
|
||||
.tipAmounts { display:grid; grid-template-columns:repeat(auto-fit,minmax(72px,1fr)); gap:8px; margin:0 0 14px; }
|
||||
.tipAmounts button { padding:10px 6px; font-size:.9rem; font-weight:600; cursor:pointer; border:1px solid var(--line); background:var(--bg); color:var(--ink); border-radius:8px; }
|
||||
.tipAmounts button.active { background:var(--accent); color:#fff; border-color:var(--accent); }
|
||||
.tipAmounts button:hover { filter:brightness(.97); }
|
||||
.tipQr { display:flex; justify-content:center; align-items:center; background:#fff; padding:14px; border-radius:8px; margin:0 auto 14px; max-width:280px; }
|
||||
.tipQr svg { display:block; width:240px; height:240px; }
|
||||
.tipBolt11 { display:flex; gap:8px; align-items:center; margin-bottom:10px; padding:8px 10px; border:1px solid var(--line); border-radius:6px; background:var(--bg); }
|
||||
.tipBolt11 code { flex:1; min-width:0; font-family:ui-monospace,SFMono-Regular,monospace; font-size:.72rem; word-break:break-all; max-height:3em; overflow:hidden; line-height:1.3; }
|
||||
.tipBolt11 button { flex-shrink:0; }
|
||||
|
||||
.addIdentifiersDetails { }
|
||||
.addIdentifiersBtn {
|
||||
display: inline-flex;
|
||||
@@ -2737,6 +2747,48 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal" id="tipModal" aria-hidden="true" style="display:none">
|
||||
<div class="modalInner" style="max-width:420px;position:relative">
|
||||
<button type="button" id="tipCloseBtn" class="welcomeCloseBtn" aria-label="Close">×</button>
|
||||
<h2 id="tipTitle" style="margin:0 0 4px;font-size:1.05rem;font-family:inherit">Send a tip</h2>
|
||||
<p class="hint" id="tipSubtitle" style="margin:0 0 14px"></p>
|
||||
|
||||
<div id="tipPickerView">
|
||||
<label class="hint" style="display:block;font-size:.78rem;margin-bottom:6px">Amount (sats)</label>
|
||||
<div class="tipAmounts" id="tipAmounts">
|
||||
<button type="button" data-sats="21">21</button>
|
||||
<button type="button" data-sats="100">100</button>
|
||||
<button type="button" data-sats="500">500</button>
|
||||
<button type="button" data-sats="1000">1k</button>
|
||||
<button type="button" data-sats="5000">5k</button>
|
||||
<button type="button" data-sats="21000">21k</button>
|
||||
<button type="button" data-sats="custom">Custom</button>
|
||||
</div>
|
||||
<input type="number" id="tipCustomInput" min="1" placeholder="Custom amount in sats" style="display:none;width:100%;margin-bottom:10px;padding:8px;border:1px solid var(--line);border-radius:6px;font-size:.9rem" />
|
||||
<p class="hint" id="tipPickerError" style="min-height:1.2em;margin:0 0 10px"></p>
|
||||
<div class="buttonRow" style="margin:0;justify-content:flex-end">
|
||||
<button type="button" id="tipGetInvoiceBtn" class="primary">Get invoice</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="tipInvoiceView" style="display:none">
|
||||
<p class="hint" id="tipInvoiceAmount" style="margin:0 0 10px;text-align:center;font-size:.88rem"></p>
|
||||
<a id="tipQrLink" href="#" target="_blank" rel="noopener" style="text-decoration:none;color:inherit;display:block">
|
||||
<div id="tipQr" class="tipQr"></div>
|
||||
</a>
|
||||
<div class="tipBolt11">
|
||||
<code id="tipBolt11"></code>
|
||||
<button type="button" id="tipCopyBtn" aria-label="Copy invoice">📋</button>
|
||||
</div>
|
||||
<p class="hint" style="margin:0 0 14px;font-size:.78rem">Scan the QR with a Lightning wallet, or copy the invoice and paste it into your wallet to send the tip.</p>
|
||||
<div class="buttonRow" style="margin:0;justify-content:space-between">
|
||||
<button type="button" id="tipBackBtn">← New amount</button>
|
||||
<button type="button" id="tipDoneBtn" class="primary">Done</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal" id="closeProjectModal" aria-hidden="true" style="display:none">
|
||||
<div class="modalInner" style="max-width:440px">
|
||||
<h2 style="font-size:1.05rem;margin:0 0 8px;font-family:inherit">Close this project?</h2>
|
||||
@@ -2775,6 +2827,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="vendor/qrcode.min.js"></script>
|
||||
<script>
|
||||
const $ = (id) => document.getElementById(id);
|
||||
const safe$ = (id) => { try { return $(id); } catch { return null; } };
|
||||
@@ -7915,12 +7968,21 @@
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
// Lightning chip (read-only copy — payment flow is a separate feature)
|
||||
// Lightning chip: clicking opens the Tip modal (LNURL-pay → bolt11 → QR)
|
||||
if (lnRowEl) {
|
||||
lnRowEl.innerHTML = '';
|
||||
const lnAddress = (profile?.lud16 || decodeLud06ToAddress(profile?.lud06) || '').trim();
|
||||
if (lnAddress) {
|
||||
const chip = makeProfileChip({
|
||||
const lnurlpUrl = resolveLnurlPayUrl(profile);
|
||||
if (lnAddress && lnurlpUrl) {
|
||||
const tipChip = makeProfileChip({
|
||||
label: `⚡ Tip ${lnAddress}`,
|
||||
title: 'Send a Lightning tip',
|
||||
onClick: () => openTipModal({ lnurlpUrl, label: displayName, lud16: lnAddress })
|
||||
});
|
||||
lnRowEl.appendChild(tipChip);
|
||||
} else if (lnAddress) {
|
||||
// Have an address but couldn't resolve it — fall back to copy.
|
||||
const copyChip = makeProfileChip({
|
||||
label: `⚡ ${lnAddress}`,
|
||||
title: 'Copy Lightning address',
|
||||
onClick: async (btn) => {
|
||||
@@ -7928,7 +7990,7 @@
|
||||
flashChipText(btn, ok ? '⚡ Copied!' : '⚡ Copy failed');
|
||||
}
|
||||
});
|
||||
lnRowEl.appendChild(chip);
|
||||
lnRowEl.appendChild(copyChip);
|
||||
}
|
||||
if (isHttpUrl(profile?.website)) {
|
||||
const link = document.createElement('a');
|
||||
@@ -8070,6 +8132,199 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Lightning Tips (LNURL-pay) ----
|
||||
|
||||
function lud16ToLnurlpUrl(ln) {
|
||||
const parts = String(ln || '').trim().split('@');
|
||||
if (parts.length !== 2 || !parts[0] || !parts[1]) return '';
|
||||
const name = parts[0];
|
||||
const host = parts[1];
|
||||
if (!/^[a-z0-9.-]+$/i.test(host)) return '';
|
||||
return `https://${host}/.well-known/lnurlp/${encodeURIComponent(name)}`;
|
||||
}
|
||||
|
||||
function lud06ToHttpUrl(bech32) {
|
||||
try {
|
||||
const dec = bech32Decode(String(bech32 || '').trim());
|
||||
if (!dec || dec.hrp !== 'lnurl') return '';
|
||||
const bytes = convertBits(dec.data, 5, 8, false);
|
||||
if (!bytes) return '';
|
||||
const url = new TextDecoder().decode(new Uint8Array(bytes));
|
||||
return url.startsWith('http://') || url.startsWith('https://') ? url : '';
|
||||
} catch { return ''; }
|
||||
}
|
||||
|
||||
function resolveLnurlPayUrl(profile) {
|
||||
if (!profile) return '';
|
||||
if (profile.lud16) {
|
||||
const u = lud16ToLnurlpUrl(profile.lud16);
|
||||
if (u) return u;
|
||||
}
|
||||
if (profile.lud06) {
|
||||
const u = lud06ToHttpUrl(profile.lud06);
|
||||
if (u) return u;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
async function fetchLnurlPayMetadata(url) {
|
||||
const r = await fetch(url, { headers: { Accept: 'application/json' } });
|
||||
if (!r.ok) throw new Error(`Lookup failed (${r.status})`);
|
||||
const j = await r.json();
|
||||
if (j.status === 'ERROR') throw new Error(j.reason || 'Service returned an error');
|
||||
if (j.tag !== 'payRequest') throw new Error('Not a Lightning tip endpoint');
|
||||
return j;
|
||||
}
|
||||
|
||||
async function fetchLnurlInvoice(callback, msats) {
|
||||
const sep = callback.includes('?') ? '&' : '?';
|
||||
const url = `${callback}${sep}amount=${msats}`;
|
||||
const r = await fetch(url, { headers: { Accept: 'application/json' } });
|
||||
if (!r.ok) throw new Error(`Invoice fetch failed (${r.status})`);
|
||||
const j = await r.json();
|
||||
if (j.status === 'ERROR') throw new Error(j.reason || 'Service returned an error');
|
||||
if (!j.pr) throw new Error('No invoice returned');
|
||||
return j.pr;
|
||||
}
|
||||
|
||||
function renderQrInto(el, text) {
|
||||
if (typeof qrcode !== 'function') { el.textContent = 'QR library unavailable'; return; }
|
||||
el.innerHTML = '';
|
||||
// Try increasing typeNumber until the data fits. Bolt11 invoices can be long.
|
||||
let qr = null;
|
||||
for (let typeNumber = 6; typeNumber <= 40; typeNumber++) {
|
||||
try {
|
||||
const candidate = qrcode(typeNumber, 'M');
|
||||
candidate.addData(text);
|
||||
candidate.make();
|
||||
qr = candidate;
|
||||
break;
|
||||
} catch { /* keep trying larger type */ }
|
||||
}
|
||||
if (!qr) { el.textContent = 'QR too large to render'; return; }
|
||||
el.innerHTML = qr.createSvgTag({ scalable: true });
|
||||
}
|
||||
|
||||
let _tipTarget = null; // { lnurlpUrl, label, lud16 }
|
||||
let _tipSelectedSats = 0;
|
||||
let _tipMetadata = null;
|
||||
|
||||
function openTipModal(opts = {}) {
|
||||
const { lnurlpUrl, label, lud16 } = opts;
|
||||
if (!lnurlpUrl) return;
|
||||
_tipTarget = { lnurlpUrl, label: label || 'Recipient', lud16: lud16 || '' };
|
||||
_tipSelectedSats = 0;
|
||||
_tipMetadata = null;
|
||||
const modal = $('tipModal');
|
||||
if (!modal) return;
|
||||
|
||||
$('tipTitle').textContent = `Tip ${label || 'Recipient'}`;
|
||||
$('tipSubtitle').textContent = lud16 || '';
|
||||
$('tipPickerError').textContent = '';
|
||||
$('tipPickerView').style.display = '';
|
||||
$('tipInvoiceView').style.display = 'none';
|
||||
$('tipCustomInput').style.display = 'none';
|
||||
$('tipCustomInput').value = '';
|
||||
const getBtn = $('tipGetInvoiceBtn');
|
||||
if (getBtn) { getBtn.disabled = true; getBtn.textContent = 'Get invoice'; }
|
||||
// Clear any active selection
|
||||
$('tipAmounts').querySelectorAll('button').forEach(b => b.classList.remove('active'));
|
||||
|
||||
modal.style.display = 'flex';
|
||||
modal.setAttribute('aria-hidden', 'false');
|
||||
bringModalToFront(modal);
|
||||
|
||||
// Fetch metadata in the background; once it arrives we know min/max.
|
||||
fetchLnurlPayMetadata(lnurlpUrl).then(meta => {
|
||||
if (_tipTarget?.lnurlpUrl !== lnurlpUrl) return;
|
||||
_tipMetadata = meta;
|
||||
}).catch(err => {
|
||||
if (_tipTarget?.lnurlpUrl !== lnurlpUrl) return;
|
||||
const errEl = $('tipPickerError');
|
||||
if (errEl) errEl.textContent = String(err?.message || err);
|
||||
});
|
||||
}
|
||||
|
||||
function closeTipModal() {
|
||||
const modal = $('tipModal');
|
||||
if (modal) { modal.style.display = 'none'; modal.setAttribute('aria-hidden', 'true'); }
|
||||
_tipTarget = null;
|
||||
_tipSelectedSats = 0;
|
||||
_tipMetadata = null;
|
||||
}
|
||||
|
||||
function selectTipAmount(value) {
|
||||
const errEl = $('tipPickerError');
|
||||
const customInput = $('tipCustomInput');
|
||||
const getBtn = $('tipGetInvoiceBtn');
|
||||
if (errEl) errEl.textContent = '';
|
||||
$('tipAmounts').querySelectorAll('button').forEach(b => b.classList.toggle('active', b.dataset.sats === String(value)));
|
||||
if (value === 'custom') {
|
||||
customInput.style.display = '';
|
||||
customInput.focus();
|
||||
_tipSelectedSats = parseInt(customInput.value, 10) || 0;
|
||||
} else {
|
||||
customInput.style.display = 'none';
|
||||
_tipSelectedSats = parseInt(value, 10) || 0;
|
||||
}
|
||||
if (getBtn) getBtn.disabled = _tipSelectedSats <= 0;
|
||||
}
|
||||
|
||||
async function getTipInvoiceFlow() {
|
||||
const errEl = $('tipPickerError');
|
||||
const getBtn = $('tipGetInvoiceBtn');
|
||||
if (!_tipTarget) return;
|
||||
let sats = _tipSelectedSats;
|
||||
if (sats <= 0) { if (errEl) errEl.textContent = 'Pick an amount first.'; return; }
|
||||
|
||||
try {
|
||||
if (getBtn) { getBtn.disabled = true; getBtn.textContent = 'Fetching…'; }
|
||||
const meta = _tipMetadata || await fetchLnurlPayMetadata(_tipTarget.lnurlpUrl);
|
||||
_tipMetadata = meta;
|
||||
const minSats = Math.ceil((meta.minSendable || 1000) / 1000);
|
||||
const maxSats = Math.floor((meta.maxSendable || 100000000) / 1000);
|
||||
if (sats < minSats || sats > maxSats) {
|
||||
if (errEl) errEl.textContent = `Enter between ${minSats} and ${maxSats.toLocaleString()} sats.`;
|
||||
if (getBtn) { getBtn.disabled = false; getBtn.textContent = 'Get invoice'; }
|
||||
return;
|
||||
}
|
||||
const msats = sats * 1000;
|
||||
const bolt11 = await fetchLnurlInvoice(meta.callback, msats);
|
||||
showTipInvoiceView(bolt11, sats);
|
||||
} catch (err) {
|
||||
if (errEl) errEl.textContent = String(err?.message || err);
|
||||
if (getBtn) { getBtn.disabled = false; getBtn.textContent = 'Get invoice'; }
|
||||
}
|
||||
}
|
||||
|
||||
function showTipInvoiceView(bolt11, sats) {
|
||||
$('tipPickerView').style.display = 'none';
|
||||
$('tipInvoiceView').style.display = '';
|
||||
$('tipInvoiceAmount').textContent = `${sats.toLocaleString()} sats`;
|
||||
const qrEl = $('tipQr');
|
||||
const link = $('tipQrLink');
|
||||
if (link) link.href = `lightning:${bolt11}`;
|
||||
renderQrInto(qrEl, bolt11.toUpperCase());
|
||||
$('tipBolt11').textContent = bolt11;
|
||||
const copyBtn = $('tipCopyBtn');
|
||||
if (copyBtn) {
|
||||
copyBtn.onclick = async () => {
|
||||
const ok = await copyToClipboard(bolt11);
|
||||
copyBtn.textContent = ok ? '✓' : '✗';
|
||||
setTimeout(() => { if (copyBtn) copyBtn.textContent = '📋'; }, 1200);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function resetTipPicker() {
|
||||
$('tipInvoiceView').style.display = 'none';
|
||||
$('tipPickerView').style.display = '';
|
||||
const errEl = $('tipPickerError');
|
||||
if (errEl) errEl.textContent = '';
|
||||
const getBtn = $('tipGetInvoiceBtn');
|
||||
if (getBtn) { getBtn.disabled = _tipSelectedSats <= 0; getBtn.textContent = 'Get invoice'; }
|
||||
}
|
||||
|
||||
let _closeProjectTarget = null;
|
||||
|
||||
function openCloseProjectModal({ body, save, reset }) {
|
||||
@@ -8458,7 +8713,9 @@
|
||||
e.stopPropagation();
|
||||
openReportModal(row.e?.id, row.e?.pubkey);
|
||||
});
|
||||
const tipBtn = makeFeedCardTipButton(row);
|
||||
actions.append(makeReactionBtn(row), prevBtn, btn, blockBtn, reportBtn);
|
||||
if (tipBtn) actions.appendChild(tipBtn);
|
||||
el.append(thumb, meta, actions);
|
||||
el.addEventListener('click', () => {
|
||||
if (el.classList.contains('feedCardBlurred')) return;
|
||||
@@ -8467,6 +8724,25 @@
|
||||
return el;
|
||||
}
|
||||
|
||||
function makeFeedCardTipButton(row) {
|
||||
const pk = row?.e?.pubkey;
|
||||
if (!pk) return null;
|
||||
const profile = _feedCache.profiles?.[pk] || state.community?.profiles?.[pk] || null;
|
||||
const lnurlpUrl = resolveLnurlPayUrl(profile);
|
||||
if (!lnurlpUrl) return null;
|
||||
const lnAddress = (profile?.lud16 || '').trim();
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.textContent = '⚡ Tip';
|
||||
btn.title = lnAddress ? `Send a Lightning tip to ${lnAddress}` : 'Send a Lightning tip';
|
||||
btn.addEventListener('click', e => {
|
||||
e.stopPropagation();
|
||||
const displayName = profile?.display_name || profile?.name || shortPubkey(pk);
|
||||
openTipModal({ lnurlpUrl, label: displayName, lud16: lnAddress });
|
||||
});
|
||||
return btn;
|
||||
}
|
||||
|
||||
function updateNostrWidget() {
|
||||
const pubkey = state.community.myPubkey;
|
||||
const profile = state.community.myProfile;
|
||||
@@ -8980,6 +9256,25 @@
|
||||
actionsDiv.removeChild(actionsDiv.firstChild);
|
||||
}
|
||||
|
||||
// Lightning tip button — only shown when the author has a resolvable LN address.
|
||||
const previewPk = row?.e?.pubkey;
|
||||
if (previewPk) {
|
||||
const previewAuthorProfile = _feedCache.profiles?.[previewPk] || state.community?.profiles?.[previewPk] || null;
|
||||
const previewLnurlpUrl = resolveLnurlPayUrl(previewAuthorProfile);
|
||||
if (previewLnurlpUrl) {
|
||||
const previewLnAddress = (previewAuthorProfile?.lud16 || '').trim();
|
||||
const previewName = previewAuthorProfile?.display_name || previewAuthorProfile?.name || shortPubkey(previewPk);
|
||||
const tipBtn = document.createElement('button');
|
||||
tipBtn.type = 'button';
|
||||
tipBtn.textContent = '⚡ Tip';
|
||||
tipBtn.title = previewLnAddress ? `Send a Lightning tip to ${previewLnAddress}` : 'Send a Lightning tip';
|
||||
tipBtn.addEventListener('click', () => {
|
||||
openTipModal({ lnurlpUrl: previewLnurlpUrl, label: previewName, lud16: previewLnAddress });
|
||||
});
|
||||
actionsDiv.insertBefore(tipBtn, saveJsonBtn);
|
||||
}
|
||||
}
|
||||
|
||||
const shareBtn = document.createElement('button');
|
||||
shareBtn.type = 'button';
|
||||
shareBtn.textContent = 'Copy share link';
|
||||
@@ -10963,9 +11258,33 @@
|
||||
if (_profilePagePubkey) renderProfileDesigns(_profilePagePubkey);
|
||||
});
|
||||
|
||||
// Tip modal wiring
|
||||
const tipModalEl = $('tipModal');
|
||||
if (tipModalEl) tipModalEl.addEventListener('click', e => { if (e.target === tipModalEl) closeTipModal(); });
|
||||
on('tipCloseBtn', 'click', closeTipModal);
|
||||
on('tipGetInvoiceBtn', 'click', getTipInvoiceFlow);
|
||||
on('tipBackBtn', 'click', resetTipPicker);
|
||||
on('tipDoneBtn', 'click', closeTipModal);
|
||||
const tipAmountsEl = $('tipAmounts');
|
||||
if (tipAmountsEl) tipAmountsEl.addEventListener('click', e => {
|
||||
const btn = e.target.closest('button[data-sats]');
|
||||
if (!btn) return;
|
||||
selectTipAmount(btn.dataset.sats);
|
||||
});
|
||||
on('tipCustomInput', 'input', e => {
|
||||
const sats = parseInt(e.target.value, 10) || 0;
|
||||
_tipSelectedSats = sats;
|
||||
const getBtn = $('tipGetInvoiceBtn');
|
||||
if (getBtn) getBtn.disabled = sats <= 0;
|
||||
const errEl = $('tipPickerError');
|
||||
if (errEl) errEl.textContent = '';
|
||||
});
|
||||
|
||||
// ESC closes whichever overlay modal is on top.
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key !== 'Escape') return;
|
||||
const tipM = $('tipModal');
|
||||
if (tipM && tipM.getAttribute('aria-hidden') === 'false') { closeTipModal(); return; }
|
||||
const profileM = $('profileModal');
|
||||
if (profileM && profileM.getAttribute('aria-hidden') === 'false') { closeProfilePage(); return; }
|
||||
const reportM = $('reportModal');
|
||||
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Minified by jsDelivr using Terser v5.37.0.
|
||||
* Original file: /npm/[email protected]/qrcode.js
|
||||
*
|
||||
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
|
||||
*/
|
||||
var qrcode=function(){var t=function(t,r){var e=t,n=g[r],o=null,i=0,a=null,u=[],f={},c=function(t,r){o=function(t){for(var r=new Array(t),e=0;e<t;e+=1){r[e]=new Array(t);for(var n=0;n<t;n+=1)r[e][n]=null}return r}(i=4*e+17),l(0,0),l(i-7,0),l(0,i-7),s(),h(),d(t,r),e>=7&&v(t),null==a&&(a=p(e,n,u)),w(a,r)},l=function(t,r){for(var e=-1;e<=7;e+=1)if(!(t+e<=-1||i<=t+e))for(var n=-1;n<=7;n+=1)r+n<=-1||i<=r+n||(o[t+e][r+n]=0<=e&&e<=6&&(0==n||6==n)||0<=n&&n<=6&&(0==e||6==e)||2<=e&&e<=4&&2<=n&&n<=4)},h=function(){for(var t=8;t<i-8;t+=1)null==o[t][6]&&(o[t][6]=t%2==0);for(var r=8;r<i-8;r+=1)null==o[6][r]&&(o[6][r]=r%2==0)},s=function(){for(var t=B.getPatternPosition(e),r=0;r<t.length;r+=1)for(var n=0;n<t.length;n+=1){var i=t[r],a=t[n];if(null==o[i][a])for(var u=-2;u<=2;u+=1)for(var f=-2;f<=2;f+=1)o[i+u][a+f]=-2==u||2==u||-2==f||2==f||0==u&&0==f}},v=function(t){for(var r=B.getBCHTypeNumber(e),n=0;n<18;n+=1){var a=!t&&1==(r>>n&1);o[Math.floor(n/3)][n%3+i-8-3]=a}for(n=0;n<18;n+=1){a=!t&&1==(r>>n&1);o[n%3+i-8-3][Math.floor(n/3)]=a}},d=function(t,r){for(var e=n<<3|r,a=B.getBCHTypeInfo(e),u=0;u<15;u+=1){var f=!t&&1==(a>>u&1);u<6?o[u][8]=f:u<8?o[u+1][8]=f:o[i-15+u][8]=f}for(u=0;u<15;u+=1){f=!t&&1==(a>>u&1);u<8?o[8][i-u-1]=f:u<9?o[8][15-u-1+1]=f:o[8][15-u-1]=f}o[i-8][8]=!t},w=function(t,r){for(var e=-1,n=i-1,a=7,u=0,f=B.getMaskFunction(r),c=i-1;c>0;c-=2)for(6==c&&(c-=1);;){for(var g=0;g<2;g+=1)if(null==o[n][c-g]){var l=!1;u<t.length&&(l=1==(t[u]>>>a&1)),f(n,c-g)&&(l=!l),o[n][c-g]=l,-1==(a-=1)&&(u+=1,a=7)}if((n+=e)<0||i<=n){n-=e,e=-e;break}}},p=function(t,r,e){for(var n=A.getRSBlocks(t,r),o=b(),i=0;i<e.length;i+=1){var a=e[i];o.put(a.getMode(),4),o.put(a.getLength(),B.getLengthInBits(a.getMode(),t)),a.write(o)}var u=0;for(i=0;i<n.length;i+=1)u+=n[i].dataCount;if(o.getLengthInBits()>8*u)throw"code length overflow. ("+o.getLengthInBits()+">"+8*u+")";for(o.getLengthInBits()+4<=8*u&&o.put(0,4);o.getLengthInBits()%8!=0;)o.putBit(!1);for(;!(o.getLengthInBits()>=8*u||(o.put(236,8),o.getLengthInBits()>=8*u));)o.put(17,8);return function(t,r){for(var e=0,n=0,o=0,i=new Array(r.length),a=new Array(r.length),u=0;u<r.length;u+=1){var f=r[u].dataCount,c=r[u].totalCount-f;n=Math.max(n,f),o=Math.max(o,c),i[u]=new Array(f);for(var g=0;g<i[u].length;g+=1)i[u][g]=255&t.getBuffer()[g+e];e+=f;var l=B.getErrorCorrectPolynomial(c),h=k(i[u],l.getLength()-1).mod(l);for(a[u]=new Array(l.getLength()-1),g=0;g<a[u].length;g+=1){var s=g+h.getLength()-a[u].length;a[u][g]=s>=0?h.getAt(s):0}}var v=0;for(g=0;g<r.length;g+=1)v+=r[g].totalCount;var d=new Array(v),w=0;for(g=0;g<n;g+=1)for(u=0;u<r.length;u+=1)g<i[u].length&&(d[w]=i[u][g],w+=1);for(g=0;g<o;g+=1)for(u=0;u<r.length;u+=1)g<a[u].length&&(d[w]=a[u][g],w+=1);return d}(o,n)};f.addData=function(t,r){var e=null;switch(r=r||"Byte"){case"Numeric":e=M(t);break;case"Alphanumeric":e=x(t);break;case"Byte":e=m(t);break;case"Kanji":e=L(t);break;default:throw"mode:"+r}u.push(e),a=null},f.isDark=function(t,r){if(t<0||i<=t||r<0||i<=r)throw t+","+r;return o[t][r]},f.getModuleCount=function(){return i},f.make=function(){if(e<1){for(var t=1;t<40;t++){for(var r=A.getRSBlocks(t,n),o=b(),i=0;i<u.length;i++){var a=u[i];o.put(a.getMode(),4),o.put(a.getLength(),B.getLengthInBits(a.getMode(),t)),a.write(o)}var g=0;for(i=0;i<r.length;i++)g+=r[i].dataCount;if(o.getLengthInBits()<=8*g)break}e=t}c(!1,function(){for(var t=0,r=0,e=0;e<8;e+=1){c(!0,e);var n=B.getLostPoint(f);(0==e||t>n)&&(t=n,r=e)}return r}())},f.createTableTag=function(t,r){t=t||2;var e="";e+='<table style="',e+=" border-width: 0px; border-style: none;",e+=" border-collapse: collapse;",e+=" padding: 0px; margin: "+(r=void 0===r?4*t:r)+"px;",e+='">',e+="<tbody>";for(var n=0;n<f.getModuleCount();n+=1){e+="<tr>";for(var o=0;o<f.getModuleCount();o+=1)e+='<td style="',e+=" border-width: 0px; border-style: none;",e+=" border-collapse: collapse;",e+=" padding: 0px; margin: 0px;",e+=" width: "+t+"px;",e+=" height: "+t+"px;",e+=" background-color: ",e+=f.isDark(n,o)?"#000000":"#ffffff",e+=";",e+='"/>';e+="</tr>"}return e+="</tbody>",e+="</table>"},f.createSvgTag=function(t,r,e,n){var o={};"object"==typeof arguments[0]&&(t=(o=arguments[0]).cellSize,r=o.margin,e=o.alt,n=o.title),t=t||2,r=void 0===r?4*t:r,(e="string"==typeof e?{text:e}:e||{}).text=e.text||null,e.id=e.text?e.id||"qrcode-description":null,(n="string"==typeof n?{text:n}:n||{}).text=n.text||null,n.id=n.text?n.id||"qrcode-title":null;var i,a,u,c,g=f.getModuleCount()*t+2*r,l="";for(c="l"+t+",0 0,"+t+" -"+t+",0 0,-"+t+"z ",l+='<svg version="1.1" xmlns="http://www.w3.org/2000/svg"',l+=o.scalable?"":' width="'+g+'px" height="'+g+'px"',l+=' viewBox="0 0 '+g+" "+g+'" ',l+=' preserveAspectRatio="xMinYMin meet"',l+=n.text||e.text?' role="img" aria-labelledby="'+y([n.id,e.id].join(" ").trim())+'"':"",l+=">",l+=n.text?'<title id="'+y(n.id)+'">'+y(n.text)+"</title>":"",l+=e.text?'<description id="'+y(e.id)+'">'+y(e.text)+"</description>":"",l+='<rect width="100%" height="100%" fill="white" cx="0" cy="0"/>',l+='<path d="',a=0;aLine truncated
|
||||
//# sourceMappingURL=/sm/26b4b0d0b1e283d6b3ec9857ac597d7a60c76ac17be1ef4c965f03086de426bb.map
|
||||
Reference in new issue
Block a user