mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-28 05:46:58 +02:00
navigator.clipboard is only exposed in a secure context (https or localhost). On self-hosted instances served over plain http:// it is undefined, so every copy / "copy all" / export button silently failed and left the clipboard empty (GitHub #3781). Add a shared copyText(text): Promise<boolean> helper in @multica/ui/lib/clipboard that prefers the async Clipboard API and falls back to a hidden <textarea> + document.execCommand('copy') for non-secure contexts. Migrate all direct navigator.clipboard.writeText call sites (code blocks, agent transcript copy-all, token / webhook / issue-link copy, etc.) to it, gating success side-effects on the returned boolean, and remove the now-redundant copyMarkdown wrapper. Secure-context users keep the native path unchanged. MUL-3068 Co-authored-by: J <j@multica.ai> Co-authored-by: multica-agent <github@multica.ai>
87 lines
2.7 KiB
TypeScript
87 lines
2.7 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { Check, Copy, Terminal } from "lucide-react";
|
|
import { Card, CardContent } from "@multica/ui/components/ui/card";
|
|
import { CODE_LIGATURE_CLASS } from "@multica/ui/lib/code-style";
|
|
import { cn } from "@multica/ui/lib/utils";
|
|
import { copyText } from "@multica/ui/lib/clipboard";
|
|
import { useT } from "../../i18n";
|
|
|
|
const INSTALL_CMD =
|
|
"curl -fsSL https://raw.githubusercontent.com/multica-ai/multica/main/scripts/install.sh | bash";
|
|
const SETUP_CMD = "multica setup";
|
|
|
|
function CopyButton({ text }: { text: string }) {
|
|
const { t } = useT("onboarding");
|
|
const [copied, setCopied] = useState(false);
|
|
|
|
const handleCopy = () => {
|
|
void copyText(text).then((ok) => {
|
|
if (!ok) return;
|
|
setCopied(true);
|
|
setTimeout(() => setCopied(false), 2000);
|
|
});
|
|
};
|
|
|
|
return (
|
|
<button
|
|
type="button"
|
|
onClick={handleCopy}
|
|
className="shrink-0 rounded p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
|
|
aria-label={t(($) => $.cli_install.copy_aria)}
|
|
>
|
|
{copied ? (
|
|
<Check className="h-3.5 w-3.5 text-success" />
|
|
) : (
|
|
<Copy className="h-3.5 w-3.5" />
|
|
)}
|
|
</button>
|
|
);
|
|
}
|
|
|
|
function Step({ n, label, cmd }: { n: number; label: string; cmd: string }) {
|
|
return (
|
|
<div>
|
|
<p className="mb-1.5 text-xs font-medium text-foreground">
|
|
{n}. {label}
|
|
</p>
|
|
<div className="flex items-start gap-2 rounded-lg bg-muted px-3 py-2.5 font-mono text-sm">
|
|
<Terminal className="mt-0.5 h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
|
<code
|
|
className={cn(
|
|
"min-w-0 flex-1 whitespace-pre-wrap break-all",
|
|
CODE_LIGATURE_CLASS,
|
|
)}
|
|
>
|
|
{cmd}
|
|
</code>
|
|
<CopyButton text={cmd} />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* CLI install instructions — two copy-and-run commands. Hardcoded because
|
|
* there's nothing environmental to infer: step 1 is the public install
|
|
* script, step 2 is the cloud `multica setup` which the CLI itself knows
|
|
* the endpoints for. Local development tests a self-host variant by
|
|
* typing the extended command directly in the terminal; no need to
|
|
* thread env vars through React.
|
|
*/
|
|
export function CliInstallInstructions() {
|
|
const { t } = useT("onboarding");
|
|
return (
|
|
<Card className="w-full">
|
|
<CardContent className="space-y-4 pt-4">
|
|
<p className="text-xs leading-[1.55] text-muted-foreground">
|
|
{t(($) => $.cli_install.intro)}
|
|
</p>
|
|
<Step n={1} label={t(($) => $.cli_install.step1_label)} cmd={INSTALL_CMD} />
|
|
<Step n={2} label={t(($) => $.cli_install.step2_label)} cmd={SETUP_CMD} />
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|