diff --git a/src/popup/views/addressDetail.js b/src/popup/views/addressDetail.js index 169ab10..51dfbc4 100644 --- a/src/popup/views/addressDetail.js +++ b/src/popup/views/addressDetail.js @@ -8,13 +8,10 @@ const { addressTitle, escapeHtml, truncateMiddle, + renderAddressHtml, + attachCopyHandlers, } = require("./helpers"); -const { - state, - currentAddress, - saveState, - currentNetwork, -} = require("../../shared/state"); +const { state, currentAddress, saveState } = require("../../shared/state"); const { formatUsd, getAddressValueUsd } = require("../../shared/prices"); const { fetchRecentTransactions, @@ -33,17 +30,6 @@ const { getSignerForAddress } = require("../../shared/wallet"); let ctx; -const EXT_ICON = - `` + - `` + - `` + - `` + - ``; - -function etherscanAddressLink(address) { - return `${currentNetwork().explorerUrl}/address/${address}`; -} - function show() { state.selectedToken = null; const wallet = state.wallets[state.selectedWallet]; @@ -61,22 +47,18 @@ function show() { img.style.imageRendering = "pixelated"; img.style.borderRadius = "50%"; blockieEl.appendChild(img); - $("address-dot").innerHTML = addressDotHtml(addr.address); - $("address-full").dataset.full = addr.address; - $("address-full").textContent = addr.address; - const addrLink = etherscanAddressLink(addr.address); - $("address-etherscan-link").innerHTML = - `${EXT_ICON}`; + const addrTitle = addressTitle(addr.address, state.wallets); + $("address-line").innerHTML = renderAddressHtml(addr.address, { + title: addrTitle, + ensName: addr.ensName, + }); + $("address-line").dataset.full = addr.address; + attachCopyHandlers($("address-line")); const usdTotal = formatUsd(getAddressValueUsd(addr)); $("address-usd-total").innerHTML = usdTotal || " "; const ensEl = $("address-ens"); - if (addr.ensName) { - ensEl.innerHTML = - addressDotHtml(addr.address) + escapeHtml(addr.ensName); - ensEl.classList.remove("hidden"); - } else { - ensEl.classList.add("hidden"); - } + // ENS is now shown inside renderAddressHtml, hide the separate element + ensEl.classList.add("hidden"); $("address-balances").innerHTML = balanceLinesForAddress( addr, state.trackedTokens, @@ -263,14 +245,6 @@ function renderTransactions(txs) { function init(_ctx) { ctx = _ctx; - $("address-full").addEventListener("click", () => { - const addr = $("address-full").dataset.full; - if (addr) { - navigator.clipboard.writeText(addr); - showFlash("Copied!"); - flashCopyFeedback($("address-full")); - } - }); $("btn-address-back").addEventListener("click", () => { ctx.renderWalletList(); @@ -334,9 +308,9 @@ function init(_ctx) { blockieEl.appendChild(bImg); $("export-privkey-title").textContent = wallet.name + " \u2014 Address " + (state.selectedAddress + 1); - $("export-privkey-dot").innerHTML = addressDotHtml(addr.address); - $("export-privkey-address").textContent = addr.address; - $("export-privkey-address").dataset.full = addr.address; + const exportAddrContainer = $("export-privkey-dot").parentElement; + exportAddrContainer.innerHTML = renderAddressHtml(addr.address); + attachCopyHandlers(exportAddrContainer); $("export-privkey-password").value = ""; $("export-privkey-flash").textContent = ""; $("export-privkey-flash").style.visibility = "hidden"; @@ -390,15 +364,6 @@ function init(_ctx) { } }); - $("export-privkey-address").addEventListener("click", () => { - const full = $("export-privkey-address").dataset.full; - if (full) { - navigator.clipboard.writeText(full); - showFlash("Copied!"); - flashCopyFeedback($("export-privkey-address")); - } - }); - $("btn-export-privkey-back").addEventListener("click", () => { $("export-privkey-value").textContent = ""; $("export-privkey-password").value = ""; diff --git a/src/popup/views/addressToken.js b/src/popup/views/addressToken.js index 9614f61..7b9d58a 100644 --- a/src/popup/views/addressToken.js +++ b/src/popup/views/addressToken.js @@ -11,13 +11,10 @@ const { escapeHtml, truncateMiddle, balanceLine, + renderAddressHtml, + attachCopyHandlers, } = require("./helpers"); -const { - state, - currentAddress, - saveState, - currentNetwork, -} = require("../../shared/state"); +const { state, currentAddress, saveState } = require("../../shared/state"); const { TOKEN_BY_ADDRESS, resolveSymbol } = require("../../shared/tokenList"); const { formatUsd, @@ -39,21 +36,6 @@ const makeBlockie = require("ethereum-blockies-base64"); let ctx; -const EXT_ICON = - `` + - `` + - `` + - `` + - ``; - -function etherscanAddressLink(address) { - return `${currentNetwork().explorerUrl}/address/${address}`; -} - -function etherscanTokenLink(tokenContract, holderAddress) { - return `${currentNetwork().explorerUrl}/token/${tokenContract}?a=${holderAddress}`; -} - function isoDate(timestamp) { const d = new Date(timestamp * 1000); const pad = (n) => String(n).padStart(2, "0"); @@ -157,15 +139,13 @@ function show() { blockieEl.appendChild(img); // Address line - $("address-token-dot").innerHTML = addressDotHtml(addr.address); - $("address-token-full").dataset.full = addr.address; - $("address-token-full").textContent = addr.address; - const addrLink = - tokenId !== "ETH" - ? etherscanTokenLink(tokenId, addr.address) - : etherscanAddressLink(addr.address); - $("address-token-etherscan-link").innerHTML = - `${EXT_ICON}`; + const addrTitle = addressTitle(addr.address, state.wallets); + $("address-token-line").innerHTML = renderAddressHtml(addr.address, { + title: addrTitle, + ensName: addr.ensName, + }); + $("address-token-line").dataset.full = addr.address; + attachCopyHandlers($("address-token-line")); // USD total for this token only const usdVal = price ? amount * price : null; @@ -205,15 +185,9 @@ function show() { ? knownToken.decimals : null; const tokenHolders = tb && tb.holders != null ? tb.holders : null; - const dot = addressDotHtml(tokenId); - const tokenLink = `${currentNetwork().explorerUrl}/token/${escapeHtml(tokenId)}`; const projectUrl = knownToken && knownToken.url ? knownToken.url : null; let infoHtml = `
Contract Address
`; - infoHtml += - `
${dot}` + - `${escapeHtml(tokenId)}` + - `${EXT_ICON}` + - `
`; + infoHtml += `
${renderAddressHtml(tokenId)}
`; if (tokenName) infoHtml += `
Name: ${tokenName}
`; if (tokenSymbol) @@ -225,6 +199,7 @@ function show() { if (projectUrl) infoHtml += `
Website: ${escapeHtml(projectUrl)}
`; contractInfo.innerHTML = infoHtml; + attachCopyHandlers(contractInfo); contractInfo.classList.remove("hidden"); } else { contractInfo.innerHTML = ""; @@ -346,15 +321,6 @@ function renderTransactions(txs) { function init(_ctx) { ctx = _ctx; - $("address-token-full").addEventListener("click", () => { - const addr = $("address-token-full").dataset.full; - if (addr) { - navigator.clipboard.writeText(addr); - showFlash("Copied!"); - flashCopyFeedback($("address-token-full")); - } - }); - $("address-token-contract-info").addEventListener("click", (e) => { const copyEl = e.target.closest("[data-copy]"); if (copyEl) { @@ -392,26 +358,11 @@ function init(_ctx) { $("send-token").classList.add("hidden"); let staticHtml = `
${escapeHtml(currentSymbol)}
`; if (tokenId !== "ETH") { - const dot = addressDotHtml(tokenId); - const link = `${currentNetwork().explorerUrl}/token/${tokenId}`; - const extLink = `${EXT_ICON}`; - staticHtml += - `
${dot}` + - `${escapeHtml(tokenId)}` + - extLink + - `
`; + staticHtml += `
${renderAddressHtml(tokenId)}
`; } $("send-token-static").innerHTML = staticHtml; $("send-token-static").classList.remove("hidden"); - // Attach copy handler for the contract address - const copyEl = $("send-token-static").querySelector("[data-copy]"); - if (copyEl) { - copyEl.addEventListener("click", () => { - navigator.clipboard.writeText(copyEl.dataset.copy); - showFlash("Copied!"); - flashCopyFeedback(copyEl); - }); - } + attachCopyHandlers($("send-token-static")); updateSendBalance(); resetSendValidation(); showView("send"); diff --git a/src/popup/views/approval.js b/src/popup/views/approval.js index 2989d03..425a6f4 100644 --- a/src/popup/views/approval.js +++ b/src/popup/views/approval.js @@ -1,11 +1,12 @@ const { $, - addressDotHtml, addressTitle, escapeHtml, showView, showError, hideError, + renderAddressHtml, + attachCopyHandlers, } = require("./helpers"); const { state, saveState, currentNetwork } = require("../../shared/state"); const { formatEther, formatUnits, Interface, toUtf8String } = require("ethers"); @@ -17,28 +18,11 @@ const uniswap = require("../../shared/uniswap"); const runtime = typeof browser !== "undefined" ? browser.runtime : chrome.runtime; -const EXT_ICON = - `` + - `` + - `` + - `` + - ``; - const erc20Iface = new Interface(ERC20_ABI); function approvalAddressHtml(address) { - const dot = addressDotHtml(address); - const link = `${currentNetwork().explorerUrl}/address/${address}`; - const extLink = `${EXT_ICON}`; const title = addressTitle(address, state.wallets); - let html = ""; - if (title) { - html += `
${dot}${escapeHtml(title)}
`; - html += `
${escapeHtml(address)}${extLink}
`; - } else { - html += `
${dot}${escapeHtml(address)}${extLink}
`; - } - return html; + return renderAddressHtml(address, { title }); } function formatTxValue(val) { @@ -53,10 +37,6 @@ function tokenLabel(address) { return t ? t.symbol : null; } -function etherscanTokenLink(address) { - return `${currentNetwork().explorerUrl}/token/${address}`; -} - // Try to decode calldata using known ABIs. // Returns { name, description, details } or null. function decodeCalldata(data, toAddress) { @@ -235,10 +215,6 @@ function showTxApproval(details) { toHtml += `
${escapeHtml(symbol)}
`; } toHtml += approvalAddressHtml(toAddr); - if (symbol) { - const link = etherscanTokenLink(toAddr); - toHtml = toHtml.replace("", "") + ""; // approvalAddressHtml already has etherscan link - } $("approve-tx-to").innerHTML = toHtml; } else { $("approve-tx-to").innerHTML = escapeHtml("(contract creation)"); @@ -266,12 +242,9 @@ function showTxApproval(details) { detailsHtml += `
${escapeHtml(d.label)}
`; if (d.address) { if (d.isToken) { - const tLink = etherscanTokenLink(d.address); detailsHtml += `
${escapeHtml(tokenLabel(d.address) || "Unknown token")}
`; - detailsHtml += approvalAddressHtml(d.address); - } else { - detailsHtml += approvalAddressHtml(d.address); } + detailsHtml += approvalAddressHtml(d.address); } else { detailsHtml += `
${escapeHtml(d.value)}
`; } @@ -295,6 +268,7 @@ function showTxApproval(details) { hideError("approve-tx-error"); showView("approve-tx"); + attachCopyHandlers("view-approve-tx"); } function decodeHexMessage(hex) { @@ -392,6 +366,7 @@ function showSignApproval(details) { $("btn-approve-sign").classList.remove("text-muted"); showView("approve-sign"); + attachCopyHandlers("view-approve-sign"); } function show(id) { @@ -419,6 +394,7 @@ function show(id) { $("approve-address").innerHTML = approvalAddressHtml( state.activeAddress, ); + attachCopyHandlers("view-approve-site"); $("approve-remember").checked = state.rememberSiteChoice; }); } diff --git a/src/popup/views/confirmTx.js b/src/popup/views/confirmTx.js index be61e71..cfd8960 100644 --- a/src/popup/views/confirmTx.js +++ b/src/popup/views/confirmTx.js @@ -17,8 +17,9 @@ const { showFlash, flashCopyFeedback, addressTitle, - addressDotHtml, escapeHtml, + renderAddressHtml, + attachCopyHandlers, } = require("./helpers"); const { state, currentNetwork } = require("../../shared/state"); const { getSignerForAddress } = require("../../shared/wallet"); @@ -34,13 +35,6 @@ const { log } = require("../../shared/log"); const makeBlockie = require("ethereum-blockies-base64"); const txStatus = require("./txStatus"); -const EXT_ICON = - `` + - `` + - `` + - `` + - ``; - let pendingTx = null; function restore() { @@ -50,14 +44,6 @@ function restore() { } } -function etherscanTokenLink(address) { - return `${currentNetwork().explorerUrl}/token/${address}`; -} - -function etherscanAddressLink(address) { - return `${currentNetwork().explorerUrl}/address/${address}`; -} - function blockieHtml(address) { const src = makeBlockie(address); return ``; @@ -65,22 +51,10 @@ function blockieHtml(address) { function confirmAddressHtml(address, ensName, title) { const blockie = blockieHtml(address); - const dot = addressDotHtml(address); - const link = etherscanAddressLink(address); - const extLink = `${EXT_ICON}`; - let html = `
${blockie}
`; - if (title) { - html += `
${dot}${escapeHtml(title)}
`; - } - if (ensName) { - html += `
${title ? "" : dot}${escapeHtml(ensName)}
`; - } - html += - `
${title || ensName ? "" : dot}` + - `${escapeHtml(address)}` + - extLink + - `
`; - return html; + return ( + `
${blockie}
` + + renderAddressHtml(address, { title, ensName }) + ); } function valueWithUsd(text, usdAmount) { @@ -107,23 +81,12 @@ function show(txInfo) { // Token contract section (ERC-20 only) const tokenSection = $("confirm-token-section"); if (isErc20) { - const dot = addressDotHtml(txInfo.token); - const link = etherscanTokenLink(txInfo.token); - $("confirm-token-contract").innerHTML = - `
${dot}` + - `${escapeHtml(txInfo.token)}` + - `${EXT_ICON}` + - `
`; + $("confirm-token-contract").innerHTML = renderAddressHtml( + txInfo.token, + {}, + ); tokenSection.classList.remove("hidden"); - // Attach click-to-copy on the contract address - const copyEl = tokenSection.querySelector("[data-copy]"); - if (copyEl) { - copyEl.onclick = () => { - navigator.clipboard.writeText(copyEl.dataset.copy); - showFlash("Copied!"); - flashCopyFeedback(copyEl); - }; - } + attachCopyHandlers(tokenSection); } else { tokenSection.classList.add("hidden"); } @@ -243,6 +206,7 @@ function show(txInfo) { $("confirm-fee-amount").textContent = "Estimating..."; state.viewData = { pendingTx: txInfo }; showView("confirm-tx"); + attachCopyHandlers("view-confirm-tx"); // Reset async warnings to hidden (space always reserved, no layout shift) $("confirm-recipient-warning").style.visibility = "hidden"; diff --git a/src/popup/views/helpers.js b/src/popup/views/helpers.js index 450c98e..fe8441c 100644 --- a/src/popup/views/helpers.js +++ b/src/popup/views/helpers.js @@ -6,7 +6,7 @@ const { getPrice, getAddressValueUsd, } = require("../../shared/prices"); -const { state, saveState } = require("../../shared/state"); +const { state, saveState, currentNetwork } = require("../../shared/state"); // When views are added, removed, or transitions between them change, // update the view-navigation documentation in README.md to match. @@ -208,21 +208,9 @@ function addressTitle(address, wallets) { // Render an address with color dot, optional ENS name, optional title, // and optional truncation. Title and ENS are shown as bold labels above // the full address. +// Delegates to renderAddressHtml for consistent output. function formatAddressHtml(address, ensName, maxLen, title) { - const dot = addressDotHtml(address); - const displayAddr = maxLen ? truncateMiddle(address, maxLen) : address; - if (title || ensName) { - let html = ""; - if (title) { - html += `
${dot}${escapeHtml(title)}
`; - } - if (ensName) { - html += `
${title ? "" : dot}${escapeHtml(ensName)}
`; - } - html += `
${escapeHtml(displayAddr)}
`; - return html; - } - return `
${dot}${escapeHtml(displayAddr)}
`; + return renderAddressHtml(address, { title, ensName, maxLen }); } function isoDate(timestamp) { @@ -281,6 +269,91 @@ function timeAgo(timestamp) { return years + " year" + (years !== 1 ? "s" : "") + " ago"; } +// Shared external-link icon SVG used across all views. +const EXT_ICON = + `` + + `` + + `` + + `` + + ``; + +function etherscanAddressUrl(address) { + return `${currentNetwork().explorerUrl}/address/${address}`; +} + +function etherscanLinkHtml(url) { + return ( + `${EXT_ICON}` + ); +} + +// Render a copyable text span with dashed underline affordance. +// The caller must attach click handlers via attachCopyHandlers() or +// manually wire up [data-copy] elements after inserting the HTML. +function copyableHtml(text, extraClass) { + const cls = + "underline decoration-dashed cursor-pointer" + + (extraClass ? " " + extraClass : ""); + return `${escapeHtml(text)}`; +} + +// Attach click-to-copy handlers to all [data-copy] elements within +// a container. Safe to call multiple times on the same container. +function attachCopyHandlers(container) { + const root = + typeof container === "string" + ? document.getElementById(container) + : container; + if (!root) return; + root.querySelectorAll("[data-copy]").forEach((el) => { + el.onclick = () => { + navigator.clipboard.writeText(el.dataset.copy); + showFlash("Copied!"); + flashCopyFeedback(el); + }; + }); +} + +// Unified address rendering. +// +// Produces consistent HTML for any Ethereum address: +// • Color dot +// • Optional title (e.g. "Wallet 1 — Address 2") shown bold above address +// • Optional ENS name shown bold above address +// • Full address (or truncated via maxLen) with dashed-underline click-to-copy +// • Etherscan external link icon +// +// Options object: +// title — wallet title string (from addressTitle) +// ensName — ENS name string +// maxLen — if set, truncate address display (min 32 chars enforced) +// noLink — if true, omit etherscan link +// +// After inserting the returned HTML into the DOM, call +// attachCopyHandlers() on the parent to wire up click-to-copy. +function renderAddressHtml(address, opts) { + const { title, ensName, maxLen, noLink } = opts || {}; + const dot = addressDotHtml(address); + const displayAddr = maxLen ? truncateMiddle(address, maxLen) : address; + const link = etherscanAddressUrl(address); + const extLink = noLink ? "" : etherscanLinkHtml(link); + + let html = ""; + if (title) { + html += `
${dot}${escapeHtml(title)}
`; + } + if (ensName) { + html += `
${title ? "" : dot}${escapeHtml(ensName)}
`; + } + if (title || ensName) { + html += `
${copyableHtml(displayAddr, "break-all")}${extLink}
`; + } else { + html += `
${dot}${copyableHtml(displayAddr, "break-all")}${extLink}
`; + } + return html; +} + function flashCopyFeedback(el) { if (!el) return; el.classList.remove("copy-flash-fade"); @@ -308,6 +381,12 @@ module.exports = { escapeHtml, addressTitle, formatAddressHtml, + renderAddressHtml, + copyableHtml, + attachCopyHandlers, + etherscanAddressUrl, + etherscanLinkHtml, + EXT_ICON, truncateMiddle, isoDate, timeAgo, diff --git a/src/popup/views/home.js b/src/popup/views/home.js index 68171d3..e6b0706 100644 --- a/src/popup/views/home.js +++ b/src/popup/views/home.js @@ -10,13 +10,10 @@ const { addressTitle, escapeHtml, truncateMiddle, + renderAddressHtml, + attachCopyHandlers, } = require("./helpers"); -const { - state, - saveState, - currentAddress, - currentNetwork, -} = require("../../shared/state"); +const { state, saveState, currentAddress } = require("../../shared/state"); const { updateSendBalance, renderSendTokenSelect, @@ -74,28 +71,12 @@ function renderTotalValue() { } } -const EXT_ICON = - `` + - `` + - `` + - `` + - ``; - function renderActiveAddress() { const el = $("active-address-display"); if (!el) return; if (state.activeAddress) { - const addr = state.activeAddress; - const dot = addressDotHtml(addr); - const link = `${currentNetwork().explorerUrl}/address/${addr}`; - el.innerHTML = - `${dot}${escapeHtml(addr)}` + - `${EXT_ICON}`; - $("active-addr-copy").addEventListener("click", (e) => { - navigator.clipboard.writeText(addr); - showFlash("Copied!"); - flashCopyFeedback(e.currentTarget); - }); + el.innerHTML = renderAddressHtml(state.activeAddress); + attachCopyHandlers(el); } else { el.textContent = ""; } diff --git a/src/popup/views/receive.js b/src/popup/views/receive.js index 59bac3e..4fdb930 100644 --- a/src/popup/views/receive.js +++ b/src/popup/views/receive.js @@ -5,17 +5,11 @@ const { flashCopyFeedback, formatAddressHtml, addressTitle, + attachCopyHandlers, } = require("./helpers"); const { state, currentAddress, currentNetwork } = require("../../shared/state"); const QRCode = require("qrcode"); -const EXT_ICON = - `` + - `` + - `` + - `` + - ``; - function show() { const addr = currentAddress(); const address = addr ? addr.address : ""; @@ -25,11 +19,8 @@ function show() { ? formatAddressHtml(address, ensName, null, title) : ""; $("receive-address-block").dataset.full = address; - const net = currentNetwork(); - const link = address ? `${net.explorerUrl}/address/${address}` : ""; - $("receive-etherscan-link").innerHTML = link - ? `${EXT_ICON}` - : ""; + // Etherscan link is now included in formatAddressHtml via renderAddressHtml + $("receive-etherscan-link").innerHTML = ""; if (address) { QRCode.toCanvas($("receive-qr"), address, { width: 200, @@ -62,18 +53,10 @@ function show() { warningEl.style.visibility = "hidden"; } showView("receive"); + attachCopyHandlers("view-receive"); } function init(ctx) { - $("receive-address-block").addEventListener("click", (e) => { - const addr = $("receive-address-block").dataset.full; - if (addr) { - navigator.clipboard.writeText(addr); - showFlash("Copied!"); - flashCopyFeedback(e.currentTarget); - } - }); - $("btn-receive-copy").addEventListener("click", () => { const addr = $("receive-address-block").dataset.full; if (addr) { diff --git a/src/popup/views/send.js b/src/popup/views/send.js index ada1370..e5db64e 100644 --- a/src/popup/views/send.js +++ b/src/popup/views/send.js @@ -3,11 +3,12 @@ const { $, showFlash, - addressDotHtml, addressTitle, escapeHtml, + renderAddressHtml, + attachCopyHandlers, } = require("./helpers"); -const { state, currentAddress, currentNetwork } = require("../../shared/state"); +const { state, currentAddress } = require("../../shared/state"); let ctx; const { getProvider } = require("../../shared/balances"); const { KNOWN_SYMBOLS, resolveSymbol } = require("../../shared/tokenList"); @@ -113,13 +114,6 @@ function updateToValidation() { } } -const EXT_ICON = - `` + - `` + - `` + - `` + - ``; - function isSpoofedToken(t) { const upper = (t.symbol || "").toUpperCase(); if (!KNOWN_SYMBOLS.has(upper)) return false; @@ -148,24 +142,12 @@ function renderSendTokenSelect(addr) { function updateSendBalance() { const addr = currentAddress(); if (!addr) return; - const dot = addressDotHtml(addr.address); - const link = `${currentNetwork().explorerUrl}/address/${addr.address}`; - const extLink = `${EXT_ICON}`; const title = addressTitle(addr.address, state.wallets); - let fromHtml = ""; - if (title) { - fromHtml += `
${dot}${escapeHtml(title)}
`; - if (addr.ensName) { - fromHtml += `
${escapeHtml(addr.ensName)}
`; - } - fromHtml += `
${escapeHtml(addr.address)}${extLink}
`; - } else if (addr.ensName) { - fromHtml += `
${dot}${escapeHtml(addr.ensName)}
`; - fromHtml += `
${escapeHtml(addr.address)}${extLink}
`; - } else { - fromHtml += `
${dot}${escapeHtml(addr.address)}${extLink}
`; - } - $("send-from").innerHTML = fromHtml; + $("send-from").innerHTML = renderAddressHtml(addr.address, { + title, + ensName: addr.ensName, + }); + attachCopyHandlers($("send-from")); const token = state.selectedToken || $("send-token").value; if (token === "ETH") { $("send-balance").textContent = diff --git a/src/popup/views/transactionDetail.js b/src/popup/views/transactionDetail.js index 306a9ac..13c2c51 100644 --- a/src/popup/views/transactionDetail.js +++ b/src/popup/views/transactionDetail.js @@ -6,11 +6,14 @@ const { showView, showFlash, flashCopyFeedback, - addressDotHtml, addressTitle, escapeHtml, isoDate, timeAgo, + renderAddressHtml, + attachCopyHandlers, + copyableHtml, + etherscanLinkHtml, } = require("./helpers"); const { state, currentNetwork } = require("../../shared/state"); const { formatEther, formatUnits } = require("ethers"); @@ -18,13 +21,6 @@ const makeBlockie = require("ethereum-blockies-base64"); const { log, debugFetch } = require("../../shared/log"); const { decodeCalldata } = require("./approval"); -const EXT_ICON = - `` + - `` + - `` + - `` + - ``; - let ctx; /** @@ -46,52 +42,17 @@ function getTransactionType(tx) { return "Native ETH Transfer"; } -function copyableHtml(text, extraClass) { - const cls = - "underline decoration-dashed cursor-pointer" + - (extraClass ? " " + extraClass : ""); - return `${escapeHtml(text)}`; -} - function blockieHtml(address) { const src = makeBlockie(address); return ``; } -function etherscanLinkHtml(url) { - return ( - `${EXT_ICON}` - ); -} - function txAddressHtml(address, ensName, title) { const blockie = blockieHtml(address); - const dot = addressDotHtml(address); - const link = `${currentNetwork().explorerUrl}/address/${address}`; - const extLink = etherscanLinkHtml(link); - let html = `
${blockie}
`; - if (title) { - html += `
${escapeHtml(title)}
`; - } - if (ensName) { - html += - `
${dot}` + - copyableHtml(ensName, "") + - `
` + - `
${dot}` + - copyableHtml(address, "break-all") + - extLink + - `
`; - } else { - html += - `
${dot}` + - copyableHtml(address, "break-all") + - extLink + - `
`; - } - return html; + return ( + `
${blockie}
` + + renderAddressHtml(address, { title, ensName }) + ); } function txHashHtml(hash) { @@ -210,17 +171,7 @@ function render() { copyableHtml(isoStr) + " (" + escapeHtml(timeAgo(tx.timestamp)) + ")"; $("tx-detail-status").textContent = tx.isError ? "Failed" : "Success"; showView("transaction"); - - document - .getElementById("view-transaction") - .querySelectorAll("[data-copy]") - .forEach((el) => { - el.onclick = () => { - navigator.clipboard.writeText(el.dataset.copy); - showFlash("Copied!"); - flashCopyFeedback(el); - }; - }); + attachCopyHandlers("view-transaction"); } function showDetailField(sectionId, contentId, value) { @@ -355,19 +306,14 @@ async function loadFullTxDetails(txHash, toAddress, isContractCall) { detailsHtml += `
`; detailsHtml += `
${escapeHtml(d.label)}
`; if (d.address && d.isToken) { - // Token entry: show symbol on its own line, then dot + address + Etherscan link - const dot = addressDotHtml(d.address); + // Token entry: show symbol on its own line, then address via shared renderer const tokenSymbol = d.value.match(/^(\S+)\s*\(/)?.[1]; if (tokenSymbol) { detailsHtml += `
${escapeHtml(tokenSymbol)}
`; } - const etherscanUrl = `${currentNetwork().explorerUrl}/token/${d.address}`; - detailsHtml += `
${dot}${copyableHtml(d.address, "break-all")}${etherscanLinkHtml(etherscanUrl)}
`; + detailsHtml += renderAddressHtml(d.address); } else if (d.address) { - // Protocol/contract entry: show name + Etherscan link - const dot = addressDotHtml(d.address); - const etherscanUrl = `${currentNetwork().explorerUrl}/address/${d.address}`; - detailsHtml += `
${dot}${copyableHtml(d.value, "break-all")}${etherscanLinkHtml(etherscanUrl)}
`; + detailsHtml += renderAddressHtml(d.address); } else { detailsHtml += `
${escapeHtml(d.value)}
`; } @@ -394,13 +340,7 @@ async function loadFullTxDetails(txHash, toAddress, isContractCall) { // Bind copy handlers for new elements (including raw data now outside section) const copyTargets = [section, rawSection].filter(Boolean); for (const container of copyTargets) { - container.querySelectorAll("[data-copy]").forEach((el) => { - el.onclick = () => { - navigator.clipboard.writeText(el.dataset.copy); - showFlash("Copied!"); - flashCopyFeedback(el); - }; - }); + attachCopyHandlers(container); } } catch (e) { log.errorf("loadCalldata failed:", e.message); diff --git a/src/popup/views/txStatus.js b/src/popup/views/txStatus.js index ae155fc..e8cf421 100644 --- a/src/popup/views/txStatus.js +++ b/src/popup/views/txStatus.js @@ -3,24 +3,18 @@ const { $, showView, - showFlash, - flashCopyFeedback, - addressDotHtml, addressTitle, escapeHtml, + renderAddressHtml, + attachCopyHandlers, + copyableHtml, + etherscanLinkHtml, } = require("./helpers"); const { TOKEN_BY_ADDRESS } = require("../../shared/tokenList"); const { state, saveState, currentNetwork } = require("../../shared/state"); const { getProvider } = require("../../shared/balances"); const { log } = require("../../shared/log"); -const EXT_ICON = - `` + - `` + - `` + - `` + - ``; - let ctx; let elapsedTimer = null; let pollTimer = null; @@ -37,50 +31,19 @@ function clearTimers() { } function toAddressHtml(address) { - const dot = addressDotHtml(address); - const link = `${currentNetwork().explorerUrl}/address/${address}`; - const extLink = `${EXT_ICON}`; const title = addressTitle(address, state.wallets); - if (title) { - return ( - `
${dot}${escapeHtml(title)}
` + - `
${escapeHtml(address)}
` + - extLink - ); - } - return `
${dot}${escapeHtml(address)}${extLink}
`; + return renderAddressHtml(address, { title }); } function txHashHtml(hash) { const link = `${currentNetwork().explorerUrl}/tx/${hash}`; - const extLink = `${EXT_ICON}`; - return ( - `${escapeHtml(hash)}` + - extLink - ); + return copyableHtml(hash, "break-all") + etherscanLinkHtml(link); } function blockNumberHtml(blockNumber) { const num = String(blockNumber); const link = `${currentNetwork().explorerUrl}/block/${num}`; - const extLink = `${EXT_ICON}`; - return ( - `${escapeHtml(num)}` + - extLink - ); -} - -function attachCopyHandlers(viewId) { - document - .getElementById(viewId) - .querySelectorAll("[data-copy]") - .forEach((el) => { - el.onclick = () => { - navigator.clipboard.writeText(el.dataset.copy); - showFlash("Copied!"); - flashCopyFeedback(el); - }; - }); + return copyableHtml(num) + etherscanLinkHtml(link); } function showWait(txInfo, txHash) {