You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@casbin.apache.org by hs...@apache.org on 2026/08/23 14:54:11 UTC
(casbin-gateway) branch master updated: feat(ui): copy a toast message with one click
This is an automated email from the ASF dual-hosted git repository.
hsluoyz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/casbin-gateway.git
The following commit(s) were added to refs/heads/master by this push:
new 2f54fae feat(ui): copy a toast message with one click
2f54fae is described below
commit 2f54fae1ca7def846d484117ff7996b4664af134
Author: Yang Luo <hs...@gmail.com>
AuthorDate: Sun Aug 23 22:54:03 2026 +0800
feat(ui): copy a toast message with one click
---
web/src/Setting.tsx | 11 ++++++++---
web/src/components/shared/misc.tsx | 21 ++++++++++++++++++---
web/src/components/ui/sonner.tsx | 38 ++++++++++++++++++++++----------------
3 files changed, 48 insertions(+), 22 deletions(-)
diff --git a/web/src/Setting.tsx b/web/src/Setting.tsx
index 3787c9c..6e90deb 100644
--- a/web/src/Setting.tsx
+++ b/web/src/Setting.tsx
@@ -17,6 +17,7 @@ import Sdk from "casdoor-js-sdk";
import {toast} from "sonner";
import * as Conf from "@/Conf";
+import {CopyButton} from "@/components/shared/misc";
import type {Account, ApiResponse, ThemeAlgorithm} from "@/types";
/**
@@ -102,14 +103,18 @@ export function goToLink(link: string) {
}
export function showMessage(type: "" | "success" | "error" | "info", text: string) {
+ // Backend messages are worth pasting into a bug report, and a toast is gone
+ // before it can be selected by hand, so every one carries a copy button.
+ const options = {action: <CopyButton value={text} className="text-current" />};
+
if (type === "") {
return;
} else if (type === "success") {
- toast.success(text);
+ toast.success(text, options);
} else if (type === "error") {
- toast.error(text);
+ toast.error(text, options);
} else {
- toast(text);
+ toast(text, options);
}
}
diff --git a/web/src/components/shared/misc.tsx b/web/src/components/shared/misc.tsx
index 97366e6..4d7091e 100644
--- a/web/src/components/shared/misc.tsx
+++ b/web/src/components/shared/misc.tsx
@@ -40,6 +40,18 @@ export function LabelWithTip({
);
}
+function copyBySelection(text: string) {
+ const area = document.createElement("textarea");
+ area.value = text;
+ area.style.position = "fixed";
+ area.style.opacity = "0";
+ document.body.appendChild(area);
+ area.select();
+ const copied = document.execCommand("copy");
+ area.remove();
+ return copied;
+}
+
/** Copies text to the clipboard and confirms it in place for a moment. */
export function CopyButton({
value,
@@ -64,13 +76,16 @@ export function CopyButton({
}, [copied]);
const handleCopy = async() => {
+ const text = String(value ?? "");
try {
- await navigator.clipboard.writeText(String(value ?? ""));
+ await navigator.clipboard.writeText(text);
setCopied(true);
+ return;
} catch {
- // Clipboard access is denied in some embedded contexts; failing quietly
- // is better than a toast the reader cannot act on.
+ // The clipboard API is missing on a plain-HTTP deployment and refuses an
+ // unfocused document, so the older selection copy has to carry those.
}
+ setCopied(copyBySelection(text));
};
return (
diff --git a/web/src/components/ui/sonner.tsx b/web/src/components/ui/sonner.tsx
index 6ed18af..30b7dd8 100644
--- a/web/src/components/ui/sonner.tsx
+++ b/web/src/components/ui/sonner.tsx
@@ -14,27 +14,33 @@
import {Toaster as Sonner, type ToasterProps} from "sonner";
+import {TooltipProvider} from "@/components/ui/tooltip";
+
// Sonner is themed through CSS variables rather than its own light/dark prop, so
// it follows the `.dark` class on <html> exactly like the rest of the UI and
// never disagrees with the page behind it.
function Toaster({...props}: ToasterProps) {
+ // The toaster hangs outside <App/>, so the copy button on a message needs its
+ // own tooltip context.
return (
- <Sonner
- className="toaster group"
- position="top-center"
- richColors
- closeButton
- toastOptions={{
- classNames: {
- toast:
- "group toast group-[.toaster]:bg-popover group-[.toaster]:text-popover-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
- description: "group-[.toast]:text-muted-foreground",
- actionButton: "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
- cancelButton: "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
- },
- }}
- {...props}
- />
+ <TooltipProvider>
+ <Sonner
+ className="toaster group"
+ position="top-center"
+ richColors
+ closeButton
+ toastOptions={{
+ classNames: {
+ toast:
+ "group toast group-[.toaster]:bg-popover group-[.toaster]:text-popover-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
+ description: "group-[.toast]:text-muted-foreground",
+ actionButton: "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
+ cancelButton: "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
+ },
+ }}
+ {...props}
+ />
+ </TooltipProvider>
);
}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@casbin.apache.org
For additional commands, e-mail: commits-help@casbin.apache.org