Greasy Fork is available in English.

Text Explainer Settings

Settings module for Text Explainer

Dette script bør ikke installeres direkte. Det er et bibliotek, som andre scripts kan inkludere med metadirektivet // @require https://update.greatest.deepsurf.us/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. }