PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.9.5
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.9.5
4.9.5 4.9.4 4.9.3 4.9.2 4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 All 203 releases
betterdocs / react-src / admin / mcp / components / CopyField.js

CopyField.js in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 4.9.5, at react-src/admin/mcp/components/CopyField.js

84 lines 3.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import React, { useState } from "react";
2 import { __ } from "@wordpress/i18n";
3 import { IconCheck, IconCopy } from "./Icons";
4
5 /**
6 * A read-only value with a copy button — the page's workhorse, used for the
7 * endpoint URLs, the connection token, the CLI one-liner, the JSON block and
8 * the AI prompt.
9 *
10 * `secret` renders the value masked until the user asks to see it: the
11 * connection token is admin-equivalent, and an admin screenshotting this page
12 * for support should not have to think about redacting it first.
13 */
14 export default function CopyField({ label, value, help, multiline = false, secret = false }) {
15 const [copied, setCopied] = useState(false);
16 const [revealed, setRevealed] = useState(false);
17
18 const copy = () => {
19 if (!value) return;
20 const done = () => {
21 setCopied(true);
22 window.setTimeout(() => setCopied(false), 2000);
23 };
24 // The async Clipboard API needs a secure context; the textarea fallback
25 // covers plain-HTTP installs, where this page still has to work.
26 if (window.navigator.clipboard?.writeText) {
27 window.navigator.clipboard.writeText(value).then(done, fallbackCopy);
28 } else {
29 fallbackCopy();
30 }
31 function fallbackCopy() {
32 const el = document.createElement("textarea");
33 el.value = value;
34 el.setAttribute("readonly", "");
35 el.style.position = "fixed";
36 el.style.opacity = "0";
37 document.body.appendChild(el);
38 el.select();
39 try {
40 document.execCommand("copy");
41 done();
42 } finally {
43 document.body.removeChild(el);
44 }
45 }
46 };
47
48 // A fixed-length mask, not one bullet per character: the real token is 64
49 // characters and would wrap to three lines of dots, and its length is not
50 // something worth showing anyway.
51 const masked = secret && !revealed && value ? "•".repeat(24) : value;
52
53 return (
54 <div className="bd-mcp-field">
55 {label && <span className="bd-mcp-field__label">{label}</span>}
56 <div className={`bd-mcp-field__control${multiline ? " is-block" : ""}`}>
57 {multiline ? (
58 <pre className="bd-mcp-field__pre">{masked}</pre>
59 ) : (
60 <code className="bd-mcp-field__value">{masked}</code>
61 )}
62 <div className="bd-mcp-field__actions">
63 {secret && (
64 <button type="button" className="bda-btn ghost sm" onClick={() => setRevealed((v) => !v)}>
65 {revealed ? __("Hide", "betterdocs") : __("Show", "betterdocs")}
66 </button>
67 )}
68 <button
69 type="button"
70 className="bda-btn ghost sm bd-mcp-field__copy"
71 onClick={copy}
72 disabled={!value}
73 aria-label={label ? `${__("Copy", "betterdocs")} — ${label}` : __("Copy", "betterdocs")}
74 >
75 {copied ? <IconCheck /> : <IconCopy />}
76 {copied ? __("Copied", "betterdocs") : __("Copy", "betterdocs")}
77 </button>
78 </div>
79 </div>
80 {help && <span className="bd-mcp-field__help">{help}</span>}
81 </div>
82 );
83 }
84