feat: Added clipboard functionality

This commit is contained in:
Cristian Ditaputratama 2024-10-31 18:09:02 +07:00
parent 4da9c484a5
commit 93fb22f29b
Signed by: ditatompel
GPG key ID: 31D3D06D77950979
9 changed files with 64 additions and 26 deletions

View file

@ -60,6 +60,7 @@ prepare:
bun install --frozen-lockfile
@mkdir -p ./internal/handler/views/assets/js
cp ./node_modules/htmx.org/dist/htmx.min.js ./internal/handler/views/assets/js
cp ./node_modules/clipboard/dist/clipboard.min.js ./internal/handler/views/assets/js
# Compile template
.PHONY: templ

BIN
bun.lockb Executable file

Binary file not shown.

View file

@ -89,7 +89,6 @@ templ Home() {
<div class="group flex flex-col text-center gap-2">
<h2 class="font-semibold text-neutral-200 text-3xl">My Stagenet Public Node</h2>
<p>Stagenet is what you need to learn Monero safely. Stagenet is technically equivalent to mainnet, both in terms of features and consensus rules.</p>
<!-- TODO: add copy to clipboard functionality -->
<div>
<label for="stagenet-p2p" class="sr-only">Stagenet P2P</label>
<div class="flex rounded-lg shadow-sm">
@ -97,7 +96,7 @@ templ Home() {
<span class="text-sm text-neutral-400">P2P</span>
</span>
<input type="text" id="stagenet-p2p" name="stagenet-p2p" class="py-3 px-4 block w-full text-neutral-400 bg-neutral-900 border-neutral-700 shadow-sm rounded-0 text-sm focus:z-10 focus:border-orange-500 focus:ring-orange-500" value="stagenet.xmr.ditatompel.com:38080" readonly/>
<button type="button" class="w-[2.875rem] h-[2.875rem] shrink-0 inline-flex justify-center items-center gap-x-2 text-sm font-semibold rounded-e-md border border-transparent bg-orange-600 text-white hover:bg-orange-500 focus:outline-none focus:bg-orange-700">
<button class="clipboard copy-input" data-clipboard-target="#stagenet-p2p">
Copy
</button>
</div>
@ -109,7 +108,7 @@ templ Home() {
<span class="text-sm text-neutral-400">RPC</span>
</span>
<input type="text" id="stagenet-rpc" name="stagenet-rpc" class="py-3 px-4 block w-full text-neutral-400 bg-neutral-900 border-neutral-700 shadow-sm rounded-0 text-sm focus:z-10 focus:border-orange-500 focus:ring-orange-500" value="stagenet.xmr.ditatompel.com:38089" readonly/>
<button type="button" class="w-[2.875rem] h-[2.875rem] shrink-0 inline-flex justify-center items-center gap-x-2 text-sm font-semibold rounded-e-md border border-transparent bg-orange-600 text-white hover:bg-orange-500 focus:outline-none focus:bg-orange-700">
<button class="clipboard copy-input" data-clipboard-target="#stagenet-rpc">
Copy
</button>
</div>
@ -121,7 +120,7 @@ templ Home() {
<span class="text-sm text-neutral-400">RPC SSL</span>
</span>
<input type="text" id="stagenet-ssl" name="stagenet-ssl" class="py-3 px-4 block w-full text-neutral-400 bg-neutral-900 border-neutral-700 shadow-sm rounded-0 text-sm focus:z-10 focus:border-orange-500 focus:ring-orange-500" value="stagenet.xmr.ditatompel.com:443" readonly/>
<button type="button" class="w-[2.875rem] h-[2.875rem] shrink-0 inline-flex justify-center items-center gap-x-2 text-sm font-semibold rounded-e-md border border-transparent bg-orange-600 text-white hover:bg-orange-500 focus:outline-none focus:bg-orange-700">
<button class="clipboard copy-input" data-clipboard-target="#stagenet-ssl">
Copy
</button>
</div>
@ -131,7 +130,6 @@ templ Home() {
<div class="group flex flex-col text-center gap-2">
<h2 class="font-semibold text-neutral-200 text-3xl">My Testnet Public Node</h2>
<p>Testnet is the <em>"experimental"</em> network and blockchain where things get released long before mainnet. As a normal user, use mainnet instead.</p>
<!-- TODO: add copy to clipboard functionality -->
<div>
<label for="testnet-p2p" class="sr-only">Testnet P2P</label>
<div class="flex rounded-lg shadow-sm">
@ -139,7 +137,7 @@ templ Home() {
<span class="text-sm text-neutral-400">P2P</span>
</span>
<input type="text" id="testnet-p2p" name="testnet-p2p" class="py-3 px-4 block w-full text-neutral-400 bg-neutral-900 border-neutral-700 shadow-sm rounded-0 text-sm focus:z-10 focus:border-orange-500 focus:ring-orange-500" value="testnet.xmr.ditatompel.com:28080" readonly/>
<button type="button" class="w-[2.875rem] h-[2.875rem] shrink-0 inline-flex justify-center items-center gap-x-2 text-sm font-semibold rounded-e-md border border-transparent bg-orange-600 text-white hover:bg-orange-500 focus:outline-none focus:bg-orange-700">
<button class="clipboard copy-input" data-clipboard-target="#testnet-p2p">
Copy
</button>
</div>
@ -151,7 +149,7 @@ templ Home() {
<span class="text-sm text-neutral-400">RPC</span>
</span>
<input type="text" id="testnet-rpc" name="testnet-rpc" class="py-3 px-4 block w-full text-neutral-400 bg-neutral-900 border-neutral-700 shadow-sm rounded-0 text-sm focus:z-10 focus:border-orange-500 focus:ring-orange-500" value="testnet.xmr.ditatompel.com:28089" readonly/>
<button type="button" class="w-[2.875rem] h-[2.875rem] shrink-0 inline-flex justify-center items-center gap-x-2 text-sm font-semibold rounded-e-md border border-transparent bg-orange-600 text-white hover:bg-orange-500 focus:outline-none focus:bg-orange-700">
<button class="clipboard copy-input" data-clipboard-target="#testnet-rpc">
Copy
</button>
</div>
@ -163,7 +161,7 @@ templ Home() {
<span class="text-sm text-neutral-400">RPC SSL</span>
</span>
<input type="text" id="testnet-ssl" name="testnet-ssl" class="py-3 px-4 block w-full text-neutral-400 bg-neutral-900 border-neutral-700 shadow-sm rounded-0 text-sm focus:z-10 focus:border-orange-500 focus:ring-orange-500" value="testnet.xmr.ditatompel.com:443" readonly/>
<button type="button" class="w-[2.875rem] h-[2.875rem] shrink-0 inline-flex justify-center items-center gap-x-2 text-sm font-semibold rounded-e-md border border-transparent bg-orange-600 text-white hover:bg-orange-500 focus:outline-none focus:bg-orange-700">
<button class="clipboard copy-input" data-clipboard-target="#testnet-ssl">
Copy
</button>
</div>
@ -199,9 +197,8 @@ templ Home() {
<div class="md:basis-3/4">
<p>If you find this project useful, please consider making a donation to help cover the ongoing expenses. Your contribution will go towards ensuring the continued availability of <strong>this website</strong>, my <strong>stagenet</strong> and <strong>testnet</strong> public nodes.</p>
<label for="donate" class="block text-sm text-white font-medium my-2">XMR Donation address:</label>
<!-- TODO: add copy to clipboard functionality -->
<textarea id="donate" class="py-3 px-4 block w-full text-neutral-400 bg-neutral-900 border-neutral-700 rounded-lg text-sm focus:border-neutral-500 focus:ring-neutral-600" rows="3" disabled>8BWYe6GzbNKbxe3D8mPkfFMQA2rViaZJFhWShhZTjJCNG6EZHkXRZCKHiuKmwwe4DXDYF8KKcbGkvNYaiRG3sNt7JhnVp7D</textarea>
<button type="button" class="mt-2 py-2 px-3 inline-flex items-center gap-x-2 text-sm font-bold rounded-full border border-transparent bg-orange-600 text-white hover:bg-orange-500 focus:outline-none">
<textarea id="donate" class="py-3 px-4 block w-full text-neutral-400 bg-neutral-900 border-neutral-700 rounded-lg text-sm focus:border-neutral-500 focus:ring-neutral-600" rows="3" readonly>8BWYe6GzbNKbxe3D8mPkfFMQA2rViaZJFhWShhZTjJCNG6EZHkXRZCKHiuKmwwe4DXDYF8KKcbGkvNYaiRG3sNt7JhnVp7D</textarea>
<button type="button" class="clipboard mt-2 py-2 px-3 inline-flex items-center gap-x-2 text-sm font-bold rounded-full border border-transparent bg-orange-600 text-white hover:bg-orange-500 focus:outline-none disabled:bg-green-600 disabled:opacity-60 disabled:pointer-events-none" data-clipboard-target="#donate" data-success-text="Donation Address Copied! 🤩">
Copy Donation Address
</button>
</div>

File diff suppressed because one or more lines are too long

View file

@ -39,6 +39,7 @@ templ base(m Meta) {
<meta property="og:description" content={ m.Description }/>
<link href={ fmt.Sprintf("/assets/css/main.min.css?t=%d", buildTime) } rel="stylesheet"/>
<script src={ fmt.Sprintf("/assets/js/htmx.min.js?t=%d", buildTime) }></script>
<script src={ fmt.Sprintf("/assets/js/clipboard.min.js?t=%d", buildTime) }></script>
<script src={ fmt.Sprintf("/assets/js/main.min.js?t=%d", buildTime) }></script>
</head>
<body class="bg-neutral-900 text-neutral-400" hx-boost="true" hx-indicator="#hx-indicator-main">

View file

@ -181,14 +181,27 @@ func base(m Meta) templ.Component {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("/assets/js/main.min.js?t=%d", buildTime))
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("/assets/js/clipboard.min.js?t=%d", buildTime))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/layout.templ`, Line: 42, Col: 70}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/layout.templ`, Line: 42, Col: 75}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"></script><script src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("/assets/js/main.min.js?t=%d", buildTime))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/layout.templ`, Line: 43, Col: 70}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"></script></head><body class=\"bg-neutral-900 text-neutral-400\" hx-boost=\"true\" hx-indicator=\"#hx-indicator-main\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
@ -209,12 +222,12 @@ func base(m Meta) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(config.Version)
var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(config.Version)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/layout.templ`, Line: 51, Col: 50}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/layout.templ`, Line: 52, Col: 50}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -242,12 +255,12 @@ func BaseLayout(m Meta, cmp templ.Component) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var14 := templ.GetChildren(ctx)
if templ_7745c5c3_Var14 == nil {
templ_7745c5c3_Var14 = templ.NopComponent
templ_7745c5c3_Var15 := templ.GetChildren(ctx)
if templ_7745c5c3_Var15 == nil {
templ_7745c5c3_Var15 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Var15 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_Var16 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
@ -265,7 +278,7 @@ func BaseLayout(m Meta, cmp templ.Component) templ.Component {
}
return templ_7745c5c3_Err
})
templ_7745c5c3_Err = base(m).Render(templ.WithChildren(ctx, templ_7745c5c3_Var15), templ_7745c5c3_Buffer)
templ_7745c5c3_Err = base(m).Render(templ.WithChildren(ctx, templ_7745c5c3_Var16), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -289,9 +302,9 @@ func BlankLayout(cmp templ.Component) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var16 := templ.GetChildren(ctx)
if templ_7745c5c3_Var16 == nil {
templ_7745c5c3_Var16 = templ.NopComponent
templ_7745c5c3_Var17 := templ.GetChildren(ctx)
if templ_7745c5c3_Var17 == nil {
templ_7745c5c3_Var17 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = cmp.Render(ctx, templ_7745c5c3_Buffer)

View file

@ -17,6 +17,11 @@ a.external {
@apply py-0.5 md:py-3 px-4 md:px-1 border-s-2 md:border-s-0 md:border-b-2 border-orange-400 font-medium text-neutral-200 focus:outline-none;
}
/** home page **/
a.btn-link {
@apply py-1 px-3 mt-2 inline-flex items-center gap-x-1 text-sm font-medium rounded-lg border border-neutral-700 bg-neutral-800 text-white shadow-sm hover:bg-neutral-700;
}
/* my nodes copy input button */
button.copy-input {
@apply px-2 shrink-0 inline-flex justify-center items-center gap-x-2 text-sm font-semibold rounded-e-md border border-transparent bg-orange-600 text-white hover:brightness-125 focus:outline-none focus:bg-orange-700 disabled:opacity-50 disabled:pointer-events-none;
}

View file

@ -1,5 +1,25 @@
import "@preline/collapse";
window.addEventListener("load", () => {
var clipboard = new ClipboardJS(".clipboard");
clipboard.on("success", function (e) {
let btnText = e.trigger.textContent;
let successText = e.trigger.getAttribute("data-success-text");
if (successText === null) {
successText = "Copied 👍";
}
e.trigger.textContent = successText;
e.trigger.disabled = true;
setTimeout(function () {
e.trigger.textContent = btnText;
e.trigger.disabled = false;
}, 1000);
});
clipboard.on("error", function (e) {
console.error("Clipboard error", e.trigger);
});
});
htmx.onLoad(function () {
// Auto init preline JS, see https://preline.co/docs/preline-javascript.html
// This need to be inside `htmx.onLoad` to be work together with hx-boost.

View file

@ -3,6 +3,7 @@
"@preline/collapse": "^2.5.0",
"@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15",
"clipboard": "^2.0.11",
"htmx.org": "^1.9.12",
"tailwindcss": "^3.4.14"
}