Download static/shared.js from xcolorday/cror: direct link, hf CLI and curl.
- Browser
- Download file 15.5 kB
-
https://huggingface.co/spaces/xcolorday/cror/resolve/main/static/shared.js
- Command line
-
hf download hf://spaces/xcolorday/cror/static/shared.js
-
curl -L -o shared.js https://huggingface.co/spaces/xcolorday/cror/resolve/main/static/shared.js
15.5 kB
| // Token 管理功能 | |
| /** | |
| * 保存认证令牌到本地存储 | |
| * @param {string} token - 要保存的认证令牌 | |
| * @returns {void} | |
| */ | |
| function saveAuthToken(token) { | |
| const expiryTime = new Date().getTime() + 24 * 60 * 60 * 1000; // 24小时后过期 | |
| localStorage.setItem("authToken", token); | |
| localStorage.setItem("authTokenExpiry", expiryTime); | |
| } | |
| /** | |
| * 获取存储的认证令牌 | |
| * @returns {string|null} 如果令牌有效则返回令牌,否则返回 null | |
| */ | |
| 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; | |
| } | |
| // 消息显示功能 | |
| /** | |
| * 在指定元素中显示消息 | |
| * @param {string} elementId - 目标元素的 ID | |
| * @param {string} text - 要显示的消息文本 | |
| * @param {boolean} [isError=false] - 是否为错误消息 | |
| * @returns {void} | |
| */ | |
| 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>"); | |
| } | |
| // 确保消息容器存在 | |
| /** | |
| * 确保消息容器存在于 DOM 中 | |
| * @returns {HTMLElement} 消息容器元素 | |
| */ | |
| function ensureMessageContainer() { | |
| let container = document.querySelector(".message-container"); | |
| if (!container) { | |
| container = document.createElement("div"); | |
| container.className = "message-container"; | |
| document.body.appendChild(container); | |
| } | |
| return container; | |
| } | |
| /** | |
| * 显示全局消息提示 | |
| * @param {string} text - 要显示的消息文本 | |
| * @param {boolean} [isError=false] - 是否为错误消息 | |
| * @param {number} [timeout=3000] - 消息显示时长(毫秒) | |
| * @returns {void} | |
| */ | |
| 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); | |
| } | |
| // Token 输入框自动填充和事件绑定 | |
| function initializeTokenHandling(inputId) { | |
| // 直接尝试填充,如果DOM未准备好会在事件中再试一次 | |
| const tryFillToken = () => { | |
| const tokenInput = document.getElementById(inputId); | |
| if (tokenInput) { | |
| const authToken = getAuthToken(); | |
| if (authToken) { | |
| tokenInput.value = authToken; | |
| } | |
| // 绑定change事件 | |
| 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()) { | |
| // 如果元素还不存在,等待DOM加载完成 | |
| if (document.readyState === 'loading') { | |
| document.addEventListener("DOMContentLoaded", tryFillToken); | |
| } else { | |
| // DOM已加载但元素不存在,可能需要等待一下 | |
| setTimeout(tryFillToken, 0); | |
| } | |
| } | |
| } | |
| // API 请求通用处理 | |
| 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; | |
| } | |
| } | |
| /** | |
| * 从字符串解析布尔值 | |
| * @param {string} str - 要解析的字符串 | |
| * @param {boolean|null} defaultValue - 解析失败时的默认值 | |
| * @returns {boolean|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; | |
| } | |
| } | |
| /** | |
| * 将布尔值转换为字符串 | |
| * @param {boolean|undefined|null} value - 要转换的布尔值 | |
| * @param {string} defaultValue - 转换失败时的默认值 | |
| * @returns {string} 转换结果,如果输入无效则返回默认值 | |
| */ | |
| function parseStringFromBoolean(value, defaultValue = null) { | |
| if (typeof value !== "boolean") { | |
| return defaultValue; | |
| } | |
| return value ? "true" : "false"; | |
| } | |
| /** | |
| * 将会员类型代码转换为显示名称 | |
| * @param {string|null} type - 会员类型代码,如 'free_trial', 'pro', 'free', 'enterprise' 等 | |
| * @returns {string} 格式化后的会员类型显示名称 | |
| * @example | |
| * formatMembershipType('free_trial') // 返回 'Pro Trial' | |
| * formatMembershipType('pro') // 返回 'Pro' | |
| * formatMembershipType(null) // 返回 '-' | |
| * formatMembershipType('custom_type') // 返回 'Custom Type' | |
| */ | |
| 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(" "); | |
| } | |
| } | |
| // 复制文本功能 | |
| /** | |
| * 复制文本到剪贴板 | |
| * @param {string} text - 要复制的文本 | |
| * @param {Object} [options={}] - 配置选项 | |
| * @param {boolean} [options.showMessage=true] - 是否显示复制结果消息 | |
| * @param {string} [options.successMessage='已复制到剪贴板'] - 复制成功时的消息 | |
| * @param {string} [options.errorMessage='复制失败,请手动复制'] - 复制失败时的消息 | |
| * @param {Function} [options.onSuccess] - 复制成功时的回调函数 | |
| * @param {Function} [options.onError] - 复制失败时的回调函数 | |
| * @param {HTMLElement} [options.sourceElement] - 触发复制的源元素(用于显示临时状态) | |
| * @returns {Promise<boolean>} 返回复制是否成功 | |
| * @example | |
| * // 基础用法 | |
| * copyToClipboard('Hello World'); | |
| * | |
| * // 自定义消息 | |
| * copyToClipboard('代理地址', { | |
| * successMessage: '代理地址已复制', | |
| * errorMessage: '无法复制代理地址' | |
| * }); | |
| * | |
| * // 带回调函数 | |
| * copyToClipboard('敏感信息', { | |
| * showMessage: false, | |
| * onSuccess: () => console.log('复制成功'), | |
| * onError: (err) => console.error('复制失败:', err) | |
| * }); | |
| * | |
| * // 与按钮配合使用 | |
| * const button = document.getElementById('copyBtn'); | |
| * copyToClipboard('文本内容', { sourceElement: button }); | |
| */ | |
| 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 { | |
| // 优先使用现代 Clipboard API | |
| 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); | |
| } | |
| } | |
| } | |
| /** | |
| * 传统的复制方法(用于不支持 Clipboard API 的浏览器) | |
| * @private | |
| * @param {string} text - 要复制的文本 | |
| * @returns {boolean} 是否复制成功 | |
| */ | |
| 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; | |
| } | |
| } | |
| /** | |
| * 为元素临时添加 CSS 类 | |
| * @private | |
| * @param {HTMLElement} element - 目标元素 | |
| * @param {string} className - 要添加的类名 | |
| * @param {number} duration - 持续时间(毫秒) | |
| */ | |
| function addTemporaryClass(element, className, duration) { | |
| if (!element || !className) return; | |
| element.classList.add(className); | |
| setTimeout(() => { | |
| element.classList.remove(className); | |
| }, duration); | |
| } | |
| /** | |
| * 复制表格单元格内容 | |
| * @param {HTMLElement} cell - 表格单元格元素 | |
| * @param {Object} [options={}] - 复制选项(同 copyToClipboard) | |
| * @returns {Promise<boolean>} 是否复制成功 | |
| * @example | |
| * // 在表格单元格点击事件中使用 | |
| * td.onclick = () => copyTableCellContent(td); | |
| */ | |
| async function copyTableCellContent(cell, options = {}) { | |
| if (!cell) { | |
| console.error("copyTableCellContent: 未提供有效的单元格元素"); | |
| return false; | |
| } | |
| // 获取纯文本内容(去除 HTML 标签) | |
| const text = cell.textContent || cell.innerText || ""; | |
| return copyToClipboard(text.trim(), { | |
| ...options, | |
| sourceElement: cell, | |
| }); | |
| } | |
| /** | |
| * 创建带复制功能的按钮 | |
| * @param {string} text - 要复制的文本 | |
| * @param {Object} [options={}] - 按钮配置选项 | |
| * @param {string} [options.buttonText='复制'] - 按钮文本 | |
| * @param {string} [options.buttonClass='copy-button'] - 按钮CSS类 | |
| * @param {string} [options.copiedText='已复制'] - 复制成功后的按钮文本 | |
| * @param {number} [options.resetDelay=2000] - 按钮文本重置延迟(毫秒) | |
| * @returns {HTMLButtonElement} 创建的按钮元素 | |
| * @example | |
| * // 创建一个复制按钮 | |
| * const copyBtn = createCopyButton('要复制的文本', { | |
| * buttonText: '复制密钥', | |
| * copiedText: '✓ 已复制' | |
| * }); | |
| * document.getElementById('container').appendChild(copyBtn); | |
| */ | |
| 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; | |
| } | |
| /** | |
| * 检查剪贴板 API 是否可用 | |
| * @returns {boolean} 是否支持 Clipboard API | |
| * @example | |
| * if (isClipboardSupported()) { | |
| * console.log('浏览器支持现代剪贴板 API'); | |
| * } | |
| */ | |
| function isClipboardSupported() { | |
| return !!(navigator.clipboard && window.isSecureContext); | |
| } | |
| /** | |
| * 从剪贴板读取文本(需要用户权限) | |
| * @param {Object} [options={}] - 配置选项 | |
| * @param {boolean} [options.showMessage=true] - 是否显示结果消息 | |
| * @param {Function} [options.onSuccess] - 读取成功时的回调 | |
| * @param {Function} [options.onError] - 读取失败时的回调 | |
| * @returns {Promise<string|null>} 剪贴板中的文本,失败时返回 null | |
| * @example | |
| * const text = await readFromClipboard(); | |
| * if (text) { | |
| * console.log('剪贴板内容:', text); | |
| * } | |
| */ | |
| 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; | |
| } | |
| } | |