Text Explainer Settings

Settings module for Text Explainer

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.gf.qytechs.cn/scripts/528763/1549028/Text%20Explainer%20Settings.js

  1. // ==UserScript==
  2. // @name Text Explainer Settings
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.1.5
  5. // @description Settings module for Text Explainer
  6. // @author RoCry
  7. // @license MIT
  8. // ==/UserScript==
  9.  
  10. class TextExplainerSettings {
  11. constructor(defaultConfig = {}) {
  12. this.defaultConfig = Object.assign({
  13. model: "openai-large",
  14. apiKey: "fake",
  15. baseUrl: "https://text.pollinations.ai/openai#",
  16. provider: "openai",
  17. language: "Chinese",
  18. shortcut: {
  19. key: "d",
  20. ctrlKey: false,
  21. altKey: true,
  22. shiftKey: false,
  23. metaKey: false
  24. },
  25. floatingButton: {
  26. enabled: true,
  27. size: "medium", // small, medium, large
  28. }
  29. }, defaultConfig);
  30.  
  31. this.config = this.load();
  32. }
  33.  
  34. /**
  35. * Load settings from storage
  36. */
  37. load() {
  38. try {
  39. const savedConfig = typeof GM_getValue === 'function'
  40. ? GM_getValue('explainerConfig', {})
  41. : JSON.parse(localStorage.getItem('explainerConfig') || '{}');
  42. return Object.assign({}, this.defaultConfig, savedConfig);
  43. } catch (e) {
  44. console.error('Error loading settings:', e);
  45. return Object.assign({}, this.defaultConfig);
  46. }
  47. }
  48.  
  49. /**
  50. * Save settings to storage
  51. */
  52. save() {
  53. try {
  54. if (typeof GM_setValue === 'function') {
  55. GM_setValue('explainerConfig', this.config);
  56. } else {
  57. localStorage.setItem('explainerConfig', JSON.stringify(this.config));
  58. }
  59. return true;
  60. } catch (e) {
  61. console.error('Error saving settings:', e);
  62. return false;
  63. }
  64. }
  65.  
  66. /**
  67. * Get setting value
  68. */
  69. get(key) {
  70. return this.config[key];
  71. }
  72.  
  73. /**
  74. * Set setting value
  75. */
  76. set(key, value) {
  77. this.config[key] = value;
  78. return this;
  79. }
  80.  
  81. /**
  82. * Update multiple settings at once
  83. */
  84. update(settings) {
  85. Object.assign(this.config, settings);
  86. return this;
  87. }
  88.  
  89. /**
  90. * Reset settings to defaults
  91. */
  92. reset() {
  93. this.config = Object.assign({}, this.defaultConfig);
  94. return this;
  95. }
  96.  
  97. /**
  98. * Get all settings
  99. */
  100. getAll() {
  101. return Object.assign({}, this.config);
  102. }
  103.  
  104. /**
  105. * Open settings dialog
  106. */
  107. openDialog(onSave = null) {
  108. // First check if dialog already exists and remove it
  109. const existingDialog = document.getElementById('explainer-settings-dialog');
  110. if (existingDialog) existingDialog.remove();
  111.  
  112. // Create dialog container
  113. const dialog = document.createElement('div');
  114. dialog.id = 'explainer-settings-dialog';
  115. dialog.style = `
  116. position: fixed;
  117. top: 50%;
  118. left: 50%;
  119. transform: translate(-50%, -50%);
  120. background: white;
  121. padding: 12px;
  122. border-radius: 8px;
  123. box-shadow: 0 2px 10px rgba(0,0,0,0.2);
  124. z-index: 10001;
  125. width: 400px;
  126. max-width: 90vw;
  127. max-height: 80vh;
  128. overflow-y: auto;
  129. font-family: system-ui, sans-serif;
  130. font-size: 14px;
  131. `;
  132.  
  133. // Add dark mode support
  134. const styleElement = document.createElement('style');
  135. styleElement.textContent = `
  136. #explainer-settings-dialog {
  137. color: #333;
  138. }
  139. #explainer-settings-dialog h3 {
  140. margin-top: 0;
  141. margin-bottom: 10px;
  142. font-size: 16px;
  143. }
  144. #explainer-settings-dialog .row {
  145. display: flex;
  146. gap: 10px;
  147. margin-bottom: 8px;
  148. }
  149. #explainer-settings-dialog .col {
  150. flex: 1;
  151. }
  152. #explainer-settings-dialog label {
  153. display: block;
  154. margin: 4px 0 2px;
  155. font-weight: 500;
  156. font-size: 12px;
  157. }
  158. #explainer-settings-dialog input[type="text"],
  159. #explainer-settings-dialog select {
  160. width: 100%;
  161. padding: 4px 6px;
  162. border: 1px solid #ccc;
  163. border-radius: 4px;
  164. box-sizing: border-box;
  165. font-size: 13px;
  166. }
  167. /* Fix for shortcut key width */
  168. #explainer-settings-dialog input#explainer-shortcut-key {
  169. width: 30px !important;
  170. min-width: 30px;
  171. max-width: 30px;
  172. text-align: center;
  173. padding: 4px 0;
  174. }
  175. #explainer-settings-dialog .buttons {
  176. display: flex;
  177. justify-content: flex-end;
  178. gap: 8px;
  179. margin-top: 12px;
  180. }
  181. #explainer-settings-dialog button {
  182. padding: 6px 10px;
  183. border: none;
  184. border-radius: 4px;
  185. cursor: pointer;
  186. font-size: 13px;
  187. }
  188. #explainer-settings-dialog button.primary {
  189. background-color: #4285f4;
  190. color: white;
  191. }
  192. #explainer-settings-dialog button.secondary {
  193. background-color: #f1f1f1;
  194. color: #333;
  195. }
  196. #explainer-settings-dialog .shortcut-section {
  197. display: flex;
  198. align-items: flex-end;
  199. gap: 15px;
  200. margin-bottom: 4px;
  201. }
  202. #explainer-settings-dialog .key-container {
  203. display: flex;
  204. flex-direction: column;
  205. }
  206. #explainer-settings-dialog .modifier-group {
  207. display: flex;
  208. align-items: center;
  209. gap: 8px;
  210. height: 28px; /* Match the height of the input field */
  211. }
  212. #explainer-settings-dialog .modifier {
  213. display: flex;
  214. flex-direction: column;
  215. align-items: center;
  216. margin: 0 2px;
  217. }
  218. #explainer-settings-dialog .modifier input[type="checkbox"] {
  219. margin: 0 0 2px;
  220. }
  221. #explainer-settings-dialog .modifier label {
  222. font-size: 11px;
  223. margin: 0;
  224. user-select: none;
  225. }
  226. #explainer-settings-dialog .section-title {
  227. font-weight: 600;
  228. margin-top: 12px;
  229. margin-bottom: 6px;
  230. border-bottom: 1px solid #ddd;
  231. padding-bottom: 2px;
  232. font-size: 13px;
  233. }
  234. #explainer-settings-dialog .checkbox-label {
  235. display: flex;
  236. align-items: center;
  237. margin: 4px 0;
  238. }
  239. #explainer-settings-dialog .checkbox-label input {
  240. margin-right: 6px;
  241. }
  242. @media (prefers-color-scheme: dark) {
  243. #explainer-settings-dialog {
  244. background: #333;
  245. color: #eee;
  246. }
  247. #explainer-settings-dialog input[type="text"],
  248. #explainer-settings-dialog select {
  249. background: #444;
  250. color: #eee;
  251. border-color: #555;
  252. }
  253. #explainer-settings-dialog button.secondary {
  254. background-color: #555;
  255. color: #eee;
  256. }
  257. #explainer-settings-dialog .section-title {
  258. border-bottom-color: #555;
  259. }
  260. }
  261. `;
  262. document.head.appendChild(styleElement);
  263.  
  264. // Prepare shortcut configuration
  265. const shortcut = this.config.shortcut || this.defaultConfig.shortcut;
  266. const floatingButton = this.config.floatingButton || this.defaultConfig.floatingButton;
  267.  
  268. // Create dialog content with a more compact layout
  269. dialog.innerHTML = `
  270. <h3>Text Explainer Settings</h3>
  271. <div class="section-title">Language & API Settings</div>
  272. <div class="row">
  273. <div class="col">
  274. <label for="explainer-language">Language</label>
  275. <select id="explainer-language">
  276. <option value="Chinese" ${this.config.language === 'Chinese' ? 'selected' : ''}>Chinese</option>
  277. <option value="English" ${this.config.language === 'English' ? 'selected' : ''}>English</option>
  278. <option value="Japanese" ${this.config.language === 'Japanese' ? 'selected' : ''}>Japanese</option>
  279. </select>
  280. </div>
  281. <div class="col">
  282. <label for="explainer-provider">Provider</label>
  283. <select id="explainer-provider">
  284. <option value="gemini" ${this.config.provider === 'gemini' ? 'selected' : ''}>Gemini</option>
  285. <option value="openai" ${this.config.provider === 'openai' ? 'selected' : ''}>OpenAI</option>
  286. <option value="anthropic" ${this.config.provider === 'anthropic' ? 'selected' : ''}>Anthropic</option>
  287. </select>
  288. </div>
  289. <div class="col">
  290. <label for="explainer-model">Model</label>
  291. <input id="explainer-model" type="text" value="${this.config.model}">
  292. </div>
  293. </div>
  294.  
  295. <div class="row">
  296. <div class="col">
  297. <label for="explainer-api-key">API Key</label>
  298. <input id="explainer-api-key" type="text" value="${this.config.apiKey || ''}">
  299. <p style="font-size: 11px; color: #666; margin-top: 0; margin-bottom: 8px;">
  300. Multiple keys supported, separated by commas
  301. </p>
  302. </div>
  303. </div>
  304. <div>
  305. <label for="explainer-base-url">API Base URL</label>
  306. <input id="explainer-base-url" type="text" value="${this.config.baseUrl}">
  307. <p style="font-size: 11px; color: #666; margin-top: 0; margin-bottom: 8px;">
  308. Ending with / ignores v1, ending with # forces use of input address
  309. </p>
  310. </div>
  311. <div class="section-title">Shortcut Settings</div>
  312. <div class="shortcut-section">
  313. <div class="key-container">
  314. <label for="explainer-shortcut-key">Key</label>
  315. <input id="explainer-shortcut-key" type="text" maxlength="1" value="${shortcut.key}">
  316. </div>
  317. <div class="modifier-group">
  318. <div class="modifier">
  319. <label for="explainer-shortcut-ctrl">⌃</label>
  320. <input type="checkbox" id="explainer-shortcut-ctrl" ${shortcut.ctrlKey ? 'checked' : ''}>
  321. </div>
  322. <div class="modifier">
  323. <label for="explainer-shortcut-alt">⌥</label>
  324. <input type="checkbox" id="explainer-shortcut-alt" ${shortcut.altKey ? 'checked' : ''}>
  325. </div>
  326. <div class="modifier">
  327. <label for="explainer-shortcut-shift">⇧</label>
  328. <input type="checkbox" id="explainer-shortcut-shift" ${shortcut.shiftKey ? 'checked' : ''}>
  329. </div>
  330. <div class="modifier">
  331. <label for="explainer-shortcut-meta">⌘</label>
  332. <input type="checkbox" id="explainer-shortcut-meta" ${shortcut.metaKey ? 'checked' : ''}>
  333. </div>
  334. </div>
  335. </div>
  336. <p style="font-size: 11px; color: #666; margin-top: 0; margin-bottom: 8px;">
  337. Tip: Choose a letter key (a-z) with at least one modifier key.
  338. </p>
  339. <div class="section-title">Touch Device Settings</div>
  340. <div class="row">
  341. <div class="col">
  342. <div class="checkbox-label">
  343. <input type="checkbox" id="explainer-floating-enabled" ${floatingButton.enabled ? 'checked' : ''}>
  344. <span>Show floating button</span>
  345. </div>
  346. </div>
  347. </div>
  348. <div class="row">
  349. <div class="col">
  350. <label for="explainer-floating-size">Button Size</label>
  351. <select id="explainer-floating-size">
  352. <option value="small" ${floatingButton.size === 'small' ? 'selected' : ''}>Small</option>
  353. <option value="medium" ${floatingButton.size === 'medium' ? 'selected' : ''}>Medium</option>
  354. <option value="large" ${floatingButton.size === 'large' ? 'selected' : ''}>Large</option>
  355. </select>
  356. </div>
  357. </div>
  358. <div class="buttons">
  359. <button id="explainer-settings-cancel" class="secondary">Cancel</button>
  360. <button id="explainer-settings-save" class="primary">Save</button>
  361. </div>
  362. `;
  363.  
  364. document.body.appendChild(dialog);
  365.  
  366. // Add event listeners
  367. document.getElementById('explainer-settings-save').addEventListener('click', () => {
  368. // Get shortcut settings
  369. const shortcutSettings = {
  370. key: document.getElementById('explainer-shortcut-key').value.toLowerCase(),
  371. ctrlKey: document.getElementById('explainer-shortcut-ctrl').checked,
  372. altKey: document.getElementById('explainer-shortcut-alt').checked,
  373. shiftKey: document.getElementById('explainer-shortcut-shift').checked,
  374. metaKey: document.getElementById('explainer-shortcut-meta').checked
  375. };
  376.  
  377. // Get floating button settings
  378. const floatingButtonSettings = {
  379. enabled: document.getElementById('explainer-floating-enabled').checked,
  380. size: document.getElementById('explainer-floating-size').value,
  381. };
  382.  
  383. // Update config with all form values
  384. this.update({
  385. language: document.getElementById('explainer-language').value,
  386. model: document.getElementById('explainer-model').value,
  387. apiKey: document.getElementById('explainer-api-key').value,
  388. baseUrl: document.getElementById('explainer-base-url').value,
  389. provider: document.getElementById('explainer-provider').value,
  390. shortcut: shortcutSettings,
  391. floatingButton: floatingButtonSettings
  392. });
  393.  
  394. // Save to storage
  395. this.save();
  396.  
  397. // Remove dialog
  398. dialog.remove();
  399. styleElement.remove();
  400.  
  401. // Call save callback if provided
  402. if (typeof onSave === 'function') {
  403. onSave(this.config);
  404. }
  405. });
  406.  
  407. document.getElementById('explainer-settings-cancel').addEventListener('click', () => {
  408. dialog.remove();
  409. styleElement.remove();
  410. });
  411.  
  412. // Focus first field
  413. document.getElementById('explainer-language').focus();
  414.  
  415. // Add validation for the shortcut key
  416. const keyInput = document.getElementById('explainer-shortcut-key');
  417. keyInput.addEventListener('input', () => {
  418. // Ensure it's a single character and convert to lowercase
  419. if (keyInput.value.length > 0) {
  420. keyInput.value = keyInput.value.charAt(0).toLowerCase();
  421. }
  422. });
  423. }
  424. }
  425.  
  426. // Make available globally and as a module if needed
  427. window.TextExplainerSettings = TextExplainerSettings;
  428.  
  429. if (typeof module !== 'undefined') {
  430. module.exports = TextExplainerSettings;
  431. }

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址