Text Explainer Settings

Settings module for Text Explainer

Pada tanggal 05 Maret 2025. Lihat %(latest_version_link).

Skrip ini tidak untuk dipasang secara langsung. Ini adalah pustaka skrip lain untuk disertakan dengan direktif meta // @require https://update.greatest.deepsurf.us/scripts/528763/1547460/Text%20Explainer%20Settings.js

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