Files
openclaw/ui/src/components/copy-button.ts
Shakker 65e12328aa feat: refactor the Control UI architecture
Refactor the Control UI around route-owned page lifecycle and state while preserving existing behavior and design.

Prepared head SHA: bd51b6fa76
Co-authored-by: Shakker <165377636+shakkernerd@users.noreply.github.com>
Reviewed-by: @shakkernerd
2026-07-04 23:19:38 +01:00

92 lines
2.6 KiB
TypeScript

// Control UI chat module implements copy as markdown behavior.
import { html, type TemplateResult } from "lit";
import { copyToClipboard } from "../lib/clipboard.ts";
import { icons } from "./icons.ts";
import "./tooltip.ts";
const COPIED_FOR_MS = 1500;
const ERROR_FOR_MS = 2000;
const COPY_LABEL = "Copy as markdown";
const COPIED_LABEL = "Copied";
const ERROR_LABEL = "Copy failed";
type CopyButtonOptions = {
text: () => string;
label?: string;
};
function setButtonLabel(button: HTMLButtonElement, label: string) {
button.setAttribute("aria-label", label);
}
function createCopyButton(options: CopyButtonOptions): TemplateResult {
const idleLabel = options.label ?? COPY_LABEL;
return html`
<openclaw-tooltip .content=${idleLabel}>
<button
class="btn btn--xs chat-copy-btn"
type="button"
aria-label=${idleLabel}
@click=${async (e: Event) => {
const btn = e.currentTarget as HTMLButtonElement | null;
if (!btn || btn.dataset.copying === "1") {
return;
}
btn.dataset.copying = "1";
btn.setAttribute("aria-busy", "true");
btn.disabled = true;
const copied = await copyToClipboard(options.text());
if (!btn.isConnected) {
return;
}
delete btn.dataset.copying;
btn.removeAttribute("aria-busy");
btn.disabled = false;
if (!copied) {
btn.dataset.error = "1";
setButtonLabel(btn, ERROR_LABEL);
window.setTimeout(() => {
if (!btn.isConnected) {
return;
}
delete btn.dataset.error;
setButtonLabel(btn, idleLabel);
}, ERROR_FOR_MS);
return;
}
btn.dataset.copied = "1";
setButtonLabel(btn, COPIED_LABEL);
window.setTimeout(() => {
if (!btn.isConnected) {
return;
}
delete btn.dataset.copied;
setButtonLabel(btn, idleLabel);
}, COPIED_FOR_MS);
}}
>
<span class="chat-copy-btn__icon" aria-hidden="true">
<span class="chat-copy-btn__icon-copy">${icons.copy}</span>
<span class="chat-copy-btn__icon-check">${icons.check}</span>
</span>
</button>
</openclaw-tooltip>
`;
}
export function renderCopyButton(text: string, label = COPY_LABEL): TemplateResult {
return createCopyButton({ text: () => text, label });
}
export function renderCopyAsMarkdownButton(markdown: string): TemplateResult {
return renderCopyButton(markdown, COPY_LABEL);
}