Files
2026-10-29 11:57:29 +01:00

131 lines
5.6 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Generated, do not edit. sha256:864ef84f3268d175b0ba11b7c30d7395b9e816338f8739d95658a7e8e95ba768
import { localDateNl } from '../../shared/time.js';
import {
linkTxEvent, unlinkTxEvent, onchainAsset, linksForTx, linksForEvent, eventCandidatesForTx,
txCandidatesForEvent
} from '../data/tx-links.js';
import { t } from '../i18n/index.js';
import { fmtInt } from './format.js';
import { openPopover, closePopover } from './popover.js';
import { esc } from './escape.js';
const trunc = s => (s && s.length > 22) ? `${s.slice(0, 10)}…${s.slice(-8)}` : (s || '');
const MANUAL = 'manual';
export function matchDot({ matched, method, kind, id }) {
const title = matched ? t('link.' + method) : t('match.add');
return `<span class="tx-link${matched ? ' is-matched' : ''}" data-match-trigger`
+ ` data-match-kind="${kind}" data-match-id="${id}" role="button" tabindex="0"`
+ ` title="${esc(title)}" aria-label="${esc(title)}"></span>`;
}
export function closeMatchPopover() { closePopover(); }
export async function openMatchPopover(anchorEl, { kind, id, assetId, onChange }) {
let changed = false;
const unit = (await onchainAsset() || {}).subunit || '';
const sats = n => `${fmtInt(n)} ${unit}`.trim();
const dateOf = iso => iso ? localDateNl(iso) : t('match.unconfirmed');
let filter = '';
const currentLinks = () => kind === 'tx' ? linksForTx(id) : linksForEvent(id);
const rawCands = () => kind === 'tx' ? eventCandidatesForTx(id, assetId) : txCandidatesForEvent(id);
const pairFor = itemId => kind === 'tx' ? { tx: id, ev: itemId } : { tx: itemId, ev: id };
const eventLine = e => ({
l1: `${esc(t('enums.event_type.' + e.type))} · ${sats(e.amount)}`,
l2: `${dateOf(e.occurredAt)} · ${esc(e.from_name || '-')} → ${esc(e.to_name || '-')}`,
text: `${t('enums.event_type.' + e.type)} ${e.amount} ${e.occurredAt} ${e.from_name || ''} ${e.to_name || ''}`,
});
const txLine = x => ({
l1: `${x.direction === 'in' ? '↓' : '↑'} ${sats(x.valueSats)}`,
l2: `${dateOf(x.blockTime)} · ${esc(x.account_name || '-')}${x.address ? ' · ' + esc(trunc(x.address)) : ''}`,
text: `${x.direction} ${x.valueSats} ${x.blockTime || ''} ${x.account_name || ''} ${x.txid || ''} ${x.address || ''}`,
});
const lineOf = item => kind === 'tx' ? eventLine(item) : txLine(item);
async function candidates() {
const list = await rawCands();
if (!filter) return list;
const f = filter.toLowerCase();
return list.filter(c => lineOf(c).text.toLowerCase().includes(f));
}
async function syncAnchor() {
if (anchorEl.isConnected) anchorEl.classList.toggle('is-matched', (await currentLinks()).length > 0);
}
const { el: pop, position } = openPopover(anchorEl, {
className: 'match-pop',
onClose: () => {
document.querySelectorAll('.tx-link.is-open').forEach(d => d.classList.remove('is-open'));
if (changed && onChange) onChange();
},
});
anchorEl.classList.add('is-open');
async function draw() {
const links = await currentLinks();
const cands = await candidates();
const addLabel = kind === 'tx' ? t('match.toEvent') : t('match.toTx');
const linksHtml = links.length ? `
<div class="t-label match-pop-head">${t('match.linked')}</div>
${links.map(it => {
const ln = lineOf(it);
return `<div class="match-pop-link">
<div class="match-pop-main"><div class="l1">${ln.l1}</div><div class="l2">${ln.l2}</div><span class="match-pop-method">${esc(t('link.' + it.method))}</span></div>
<button class="match-pop-x" data-unlink="${it.id}" title="${esc(t('match.unlink'))}" aria-label="${esc(t('match.unlink'))}">×</button>
</div>`;
}).join('')}
<div class="match-pop-sep"></div>` : '';
const listHtml = cands.length
? cands.map(c => {
const ln = lineOf(c);
return `<div class="match-pop-cand" data-link="${c.id}">
<div class="match-pop-main"><div class="l1">${ln.l1}</div><div class="l2">${ln.l2}</div></div>
</div>`;
}).join('')
: `<div class="match-pop-empty">${t('match.noCandidates')}</div>`;
pop.innerHTML = `${linksHtml}
<div class="t-label match-pop-head">${esc(addLabel)}</div>
<input class="input match-pop-filter" data-filter placeholder="${esc(t('match.filter'))}" value="${esc(filter)}">
<div class="match-pop-list">${listHtml}</div>`;
position();
}
pop.addEventListener('click', async e => {
const un = e.target.closest('[data-unlink]');
if (un) { const p = pairFor(Number(un.dataset.unlink)); await unlinkTxEvent(p.tx, p.ev); changed = true; await syncAnchor(); await draw(); return; }
const lk = e.target.closest('[data-link]');
if (lk) { const p = pairFor(Number(lk.dataset.link)); await linkTxEvent(p.tx, p.ev, MANUAL); changed = true; await syncAnchor(); await draw(); return; }
});
pop.addEventListener('input', e => {
if (e.target.matches('[data-filter]')) { filter = e.target.value; draw(); }
});
await draw();
}
export function wireMatchTriggers(container, optsFactory) {
const open = el => openMatchPopover(el, optsFactory(el.dataset.matchKind, Number(el.dataset.matchId)));
const onClick = e => {
const dot = e.target.closest('[data-match-trigger]');
if (!dot || !container.contains(dot)) return;
e.preventDefault(); e.stopPropagation();
open(dot);
};
const onKey = e => {
if (e.key !== 'Enter' && e.key !== ' ') return;
const dot = e.target.closest('[data-match-trigger]');
if (!dot || !container.contains(dot)) return;
e.preventDefault(); e.stopPropagation();
open(dot);
};
container.addEventListener('click', onClick, true);
container.addEventListener('keydown', onKey, true);
}