您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
Settings module for Text Explainer
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.icu/scripts/528763/1549028/Text%20Explainer%20Settings.js
// ==UserScript== // @name Text Explainer Settings // @namespace http://tampermonkey.net/ // @version 0.1.5 // @description Settings module for Text Explainer // @author RoCry // @license MIT // ==/UserScript== class TextExplainerSettings { constructor(defaultConfig = {}) { this.defaultConfig = Object.assign({ model: "openai-large", apiKey: "fake", baseUrl: "https://text.pollinations.ai/openai#", provider: "openai", language: "Chinese", shortcut: { key: "d", ctrlKey: false, altKey: true, shiftKey: false, metaKey: false }, floatingButton: { enabled: true, size: "medium", // small, medium, large } }, defaultConfig); this.config = this.load(); } /** * Load settings from storage */ load() { try { const savedConfig = typeof GM_getValue === 'function' ? GM_getValue('explainerConfig', {}) : JSON.parse(localStorage.getItem('explainerConfig') || '{}'); return Object.assign({}, this.defaultConfig, savedConfig); } catch (e) { console.error('Error loading settings:', e); return Object.assign({}, this.defaultConfig); } } /** * Save settings to storage */ save() { try { if (typeof GM_setValue === 'function') { GM_setValue('explainerConfig', this.config); } else { localStorage.setItem('explainerConfig', JSON.stringify(this.config)); } return true; } catch (e) { console.error('Error saving settings:', e); return false; } } /** * Get setting value */ get(key) { return this.config[key]; } /** * Set setting value */ set(key, value) { this.config[key] = value; return this; } /** * Update multiple settings at once */ update(settings) { Object.assign(this.config, settings); return this; } /** * Reset settings to defaults */ reset() { this.config = Object.assign({}, this.defaultConfig); return this; } /** * Get all settings */ getAll() { return Object.assign({}, this.config); } /** * Open settings dialog */ openDialog(onSave = null) { // First check if dialog already exists and remove it const existingDialog = document.getElementById('explainer-settings-dialog'); if (existingDialog) existingDialog.remove(); // Create dialog container const dialog = document.createElement('div'); dialog.id = 'explainer-settings-dialog'; dialog.style = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 12px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.2); z-index: 10001; width: 400px; max-width: 90vw; max-height: 80vh; overflow-y: auto; font-family: system-ui, sans-serif; font-size: 14px; `; // Add dark mode support const styleElement = document.createElement('style'); styleElement.textContent = ` #explainer-settings-dialog { color: #333; } #explainer-settings-dialog h3 { margin-top: 0; margin-bottom: 10px; font-size: 16px; } #explainer-settings-dialog .row { display: flex; gap: 10px; margin-bottom: 8px; } #explainer-settings-dialog .col { flex: 1; } #explainer-settings-dialog label { display: block; margin: 4px 0 2px; font-weight: 500; font-size: 12px; } #explainer-settings-dialog input[type="text"], #explainer-settings-dialog select { width: 100%; padding: 4px 6px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; font-size: 13px; } /* Fix for shortcut key width */ #explainer-settings-dialog input#explainer-shortcut-key { width: 30px !important; min-width: 30px; max-width: 30px; text-align: center; padding: 4px 0; } #explainer-settings-dialog .buttons { display: flex; justify-content: flex-end; gap: 8px; margin-top: 12px; } #explainer-settings-dialog button { padding: 6px 10px; border: none; border-radius: 4px; cursor: pointer; font-size: 13px; } #explainer-settings-dialog button.primary { background-color: #4285f4; color: white; } #explainer-settings-dialog button.secondary { background-color: #f1f1f1; color: #333; } #explainer-settings-dialog .shortcut-section { display: flex; align-items: flex-end; gap: 15px; margin-bottom: 4px; } #explainer-settings-dialog .key-container { display: flex; flex-direction: column; } #explainer-settings-dialog .modifier-group { display: flex; align-items: center; gap: 8px; height: 28px; /* Match the height of the input field */ } #explainer-settings-dialog .modifier { display: flex; flex-direction: column; align-items: center; margin: 0 2px; } #explainer-settings-dialog .modifier input[type="checkbox"] { margin: 0 0 2px; } #explainer-settings-dialog .modifier label { font-size: 11px; margin: 0; user-select: none; } #explainer-settings-dialog .section-title { font-weight: 600; margin-top: 12px; margin-bottom: 6px; border-bottom: 1px solid #ddd; padding-bottom: 2px; font-size: 13px; } #explainer-settings-dialog .checkbox-label { display: flex; align-items: center; margin: 4px 0; } #explainer-settings-dialog .checkbox-label input { margin-right: 6px; } @media (prefers-color-scheme: dark) { #explainer-settings-dialog { background: #333; color: #eee; } #explainer-settings-dialog input[type="text"], #explainer-settings-dialog select { background: #444; color: #eee; border-color: #555; } #explainer-settings-dialog button.secondary { background-color: #555; color: #eee; } #explainer-settings-dialog .section-title { border-bottom-color: #555; } } `; document.head.appendChild(styleElement); // Prepare shortcut configuration const shortcut = this.config.shortcut || this.defaultConfig.shortcut; const floatingButton = this.config.floatingButton || this.defaultConfig.floatingButton; // Create dialog content with a more compact layout dialog.innerHTML = ` <h3>Text Explainer Settings</h3> <div class="section-title">Language & API Settings</div> <div class="row"> <div class="col"> <label for="explainer-language">Language</label> <select id="explainer-language"> <option value="Chinese" ${this.config.language === 'Chinese' ? 'selected' : ''}>Chinese</option> <option value="English" ${this.config.language === 'English' ? 'selected' : ''}>English</option> <option value="Japanese" ${this.config.language === 'Japanese' ? 'selected' : ''}>Japanese</option> </select> </div> <div class="col"> <label for="explainer-provider">Provider</label> <select id="explainer-provider"> <option value="gemini" ${this.config.provider === 'gemini' ? 'selected' : ''}>Gemini</option> <option value="openai" ${this.config.provider === 'openai' ? 'selected' : ''}>OpenAI</option> <option value="anthropic" ${this.config.provider === 'anthropic' ? 'selected' : ''}>Anthropic</option> </select> </div> <div class="col"> <label for="explainer-model">Model</label> <input id="explainer-model" type="text" value="${this.config.model}"> </div> </div> <div class="row"> <div class="col"> <label for="explainer-api-key">API Key</label> <input id="explainer-api-key" type="text" value="${this.config.apiKey || ''}"> <p style="font-size: 11px; color: #666; margin-top: 0; margin-bottom: 8px;"> Multiple keys supported, separated by commas </p> </div> </div> <div> <label for="explainer-base-url">API Base URL</label> <input id="explainer-base-url" type="text" value="${this.config.baseUrl}"> <p style="font-size: 11px; color: #666; margin-top: 0; margin-bottom: 8px;"> Ending with / ignores v1, ending with # forces use of input address </p> </div> <div class="section-title">Shortcut Settings</div> <div class="shortcut-section"> <div class="key-container"> <label for="explainer-shortcut-key">Key</label> <input id="explainer-shortcut-key" type="text" maxlength="1" value="${shortcut.key}"> </div> <div class="modifier-group"> <div class="modifier"> <label for="explainer-shortcut-ctrl">⌃</label> <input type="checkbox" id="explainer-shortcut-ctrl" ${shortcut.ctrlKey ? 'checked' : ''}> </div> <div class="modifier"> <label for="explainer-shortcut-alt">⌥</label> <input type="checkbox" id="explainer-shortcut-alt" ${shortcut.altKey ? 'checked' : ''}> </div> <div class="modifier"> <label for="explainer-shortcut-shift">⇧</label> <input type="checkbox" id="explainer-shortcut-shift" ${shortcut.shiftKey ? 'checked' : ''}> </div> <div class="modifier"> <label for="explainer-shortcut-meta">⌘</label> <input type="checkbox" id="explainer-shortcut-meta" ${shortcut.metaKey ? 'checked' : ''}> </div> </div> </div> <p style="font-size: 11px; color: #666; margin-top: 0; margin-bottom: 8px;"> Tip: Choose a letter key (a-z) with at least one modifier key. </p> <div class="section-title">Touch Device Settings</div> <div class="row"> <div class="col"> <div class="checkbox-label"> <input type="checkbox" id="explainer-floating-enabled" ${floatingButton.enabled ? 'checked' : ''}> <span>Show floating button</span> </div> </div> </div> <div class="row"> <div class="col"> <label for="explainer-floating-size">Button Size</label> <select id="explainer-floating-size"> <option value="small" ${floatingButton.size === 'small' ? 'selected' : ''}>Small</option> <option value="medium" ${floatingButton.size === 'medium' ? 'selected' : ''}>Medium</option> <option value="large" ${floatingButton.size === 'large' ? 'selected' : ''}>Large</option> </select> </div> </div> <div class="buttons"> <button id="explainer-settings-cancel" class="secondary">Cancel</button> <button id="explainer-settings-save" class="primary">Save</button> </div> `; document.body.appendChild(dialog); // Add event listeners document.getElementById('explainer-settings-save').addEventListener('click', () => { // Get shortcut settings const shortcutSettings = { key: document.getElementById('explainer-shortcut-key').value.toLowerCase(), ctrlKey: document.getElementById('explainer-shortcut-ctrl').checked, altKey: document.getElementById('explainer-shortcut-alt').checked, shiftKey: document.getElementById('explainer-shortcut-shift').checked, metaKey: document.getElementById('explainer-shortcut-meta').checked }; // Get floating button settings const floatingButtonSettings = { enabled: document.getElementById('explainer-floating-enabled').checked, size: document.getElementById('explainer-floating-size').value, }; // Update config with all form values this.update({ language: document.getElementById('explainer-language').value, model: document.getElementById('explainer-model').value, apiKey: document.getElementById('explainer-api-key').value, baseUrl: document.getElementById('explainer-base-url').value, provider: document.getElementById('explainer-provider').value, shortcut: shortcutSettings, floatingButton: floatingButtonSettings }); // Save to storage this.save(); // Remove dialog dialog.remove(); styleElement.remove(); // Call save callback if provided if (typeof onSave === 'function') { onSave(this.config); } }); document.getElementById('explainer-settings-cancel').addEventListener('click', () => { dialog.remove(); styleElement.remove(); }); // Focus first field document.getElementById('explainer-language').focus(); // Add validation for the shortcut key const keyInput = document.getElementById('explainer-shortcut-key'); keyInput.addEventListener('input', () => { // Ensure it's a single character and convert to lowercase if (keyInput.value.length > 0) { keyInput.value = keyInput.value.charAt(0).toLowerCase(); } }); } } // Make available globally and as a module if needed window.TextExplainerSettings = TextExplainerSettings; if (typeof module !== 'undefined') { module.exports = TextExplainerSettings; }