| |
| |
| |
| |
| |
| |
| function saveAuthToken(token) { |
| const expiryTime = new Date().getTime() + 24 * 60 * 60 * 1000; |
| localStorage.setItem("authToken", token); |
| localStorage.setItem("authTokenExpiry", expiryTime); |
| } |
|
|
| |
| |
| |
| |
| function getAuthToken() { |
| const token = localStorage.getItem("authToken"); |
| const expiry = localStorage.getItem("authTokenExpiry"); |
|
|
| if (!token || !expiry) { |
| return null; |
| } |
|
|
| if (new Date().getTime() > parseInt(expiry)) { |
| localStorage.removeItem("authToken"); |
| localStorage.removeItem("authTokenExpiry"); |
| return null; |
| } |
|
|
| return token; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| function showMessage(elementId, text, isError = false) { |
| let msg = document.getElementById(elementId); |
|
|
| |
| if (!msg) { |
| msg = document.createElement("div"); |
| msg.id = elementId; |
| document.body.appendChild(msg); |
| } |
|
|
| msg.className = `floating-message ${isError ? "error" : "success"}`; |
| msg.innerHTML = text.replace(/\n/g, "<br>"); |
| } |
|
|
| |
| |
| |
| |
| |
| function ensureMessageContainer() { |
| let container = document.querySelector(".message-container"); |
| if (!container) { |
| container = document.createElement("div"); |
| container.className = "message-container"; |
| document.body.appendChild(container); |
| } |
| return container; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| function showGlobalMessage(text, isError = false, timeout = 3000) { |
| const container = ensureMessageContainer(); |
|
|
| const msgElement = document.createElement("div"); |
| msgElement.className = `message ${isError ? "error" : "success"}`; |
| msgElement.textContent = text; |
|
|
| container.appendChild(msgElement); |
|
|
| |
| setTimeout(() => { |
| msgElement.style.animation = "messageOut 0.3s ease-in-out"; |
| setTimeout(() => { |
| msgElement.remove(); |
| |
| if (container.children.length === 0) { |
| container.remove(); |
| } |
| }, 300); |
| }, timeout); |
| } |
|
|
| |
| function initializeTokenHandling(inputId) { |
| |
| const tryFillToken = () => { |
| const tokenInput = document.getElementById(inputId); |
| if (tokenInput) { |
| const authToken = getAuthToken(); |
| if (authToken) { |
| tokenInput.value = authToken; |
| } |
|
|
| |
| tokenInput.addEventListener("change", (e) => { |
| if (e.target.value) { |
| saveAuthToken(e.target.value); |
| } else { |
| localStorage.removeItem("authToken"); |
| localStorage.removeItem("authTokenExpiry"); |
| } |
| }); |
|
|
| return true; |
| } |
| return false; |
| }; |
|
|
| |
| if (!tryFillToken()) { |
| |
| if (document.readyState === 'loading') { |
| document.addEventListener("DOMContentLoaded", tryFillToken); |
| } else { |
| |
| setTimeout(tryFillToken, 0); |
| } |
| } |
| } |
|
|
| |
| async function makeAuthenticatedRequest(url, options = {}) { |
| const tokenId = options.tokenId || "authToken"; |
| const token = document.getElementById(tokenId).value; |
|
|
| if (!token) { |
| showGlobalMessage("请输入 AUTH_TOKEN", true); |
| return null; |
| } |
|
|
| if (!/^[A-Za-z0-9\-._~+/]+=*$/.test(token)) { |
| showGlobalMessage("TOKEN格式无效,请检查是否包含特殊字符", true); |
| return null; |
| } |
|
|
| const defaultOptions = { |
| method: "POST", |
| headers: { |
| Authorization: `Bearer ${token}`, |
| "Content-Type": "application/json", |
| }, |
| }; |
|
|
| try { |
| const response = await fetch(url, { ...defaultOptions, ...options }); |
|
|
| if (!response.ok) { |
| throw new Error(`HTTP error! status: ${response.status}`); |
| } |
|
|
| return await response.json(); |
| } catch (error) { |
| showGlobalMessage(`请求失败: ${error.message}`, true); |
| return null; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| function parseBooleanFromString(str, defaultValue = null) { |
| if (typeof str !== "string") { |
| return defaultValue; |
| } |
|
|
| const lowercaseStr = str.toLowerCase().trim(); |
|
|
| if (lowercaseStr === "true" || lowercaseStr === "1") { |
| return true; |
| } else if (lowercaseStr === "false" || lowercaseStr === "0") { |
| return false; |
| } else { |
| return defaultValue; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| function parseStringFromBoolean(value, defaultValue = null) { |
| if (typeof value !== "boolean") { |
| return defaultValue; |
| } |
|
|
| return value ? "true" : "false"; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| function formatMembershipType(type) { |
| if (!type) return "-"; |
| switch (type) { |
| case "free_trial": |
| return "Pro Trial"; |
| case "pro": |
| return "Pro"; |
| case "free": |
| return "Free"; |
| case "enterprise": |
| return "Business"; |
| default: |
| return type |
| .split("_") |
| .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) |
| .join(" "); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async function copyToClipboard(text, options = {}) { |
| const { |
| showMessage = true, |
| successMessage = "已复制到剪贴板", |
| errorMessage = "复制失败,请手动复制", |
| onSuccess, |
| onError, |
| sourceElement, |
| } = options; |
|
|
| |
| if (typeof text !== "string") { |
| console.error("copyToClipboard: 文本必须是字符串类型"); |
| if (showMessage) { |
| showGlobalMessage("无效的复制内容", true); |
| } |
| if (onError) { |
| onError(new Error("Invalid text type")); |
| } |
| return false; |
| } |
|
|
| |
| if (!text.trim()) { |
| console.warn("copyToClipboard: 尝试复制空文本"); |
| if (showMessage) { |
| showGlobalMessage("没有可复制的内容", true); |
| } |
| if (onError) { |
| onError(new Error("Empty text")); |
| } |
| return false; |
| } |
|
|
| try { |
| |
| if (navigator.clipboard && window.isSecureContext) { |
| await navigator.clipboard.writeText(text); |
| handleCopySuccess(); |
| return true; |
| } else { |
| |
| const success = fallbackCopyToClipboard(text); |
| if (success) { |
| handleCopySuccess(); |
| return true; |
| } else { |
| throw new Error("Fallback copy failed"); |
| } |
| } |
| } catch (error) { |
| console.error("复制到剪贴板失败:", error); |
|
|
| if (showMessage) { |
| showGlobalMessage(errorMessage, true); |
| } |
|
|
| if (onError) { |
| onError(error); |
| } |
|
|
| return false; |
| } |
|
|
| |
| function handleCopySuccess() { |
| if (showMessage) { |
| showGlobalMessage(successMessage); |
| } |
|
|
| if (onSuccess) { |
| onSuccess(); |
| } |
|
|
| |
| if (sourceElement) { |
| addTemporaryClass(sourceElement, "copied", 2000); |
| } |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| function fallbackCopyToClipboard(text) { |
| |
| const textArea = document.createElement("textarea"); |
|
|
| |
| textArea.value = text; |
| textArea.style.position = "fixed"; |
| textArea.style.top = "0"; |
| textArea.style.left = "0"; |
| textArea.style.width = "2em"; |
| textArea.style.height = "2em"; |
| textArea.style.padding = "0"; |
| textArea.style.border = "none"; |
| textArea.style.outline = "none"; |
| textArea.style.boxShadow = "none"; |
| textArea.style.background = "transparent"; |
| textArea.style.opacity = "0"; |
| textArea.style.pointerEvents = "none"; |
|
|
| |
| textArea.style.fontSize = "12pt"; |
|
|
| document.body.appendChild(textArea); |
|
|
| try { |
| |
| textArea.select(); |
| textArea.setSelectionRange(0, text.length); |
|
|
| |
| const successful = document.execCommand("copy"); |
|
|
| |
| document.body.removeChild(textArea); |
|
|
| return successful; |
| } catch (error) { |
| console.error("传统复制方法失败:", error); |
| |
| if (document.body.contains(textArea)) { |
| document.body.removeChild(textArea); |
| } |
| return false; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| function addTemporaryClass(element, className, duration) { |
| if (!element || !className) return; |
|
|
| element.classList.add(className); |
| setTimeout(() => { |
| element.classList.remove(className); |
| }, duration); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async function copyTableCellContent(cell, options = {}) { |
| if (!cell) { |
| console.error("copyTableCellContent: 未提供有效的单元格元素"); |
| return false; |
| } |
|
|
| |
| const text = cell.textContent || cell.innerText || ""; |
|
|
| return copyToClipboard(text.trim(), { |
| ...options, |
| sourceElement: cell, |
| }); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| function createCopyButton(text, options = {}) { |
| const { |
| buttonText = "复制", |
| buttonClass = "copy-button", |
| copiedText = "已复制", |
| resetDelay = 2000, |
| } = options; |
|
|
| const button = document.createElement("button"); |
| button.textContent = buttonText; |
| button.className = buttonClass; |
| button.type = "button"; |
|
|
| button.addEventListener("click", async () => { |
| const originalText = button.textContent; |
|
|
| const success = await copyToClipboard(text, { |
| sourceElement: button, |
| showMessage: true, |
| }); |
|
|
| if (success) { |
| button.textContent = copiedText; |
| button.disabled = true; |
|
|
| setTimeout(() => { |
| button.textContent = originalText; |
| button.disabled = false; |
| }, resetDelay); |
| } |
| }); |
|
|
| return button; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| function isClipboardSupported() { |
| return !!(navigator.clipboard && window.isSecureContext); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async function readFromClipboard(options = {}) { |
| const { showMessage = true, onSuccess, onError } = options; |
|
|
| if (!isClipboardSupported()) { |
| const error = new Error("浏览器不支持剪贴板 API"); |
| if (showMessage) { |
| showGlobalMessage("浏览器不支持读取剪贴板", true); |
| } |
| if (onError) { |
| onError(error); |
| } |
| return null; |
| } |
|
|
| try { |
| const text = await navigator.clipboard.readText(); |
|
|
| if (onSuccess) { |
| onSuccess(text); |
| } |
|
|
| return text; |
| } catch (error) { |
| console.error("读取剪贴板失败:", error); |
|
|
| if (showMessage) { |
| if (error.name === "NotAllowedError") { |
| showGlobalMessage("需要您的许可才能读取剪贴板", true); |
| } else { |
| showGlobalMessage("无法读取剪贴板内容", true); |
| } |
| } |
|
|
| if (onError) { |
| onError(error); |
| } |
|
|
| return null; |
| } |
| } |
|
|