- // ==UserScript==
- // @name Text Explainer Settings
- // @namespace http://tampermonkey.net/
- // @version 0.1.1
- // @description Settings module for Text Explainer
- // @author RoCry
- // @license MIT
- // ==/UserScript==
-
- class TextExplainerSettings {
- constructor(defaultConfig = {}) {
- this.defaultConfig = Object.assign({
- model: "gemini-2.0-flash",
- apiKey: null,
- baseUrl: "https://generativelanguage.googleapis.com",
- provider: "gemini",
- language: "Chinese",
- shortcut: {
- key: "d",
- ctrlKey: false,
- altKey: true,
- shiftKey: false,
- metaKey: false
- },
- floatingButton: {
- enabled: true,
- size: "medium", // small, medium, large
- position: "bottom-right" // top-left, top-right, bottom-left, bottom-right
- }
- }, 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: 16px;
- border-radius: 8px;
- box-shadow: 0 2px 10px rgba(0,0,0,0.2);
- z-index: 10001;
- width: 400px;
- max-width: 90vw;
- font-family: system-ui, sans-serif;
- `;
-
- // Add dark mode support
- const styleElement = document.createElement('style');
- styleElement.textContent = `
- #explainer-settings-dialog {
- color: #333;
- }
- #explainer-settings-dialog label {
- display: block;
- margin: 12px 0 4px;
- font-weight: 500;
- }
- #explainer-settings-dialog input[type="text"],
- #explainer-settings-dialog select {
- width: 100%;
- padding: 8px;
- border: 1px solid #ccc;
- border-radius: 4px;
- box-sizing: border-box;
- font-size: 14px;
- }
- #explainer-settings-dialog .buttons {
- display: flex;
- justify-content: flex-end;
- gap: 8px;
- margin-top: 20px;
- }
- #explainer-settings-dialog button {
- padding: 8px 12px;
- border: none;
- border-radius: 4px;
- cursor: pointer;
- font-size: 14px;
- }
- #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;
- gap: 8px;
- }
- #explainer-settings-dialog .shortcut-section label {
- display: inline;
- margin-right: 4px;
- }
- #explainer-settings-dialog .shortcut-section input[type="checkbox"] {
- margin-right: 2px;
- }
- #explainer-settings-dialog .shortcut-key {
- width: 60px;
- }
- #explainer-settings-dialog .section-title {
- font-weight: 600;
- margin-top: 16px;
- border-bottom: 1px solid #ddd;
- padding-bottom: 4px;
- }
- @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
- dialog.innerHTML = `
- <h3 style="margin-top:0;">Text Explainer Settings</h3>
-
- <div class="section-title">Language & API Settings</div>
- <div>
- <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>
- <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>
- <label for="explainer-model">Model</label>
- <input id="explainer-model" type="text" value="${this.config.model}">
- </div>
- <div>
- <label for="explainer-api-key">API Key</label>
- <input id="explainer-api-key" type="text" value="${this.config.apiKey || ''}">
- </div>
- <div>
- <label for="explainer-base-url">API Base URL</label>
- <input id="explainer-base-url" type="text" value="${this.config.baseUrl}">
- </div>
-
- <div class="section-title">Shortcut Settings</div>
- <div class="shortcut-section">
- <div>
- <label for="explainer-shortcut-key">Key</label>
- <input id="explainer-shortcut-key" class="shortcut-key" type="text" maxlength="1" value="${shortcut.key}">
- </div>
- <div>
- <input type="checkbox" id="explainer-shortcut-ctrl" ${shortcut.ctrlKey ? 'checked' : ''}>
- <label for="explainer-shortcut-ctrl">Ctrl</label>
- </div>
- <div>
- <input type="checkbox" id="explainer-shortcut-alt" ${shortcut.altKey ? 'checked' : ''}>
- <label for="explainer-shortcut-alt">Alt</label>
- </div>
- <div>
- <input type="checkbox" id="explainer-shortcut-shift" ${shortcut.shiftKey ? 'checked' : ''}>
- <label for="explainer-shortcut-shift">Shift</label>
- </div>
- <div>
- <input type="checkbox" id="explainer-shortcut-meta" ${shortcut.metaKey ? 'checked' : ''}>
- <label for="explainer-shortcut-meta">Meta</label>
- </div>
- </div>
-
- <div class="section-title">Touch Device Settings</div>
- <div>
- <input type="checkbox" id="explainer-floating-enabled" ${floatingButton.enabled ? 'checked' : ''}>
- <label for="explainer-floating-enabled">Show floating button on touch devices</label>
- </div>
- <div>
- <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>
- <label for="explainer-floating-position">Button Position</label>
- <select id="explainer-floating-position">
- <option value="top-left" ${floatingButton.position === 'top-left' ? 'selected' : ''}>Top Left</option>
- <option value="top-right" ${floatingButton.position === 'top-right' ? 'selected' : ''}>Top Right</option>
- <option value="bottom-left" ${floatingButton.position === 'bottom-left' ? 'selected' : ''}>Bottom Left</option>
- <option value="bottom-right" ${floatingButton.position === 'bottom-right' ? 'selected' : ''}>Bottom Right</option>
- </select>
- </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,
- position: document.getElementById('explainer-floating-position').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();
- }
- }
-
- // Make available globally and as a module if needed
- window.TextExplainerSettings = TextExplainerSettings;
-
- if (typeof module !== 'undefined') {
- module.exports = TextExplainerSettings;
- }