diff --git a/lib/common/static/instance_settings_prefs.js b/lib/common/static/instance_settings_prefs.js index e2c97cb..ecc4eb3 100644 --- a/lib/common/static/instance_settings_prefs.js +++ b/lib/common/static/instance_settings_prefs.js @@ -297,7 +297,21 @@ } input.dataset.envKey = field.key; input.className = "env-field-input"; - row.appendChild(input); + if (field.key === "HUB_BRIDGE_TOKEN" && !field.sensitive && field.type !== "bool" && field.type !== "select") { + const wrap = document.createElement("div"); + wrap.className = "env-field-input-wrap env-field-input-wrap--copy"; + wrap.appendChild(input); + const copyBtn = document.createElement("button"); + copyBtn.type = "button"; + copyBtn.className = "btn-secondary btn-sm env-copy-btn"; + copyBtn.dataset.envCopy = "HUB_BRIDGE_TOKEN"; + copyBtn.title = "复制 Token"; + copyBtn.textContent = "复制"; + wrap.appendChild(copyBtn); + row.appendChild(wrap); + } else { + row.appendChild(input); + } if (field.hidden) { row.hidden = true; row.style.display = "none"; @@ -637,12 +651,58 @@ } } + async function copyTextToClipboard(text) { + const value = String(text || ""); + if (!value) throw new Error("内容为空"); + if (navigator.clipboard && navigator.clipboard.writeText) { + await navigator.clipboard.writeText(value); + return; + } + const ta = document.createElement("textarea"); + ta.value = value; + ta.setAttribute("readonly", ""); + ta.style.position = "fixed"; + ta.style.left = "-9999px"; + document.body.appendChild(ta); + ta.select(); + const ok = document.execCommand("copy"); + document.body.removeChild(ta); + if (!ok) throw new Error("复制失败"); + } + + async function copyEnvFieldValue(key, btn) { + const status = document.getElementById("env-config-status"); + const input = document.querySelector('.env-field-input[data-env-key="' + key + '"]'); + const value = input ? String(input.value || "").trim() : ""; + try { + await copyTextToClipboard(value); + if (btn) { + const old = btn.textContent; + btn.textContent = "已复制"; + btn.disabled = true; + setTimeout(() => { + btn.textContent = old || "复制"; + btn.disabled = false; + }, 1200); + } + setStatus(status, "已复制到剪贴板"); + } catch (e) { + setStatus(status, (e && e.message) || "复制失败", true); + } + } + function installDelegatedHandlers() { if (document.documentElement.dataset.prefsDelegateBound === "1") return; document.documentElement.dataset.prefsDelegateBound = "1"; document.addEventListener("click", (ev) => { const target = ev.target; if (!(target instanceof Element)) return; + const copyBtn = target.closest("[data-env-copy]"); + if (copyBtn) { + ev.preventDefault(); + void copyEnvFieldValue(copyBtn.getAttribute("data-env-copy") || "", copyBtn); + return; + } if (target.closest("#display-prefs-save")) { ev.preventDefault(); void saveDisplayPrefs(); diff --git a/lib/common/static/instance_theme.css b/lib/common/static/instance_theme.css index 726f55e..72db053 100644 --- a/lib/common/static/instance_theme.css +++ b/lib/common/static/instance_theme.css @@ -2684,6 +2684,24 @@ html[data-theme="light"] .journal-detail-img-thumb { box-sizing: border-box; } +.env-field-input-wrap { + display: flex; + align-items: center; + gap: 8px; + min-width: 0; +} + +.env-field-input-wrap .env-field-input { + flex: 1 1 auto; + min-width: 0; +} + +.env-field-input-wrap--copy .env-copy-btn { + flex: 0 0 auto; + margin-top: 0; + white-space: nowrap; +} + .env-config-loading-wrap { padding: 24px; text-align: center; diff --git a/lib/instance/templates/env_config_panel.html b/lib/instance/templates/env_config_panel.html index c3ad052..7b5c194 100644 --- a/lib/instance/templates/env_config_panel.html +++ b/lib/instance/templates/env_config_panel.html @@ -73,6 +73,7 @@ onfocus="this.removeAttribute('readonly')" > {% else %} +
+ {% if field.key == 'HUB_BRIDGE_TOKEN' %} + + {% endif %} +
{% endif %} {% endfor %}