Checkout v2: Improve truncation of displayed addresses (#4924)

This commit is contained in:
d11n 2023-05-05 10:00:55 +02:00 committed by GitHub
parent a0bb3ace61
commit 18e34b3cbe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 86 additions and 36 deletions

View file

@ -64,9 +64,9 @@ namespace BTCPayServer.Tests
var qrValue = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-qr-value");
var address = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
var payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
var copyAddress = s.Driver.FindElement(By.Id("Address_BTC")).GetAttribute("value");
var copyAddress = s.Driver.FindElement(By.CssSelector("#Address_BTC .truncate-center-start")).Text;
Assert.Equal($"bitcoin:{address}", payUrl);
Assert.StartsWith("bcrt", s.Driver.FindElement(By.Id("Address_BTC")).GetAttribute("value"));
Assert.StartsWith("bcrt", s.Driver.FindElement(By.CssSelector("#Address_BTC .truncate-center-start")).Text);
Assert.DoesNotContain("lightning=", payUrl);
Assert.Equal(address, copyAddress);
Assert.Equal($"bitcoin:{address.ToUpperInvariant()}", qrValue);
@ -86,7 +86,7 @@ namespace BTCPayServer.Tests
{
payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
Assert.StartsWith("lightning:lnurl", payUrl);
Assert.StartsWith("lnurl", s.Driver.WaitForElement(By.Id("Lightning_BTC")).GetAttribute("value"));
Assert.StartsWith("lnurl", s.Driver.WaitForElement(By.CssSelector("#Lightning_BTC .truncate-center-start")).Text);
s.Driver.ElementDoesNotExist(By.Id("Address_BTC"));
});
@ -101,7 +101,7 @@ namespace BTCPayServer.Tests
qrValue = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-qr-value");
address = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
copyAddress = s.Driver.FindElement(By.Id("Lightning_BTC_LightningLike")).GetAttribute("value");
copyAddress = s.Driver.FindElement(By.CssSelector("#Lightning_BTC_LightningLike .truncate-center-start")).Text;
Assert.Equal($"lightning:{address}", payUrl);
Assert.Equal(address, copyAddress);
Assert.Equal($"lightning:{address.ToUpperInvariant()}", qrValue);
@ -229,8 +229,8 @@ namespace BTCPayServer.Tests
qrValue = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-qr-value");
address = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
var copyAddressOnchain = s.Driver.FindElement(By.Id("Address_BTC")).GetAttribute("value");
var copyAddressLightning = s.Driver.FindElement(By.Id("Lightning_BTC")).GetAttribute("value");
var copyAddressOnchain = s.Driver.FindElement(By.CssSelector("#Address_BTC .truncate-center-start")).Text;
var copyAddressLightning = s.Driver.FindElement(By.CssSelector("#Lightning_BTC .truncate-center-start")).Text;
Assert.StartsWith($"bitcoin:{address}?amount=", payUrl);
Assert.Contains("?amount=", payUrl);
Assert.Contains("&lightning=", payUrl);
@ -297,8 +297,8 @@ namespace BTCPayServer.Tests
qrValue = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-qr-value");
address = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
copyAddressOnchain = s.Driver.FindElement(By.Id("Address_BTC")).GetAttribute("value");
copyAddressLightning = s.Driver.FindElement(By.Id("Lightning_BTC")).GetAttribute("value");
copyAddressOnchain = s.Driver.FindElement(By.CssSelector("#Address_BTC .truncate-center-start")).Text;
copyAddressLightning = s.Driver.FindElement(By.CssSelector("#Lightning_BTC .truncate-center-start")).Text;
Assert.StartsWith($"bitcoin:{address}", payUrl);
Assert.Contains("?lightning=lnurl", payUrl);
Assert.DoesNotContain("amount=", payUrl);

View file

@ -1,10 +1,29 @@
@model BTCPayServer.Components.TruncateCenter.TruncateCenterViewModel
<span class="truncate-center @Model.Classes">
<span class="truncate-center-truncated" @(Model.Truncated != Model.Text ? $"data-bs-toggle=tooltip title={Model.Text}" : "")>@Model.Truncated</span>
<span class="truncate-center-text">@Model.Text</span>
@{
var classes = string.IsNullOrEmpty(Model.Classes) ? string.Empty : Model.Classes.Trim();
@if (Model.Copy) classes += " truncate-center--copy";
@if (Model.Elastic) classes += " truncate-center--elastic";
}
<span class="truncate-center @classes">
@if (Model.IsVue)
{
<span class="truncate-center-truncated" data-bs-toggle="tooltip" :title=@Safe.Json(Model.Text)>
<span class="truncate-center-start" v-text=@Safe.Json(Model.Text)></span>
<span class="truncate-center-end" v-text=@Safe.Json($"{Model.Text}.slice(-{Model.Padding})")></span>
</span>
<span class="truncate-center-text" v-text=@Safe.Json(Model.Text)></span>
}
else
{
<span class="truncate-center-truncated" @(!string.IsNullOrEmpty(Model.Start) ? $"data-bs-toggle=tooltip title={Model.Text}" : "")>
<span class="truncate-center-start">@(Model.Elastic ? Model.Text : $"{Model.Start}…")</span>
<span class="truncate-center-end">@Model.End</span>
</span>
<span class="truncate-center-text">@Model.Text</span>
}
@if (Model.Copy)
{
<button type="button" class="btn btn-link p-0" data-clipboard="@Model.Text">
<button type="button" class="btn btn-link p-0" @(Model.IsVue ? ":" : string.Empty)data-clipboard=@Safe.Json(Model.Text)>
<vc:icon symbol="copy" />
</button>
}

View file

@ -15,7 +15,7 @@ namespace BTCPayServer.Components.TruncateCenter;
/// <returns>HTML with truncated string</returns>
public class TruncateCenter : ViewComponent
{
public IViewComponentResult Invoke(string text, string link = null, string classes = null, int padding = 7, bool copy = true)
public IViewComponentResult Invoke(string text, string link = null, string classes = null, int padding = 7, bool copy = true, bool elastic = false, bool isVue = false)
{
if (string.IsNullOrEmpty(text))
return new HtmlContentViewComponentResult(new StringHtmlContent(string.Empty));
@ -23,11 +23,17 @@ public class TruncateCenter : ViewComponent
{
Classes = classes,
Padding = padding,
Elastic = elastic,
IsVue = isVue,
Copy = copy,
Text = text,
Link = link,
Truncated = text.Length > 2 * padding ? $"{text[..padding]}…{text[^padding..]}" : text
Link = link
};
if (!isVue && text.Length > 2 * padding)
{
vm.Start = text[..padding];
vm.End = text[^padding..];
}
return View(vm);
}
}

View file

@ -3,10 +3,13 @@ namespace BTCPayServer.Components.TruncateCenter
public class TruncateCenterViewModel
{
public string Text { get; set; }
public string Truncated { get; set; }
public string Start { get; set; }
public string End { get; set; }
public string Classes { get; set; }
public string Link { get; set; }
public int Padding { get; set; }
public bool Copy { get; set; }
public bool Elastic { get; set; }
public bool IsVue { get; set; }
}
}

View file

@ -1,4 +1,6 @@
@using BTCPayServer.BIP78.Sender
@using BTCPayServer.Components.TruncateCenter
@using BTCPayServer.Abstractions.TagHelpers
@model BTCPayServer.Models.InvoicingModels.PaymentModel
<template id="bitcoin-method-checkout-template">
@ -11,22 +13,16 @@
<img class="qr-icon" :src="model.cryptoImage" :alt="model.paymentMethodName"/>
</div>
<div v-if="model.btcAddress" class="input-group mt-3">
<div class="form-floating">
<input id="Address_@Model.PaymentMethodId" class="form-control-plaintext" readonly="readonly" :value="model.btcAddress">
<label for="Address_@Model.PaymentMethodId" v-t="{ path: 'address', args: { paymentMethod: model.paymentMethodName }}"></label>
<div class="form-floating" id="Address_@Model.PaymentMethodId">
<vc:truncate-center text="model.btcAddress" is-vue="true" padding="15" elastic="true" classes="form-control-plaintext" />
<label v-t="{ path: 'address', args: { paymentMethod: model.paymentMethodName }}"></label>
</div>
<button type="button" class="btn btn-link" data-clipboard-target="#Address_@Model.PaymentMethodId">
<vc:icon symbol="copy" />
</button>
</div>
<div v-if="lightning" class="input-group mt-3">
<div class="form-floating">
<input id="Lightning_@Model.PaymentMethodId" class="form-control-plaintext" readonly="readonly" :value="lightning" />
<label for="Lightning_@Model.PaymentMethodId" v-t="'lightning'"></label>
<div class="form-floating" id="Lightning_@Model.PaymentMethodId">
<vc:truncate-center text="lightning" is-vue="true" padding="15" elastic="true" classes="form-control-plaintext" />
<label v-t="'lightning'"></label>
</div>
<button type="button" class="btn btn-link" data-clipboard-target="#Lightning_@Model.PaymentMethodId">
<vc:icon symbol="copy" />
</button>
</div>
<a v-if="model.invoiceBitcoinUrl && model.showPayInWalletButton" class="btn btn-primary rounded-pill w-100 mt-4" target="_top" id="PayInWallet"
:href="model.invoiceBitcoinUrl" :title="$t(hasPayjoin ? 'BIP21 payment link with PayJoin support' : 'BIP21 payment link')" v-t="'pay_in_wallet'"></a>

View file

@ -10,13 +10,10 @@
<img class="qr-icon" :src="model.cryptoImage" :alt="model.paymentMethodName"/>
</div>
<div v-if="model.btcAddress" class="input-group mt-3">
<div class="form-floating">
<input id="Lightning_@Model.PaymentMethodId" class="form-control-plaintext" readonly="readonly" :value="model.btcAddress">
<label for="Lightning_@Model.PaymentMethodId" v-t="'lightning'"></label>
<div class="form-floating" id="Lightning_@Model.PaymentMethodId">
<vc:truncate-center text="model.btcAddress" is-vue="true" padding="15" elastic="true" classes="form-control-plaintext" />
<label v-t="'lightning'"></label>
</div>
<button type="button" class="btn btn-link" data-clipboard-target="#Lightning_@Model.PaymentMethodId">
<vc:icon symbol="copy" />
</button>
</div>
<a v-if="model.invoiceBitcoinUrl && model.showPayInWalletButton" class="btn btn-primary rounded-pill w-100 mt-4" target="_top" id="PayInWallet"
:href="model.invoiceBitcoinUrl" v-t="'pay_in_wallet'"></a>

View file

@ -928,6 +928,14 @@ input.ts-wrapper.form-control:not(.ts-hidden-accessible,.ts-inline) {
display: inline-flex;
align-items: center;
gap: var(--btcpay-space-s);
max-width: 100%;
}
.truncate-center--elastic .truncate-center-start {
flex: 0 1 auto;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.truncate-center-id {
@ -941,15 +949,36 @@ input.ts-wrapper.form-control:not(.ts-hidden-accessible,.ts-inline) {
}
.truncate-center a,
.truncate-center button,
.truncate-center-truncated {
.truncate-center button {
line-height: 1;
}
.truncate-center-truncated {
text-align: left;
display: inline-flex;
max-width: 100%;
}
.truncate-center button.btn .icon {
--btn-icon-size: 1em;
}
.truncate-center--elastic .truncate-center-truncated {
max-width: 100%;
}
.truncate-center--elastic.truncate-center--copy .truncate-center-truncated {
max-width: calc(100% - 2em);
}
.truncate-center.form-control-plaintext {
padding-right: 3px;
}
.truncate-center.form-control-plaintext button.btn .icon {
--btn-icon-size: 1.25em;
}
@media screen {
.truncate-center-id {
background: var(--btcpay-neutral-100);