Text Explainer Settings

Settings module for Text Explainer

目前为 2025-03-04 提交的版本。查看 最新版本

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

  1. // ==UserScript==
  2. // @name Text Explainer Settings
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.1.1
  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: "gemini-2.0-flash",
  14. apiKey: null,
  15. baseUrl: "https://generativelanguage.googleapis.com",
  16. provider: "gemini",
  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. position: "bottom-right" // top-left, top-right, bottom-left, bottom-right
  29. }
  30. }, defaultConfig);
  31. this.config = this.load();
  32. }
  33. /**
  34. * Load settings from storage
  35. */
  36. load() {
  37. try {
  38. const savedConfig = typeof GM_getValue === 'function'
  39. ? GM_getValue('explainerConfig', {})
  40. : JSON.parse(localStorage.getItem('explainerConfig') || '{}');
  41. return Object.assign({}, this.defaultConfig, savedConfig);
  42. } catch (e) {
  43. console.error('Error loading settings:', e);
  44. return Object.assign({}, this.defaultConfig);
  45. }
  46. }
  47. /**
  48. * Save settings to storage
  49. */
  50. save() {
  51. try {
  52. if (typeof GM_setValue === 'function') {
  53. GM_setValue('explainerConfig', this.config);
  54. } else {
  55. localStorage.setItem('explainerConfig', JSON.stringify(this.config));
  56. }
  57. return true;
  58. } catch (e) {
  59. console.error('Error saving settings:', e);
  60. return false;
  61. }
  62. }
  63. /**
  64. * Get setting value
  65. */
  66. get(key) {
  67. return this.config[key];
  68. }
  69. /**
  70. * Set setting value
  71. */
  72. set(key, value) {
  73. this.config[key] = value;
  74. return this;
  75. }
  76. /**
  77. * Update multiple settings at once
  78. */
  79. update(settings) {
  80. Object.assign(this.config, settings);
  81. return this;
  82. }
  83. /**
  84. * Reset settings to defaults
  85. */
  86. reset() {
  87. this.config = Object.assign({}, this.defaultConfig);
  88. return this;
  89. }
  90. /**
  91. * Get all settings
  92. */
  93. getAll() {
  94. return Object.assign({}, this.config);
  95. }
  96. /**
  97. * Open settings dialog
  98. */
  99. openDialog(onSave = null) {
  100. // First check if dialog already exists and remove it
  101. const existingDialog = document.getElementById('explainer-settings-dialog');
  102. if (existingDialog) existingDialog.remove();
  103. // Create dialog container
  104. const dialog = document.createElement('div');
  105. dialog.id = 'explainer-settings-dialog';
  106. dialog.style = `
  107. position: fixed;
  108. top: 50%;
  109. left: 50%;
  110. transform: translate(-50%, -50%);
  111. background: white;
  112. padding: 16px;
  113. border-radius: 8px;
  114. box-shadow: 0 2px 10px rgba(0,0,0,0.2);
  115. z-index: 10001;
  116. width: 400px;
  117. max-width: 90vw;
  118. font-family: system-ui, sans-serif;
  119. `;
  120. // Add dark mode support
  121. const styleElement = document.createElement('style');
  122. styleElement.textContent = `
  123. #explainer-settings-dialog {
  124. color: #333;
  125. }
  126. #explainer-settings-dialog label {
  127. display: block;
  128. margin: 12px 0 4px;
  129. font-weight: 500;
  130. }
  131. #explainer-settings-dialog input[type="text"],
  132. #explainer-settings-dialog select {
  133. width: 100%;
  134. padding: 8px;
  135. border: 1px solid #ccc;
  136. border-radius: 4px;
  137. box-sizing: border-box;
  138. font-size: 14px;
  139. }
  140. #explainer-settings-dialog .buttons {
  141. display: flex;
  142. justify-content: flex-end;
  143. gap: 8px;
  144. margin-top: 20px;
  145. }
  146. #explainer-settings-dialog button {
  147. padding: 8px 12px;
  148. border: none;
  149. border-radius: 4px;
  150. cursor: pointer;
  151. font-size: 14px;
  152. }
  153. #explainer-settings-dialog button.primary {
  154. background-color: #4285f4;
  155. color: white;
  156. }
  157. #explainer-settings-dialog button.secondary {
  158. background-color: #f1f1f1;
  159. color: #333;
  160. }
  161. #explainer-settings-dialog .shortcut-section {
  162. display: flex;
  163. gap: 8px;
  164. }
  165. #explainer-settings-dialog .shortcut-section label {
  166. display: inline;
  167. margin-right: 4px;
  168. }
  169. #explainer-settings-dialog .shortcut-section input[type="checkbox"] {
  170. margin-right: 2px;
  171. }
  172. #explainer-settings-dialog .shortcut-key {
  173. width: 60px;
  174. }
  175. #explainer-settings-dialog .section-title {
  176. font-weight: 600;
  177. margin-top: 16px;
  178. border-bottom: 1px solid #ddd;
  179. padding-bottom: 4px;
  180. }
  181. @media (prefers-color-scheme: dark) {
  182. #explainer-settings-dialog {
  183. background: #333;
  184. color: #eee;
  185. }
  186. #explainer-settings-dialog input[type="text"],
  187. #explainer-settings-dialog select {
  188. background: #444;
  189. color: #eee;
  190. border-color: #555;
  191. }
  192. #explainer-settings-dialog button.secondary {
  193. background-color: #555;
  194. color: #eee;
  195. }
  196. #explainer-settings-dialog .section-title {
  197. border-bottom-color: #555;
  198. }
  199. }
  200. `;
  201. document.head.appendChild(styleElement);
  202. // Prepare shortcut configuration
  203. const shortcut = this.config.shortcut || this.defaultConfig.shortcut;
  204. const floatingButton = this.config.floatingButton || this.defaultConfig.floatingButton;
  205. // Create dialog content
  206. dialog.innerHTML = `
  207. <h3 style="margin-top:0;">Text Explainer Settings</h3>
  208. <div class="section-title">Language & API Settings</div>
  209. <div>
  210. <label for="explainer-language">Language</label>
  211. <select id="explainer-language">
  212. <option value="Chinese" ${this.config.language === 'Chinese' ? 'selected' : ''}>Chinese</option>
  213. <option value="English" ${this.config.language === 'English' ? 'selected' : ''}>English</option>
  214. <option value="Japanese" ${this.config.language === 'Japanese' ? 'selected' : ''}>Japanese</option>
  215. </select>
  216. </div>
  217. <div>
  218. <label for="explainer-provider">Provider</label>
  219. <select id="explainer-provider">
  220. <option value="gemini" ${this.config.provider === 'gemini' ? 'selected' : ''}>Gemini</option>
  221. <option value="openai" ${this.config.provider === 'openai' ? 'selected' : ''}>OpenAI</option>
  222. <option value="anthropic" ${this.config.provider === 'anthropic' ? 'selected' : ''}>Anthropic</option>
  223. </select>
  224. </div>
  225. <div>
  226. <label for="explainer-model">Model</label>
  227. <input id="explainer-model" type="text" value="${this.config.model}">
  228. </div>
  229. <div>
  230. <label for="explainer-api-key">API Key</label>
  231. <input id="explainer-api-key" type="text" value="${this.config.apiKey || ''}">
  232. </div>
  233. <div>
  234. <label for="explainer-base-url">API Base URL</label>
  235. <input id="explainer-base-url" type="text" value="${this.config.baseUrl}">
  236. </div>
  237. <div class="section-title">Shortcut Settings</div>
  238. <div class="shortcut-section">
  239. <div>
  240. <label for="explainer-shortcut-key">Key</label>
  241. <input id="explainer-shortcut-key" class="shortcut-key" type="text" maxlength="1" value="${shortcut.key}">
  242. </div>
  243. <div>
  244. <input type="checkbox" id="explainer-shortcut-ctrl" ${shortcut.ctrlKey ? 'checked' : ''}>
  245. <label for="explainer-shortcut-ctrl">Ctrl</label>
  246. </div>
  247. <div>
  248. <input type="checkbox" id="explainer-shortcut-alt" ${shortcut.altKey ? 'checked' : ''}>
  249. <label for="explainer-shortcut-alt">Alt</label>
  250. </div>
  251. <div>
  252. <input type="checkbox" id="explainer-shortcut-shift" ${shortcut.shiftKey ? 'checked' : ''}>
  253. <label for="explainer-shortcut-shift">Shift</label>
  254. </div>
  255. <div>
  256. <input type="checkbox" id="explainer-shortcut-meta" ${shortcut.metaKey ? 'checked' : ''}>
  257. <label for="explainer-shortcut-meta">Meta</label>
  258. </div>
  259. </div>
  260. <div class="section-title">Touch Device Settings</div>
  261. <div>
  262. <input type="checkbox" id="explainer-floating-enabled" ${floatingButton.enabled ? 'checked' : ''}>
  263. <label for="explainer-floating-enabled">Show floating button on touch devices</label>
  264. </div>
  265. <div>
  266. <label for="explainer-floating-size">Button Size</label>
  267. <select id="explainer-floating-size">
  268. <option value="small" ${floatingButton.size === 'small' ? 'selected' : ''}>Small</option>
  269. <option value="medium" ${floatingButton.size === 'medium' ? 'selected' : ''}>Medium</option>
  270. <option value="large" ${floatingButton.size === 'large' ? 'selected' : ''}>Large</option>
  271. </select>
  272. </div>
  273. <div>
  274. <label for="explainer-floating-position">Button Position</label>
  275. <select id="explainer-floating-position">
  276. <option value="top-left" ${floatingButton.position === 'top-left' ? 'selected' : ''}>Top Left</option>
  277. <option value="top-right" ${floatingButton.position === 'top-right' ? 'selected' : ''}>Top Right</option>
  278. <option value="bottom-left" ${floatingButton.position === 'bottom-left' ? 'selected' : ''}>Bottom Left</option>
  279. <option value="bottom-right" ${floatingButton.position === 'bottom-right' ? 'selected' : ''}>Bottom Right</option>
  280. </select>
  281. </div>
  282. <div class="buttons">
  283. <button id="explainer-settings-cancel" class="secondary">Cancel</button>
  284. <button id="explainer-settings-save" class="primary">Save</button>
  285. </div>
  286. `;
  287. document.body.appendChild(dialog);
  288. // Add event listeners
  289. document.getElementById('explainer-settings-save').addEventListener('click', () => {
  290. // Get shortcut settings
  291. const shortcutSettings = {
  292. key: document.getElementById('explainer-shortcut-key').value.toLowerCase(),
  293. ctrlKey: document.getElementById('explainer-shortcut-ctrl').checked,
  294. altKey: document.getElementById('explainer-shortcut-alt').checked,
  295. shiftKey: document.getElementById('explainer-shortcut-shift').checked,
  296. metaKey: document.getElementById('explainer-shortcut-meta').checked
  297. };
  298. // Get floating button settings
  299. const floatingButtonSettings = {
  300. enabled: document.getElementById('explainer-floating-enabled').checked,
  301. size: document.getElementById('explainer-floating-size').value,
  302. position: document.getElementById('explainer-floating-position').value
  303. };
  304. // Update config with all form values
  305. this.update({
  306. language: document.getElementById('explainer-language').value,
  307. model: document.getElementById('explainer-model').value,
  308. apiKey: document.getElementById('explainer-api-key').value,
  309. baseUrl: document.getElementById('explainer-base-url').value,
  310. provider: document.getElementById('explainer-provider').value,
  311. shortcut: shortcutSettings,
  312. floatingButton: floatingButtonSettings
  313. });
  314. // Save to storage
  315. this.save();
  316. // Remove dialog
  317. dialog.remove();
  318. styleElement.remove();
  319. // Call save callback if provided
  320. if (typeof onSave === 'function') {
  321. onSave(this.config);
  322. }
  323. });
  324. document.getElementById('explainer-settings-cancel').addEventListener('click', () => {
  325. dialog.remove();
  326. styleElement.remove();
  327. });
  328. // Focus first field
  329. document.getElementById('explainer-language').focus();
  330. }
  331. }
  332.  
  333. // Make available globally and as a module if needed
  334. window.TextExplainerSettings = TextExplainerSettings;
  335.  
  336. if (typeof module !== 'undefined') {
  337. module.exports = TextExplainerSettings;
  338. }