GC - Universal Userscripts Settings

Library for adding a user interface to manage settings for grundos.cafe userscripts

目前為 2024-10-29 提交的版本,檢視 最新版本

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.cn-greasyfork.org/scripts/514423/1473491/GC%20-%20Universal%20Userscripts%20Settings.js

  1. async function addTextInput(configuration) {
  2. let {
  3. categoryName,
  4. settingName,
  5. labelText,
  6. lableTooltip = undefined,
  7. currentSetting = undefined,
  8. defaultSetting = '',
  9. callbackFunction = undefined,
  10. } = configuration;
  11. const header = await _addCategoryHeader(categoryName);
  12. if (currentSetting === undefined) {
  13. currentSetting = await GM.getValue(settingName, defaultSetting);
  14. }
  15.  
  16. const textInput = document.createElement('input');
  17. textInput.type = 'text';
  18. textInput.name = settingName;
  19. textInput.value = currentSetting;
  20. const label = _createLabel(labelText);
  21. _formatLabel(label, lableTooltip);
  22. _addBelowHeader(header, label);
  23.  
  24. const inputElement = _createInput(textInput);
  25. label.insertAdjacentElement('afterend', inputElement);
  26.  
  27. if (callbackFunction) {
  28. _addCallback(() => callbackFunction(settingName, textInput.value));
  29. } else {
  30. _addCallback(async () => await GM.setValue(settingName, checkbox.checked));
  31. }
  32. }
  33.  
  34. async function addNumberInput(configuration) {
  35. let {
  36. categoryName,
  37. settingName,
  38. labelText,
  39. lableTooltip = undefined,
  40. min = 0,
  41. max = 100,
  42. step = 1,
  43. currentSetting = undefined,
  44. defaultSetting = 0,
  45. callbackFunction = undefined
  46. } = configuration;
  47.  
  48. const header = await _addCategoryHeader(categoryName);
  49. if (currentSetting === undefined) {
  50. currentSetting = await GM.getValue(settingName, defaultSetting);
  51. }
  52.  
  53. const numberInput = document.createElement('input');
  54. numberInput.type = 'number';
  55. numberInput.name = settingName;
  56. numberInput.value = currentSetting;
  57. numberInput.min = min;
  58. numberInput.max = max;
  59. numberInput.step = step;
  60. const label = _createLabel(labelText);
  61. _formatLabel(label, lableTooltip);
  62. _addBelowHeader(header, label);
  63.  
  64. const inputElement = _createInput(numberInput);
  65. label.insertAdjacentElement('afterend', inputElement);
  66.  
  67. if (callbackFunction) {
  68. _addCallback(() => callbackFunction(settingName, numberInput.value));
  69. } else {
  70. _addCallback(async () => await GM.setValue(settingName, checkbox.checked));
  71. }
  72. }
  73.  
  74. async function addCheckboxInput(configuration) {
  75. let {
  76. categoryName,
  77. settingName,
  78. labelText,
  79. lableTooltip = undefined,
  80. currentSetting = undefined,
  81. defaultSetting = false,
  82. callbackFunction = undefined
  83. } = configuration;
  84. const header = await _addCategoryHeader(categoryName);
  85. if (currentSetting === undefined) {
  86. currentSetting = await GM.getValue(settingName, defaultSetting);
  87. }
  88.  
  89. const checkbox = document.createElement('input');
  90. checkbox.type = 'checkbox';
  91. checkbox.name = settingName;
  92. checkbox.checked = currentSetting;
  93. const label = _createLabel(labelText);
  94. _formatLabel(label, lableTooltip);
  95. _addBelowHeader(header, label);
  96.  
  97. const inputElement = _createInput(checkbox);
  98. label.insertAdjacentElement('afterend', inputElement);
  99.  
  100. if (callbackFunction) {
  101. _addCallback(() => callbackFunction(settingName, checkbox.checked));
  102. } else {
  103. _addCallback(async () => await GM.setValue(settingName, checkbox.checked));
  104. }
  105. }
  106.  
  107. async function addDropdown(configuration) {
  108. let {
  109. categoryName,
  110. settingName,
  111. labelText,
  112. lableTooltip = undefined,
  113. options, // [{ value: 'value', text: 'text', ... }]
  114. currentSetting = undefined,
  115. defaultSetting = undefined,
  116. callbackFunction = undefined
  117. } = configuration;
  118.  
  119. const header = await _addCategoryHeader(categoryName);
  120. if (currentSetting === undefined) {
  121. currentSetting = await GM.getValue(settingName, defaultSetting);
  122. }
  123.  
  124. const select = document.createElement('select');
  125. select.name = settingName;
  126. select.classList.add('form-control');
  127.  
  128. options.forEach(option => {
  129. const optionElement = document.createElement('option');
  130. optionElement.value = option.value;
  131. optionElement.textContent = option.text;
  132. if (option.value === currentSetting) {
  133. optionElement.selected = true;
  134. }
  135. select.appendChild(optionElement);
  136. });
  137. const label = _createLabel(labelText);
  138. _formatLabel(label, lableTooltip);
  139. _addBelowHeader(header, label);
  140.  
  141. const inputElement = _createInput(select);
  142. label.insertAdjacentElement('afterend', inputElement);
  143.  
  144. if (callbackFunction) {
  145. _addCallback(() => callbackFunction(settingName, select.value));
  146. } else {
  147. _addCallback(async () => await GM.setValue(settingName, checkbox.checked));
  148. }
  149. }
  150.  
  151. function _formatLabel(label, lableTooltip) {
  152. if (lableTooltip) {
  153. const info = document.createElement('sup');
  154. info.textContent = ' ⓘ';
  155. label.appendChild(info);
  156.  
  157. const showTooltip = () => {
  158. const tooltip = document.getElementById('universal-userscript-tooltip');
  159. const hideTooltip = () => {
  160. tooltip.style.display = 'None';
  161. };
  162.  
  163. const closeButton = `<a href="#" id="tooltip-close" style="display: flex; justify-content: center; color: var(--link_color); margin-top: 0.5rem;">Close Tooltip</a>`;
  164. tooltip.innerHTML = lableTooltip + closeButton;
  165. tooltip.style.display = 'block';
  166. tooltip.querySelector('#tooltip-close').addEventListener('click', (event) => {
  167. event.preventDefault();
  168. hideTooltip();
  169. });
  170. };
  171.  
  172. label.addEventListener('click', (event) => {
  173. showTooltip(event);
  174. });
  175. }
  176. const colon = document.createTextNode('\u00A0:');
  177. label.appendChild(colon);
  178. }
  179.  
  180. function _addBelowHeader(header, label) {
  181. let nextHeader = header.nextElementSibling;
  182. while (nextHeader && !nextHeader.matches('.header')) {
  183. nextHeader = nextHeader.nextElementSibling;
  184. }
  185. if (!nextHeader) {
  186. nextHeader = document.querySelector('#universal-userscript-preferences>.market_grid.profile.prefs.margin-auto>.footer.small-gap');
  187. }
  188. nextHeader.insertAdjacentElement('beforebegin', label);
  189. }
  190.  
  191. function _addSettingsLink() {
  192. const existingNav = document.querySelector('nav.center.margin-1');
  193. if (existingNav) {
  194. const newNav = document.createElement('nav');
  195. newNav.classList.add('center', 'margin-1');
  196. newNav.id = 'universal-userscript-navigation';
  197. const newLink = document.createElement('a');
  198. newLink.href = '/help/userscripts/';
  199. newLink.textContent = 'Userscript Preferences';
  200. newNav.appendChild(newLink);
  201. existingNav.after(newNav);
  202. } else {
  203. console.error('Existing navigation element not found.');
  204. }
  205. }
  206.  
  207. function _createLabel(labelText) {
  208. const labelContainer = document.createElement('div');
  209. labelContainer.classList.add('data', 'left');
  210.  
  211. const label = document.createElement('span');
  212. label.textContent = labelText;
  213. labelContainer.appendChild(label);
  214. return labelContainer;
  215. }
  216.  
  217. function _createInput(input) {
  218. const inputContainer = document.createElement('div');
  219. inputContainer.classList.add('data', 'flex-column', 'right');
  220. inputContainer.appendChild(input);
  221. return inputContainer;
  222. }
  223.  
  224. function _addCallback(callbackFunction) {
  225. document.getElementById('universal-userscript-update').addEventListener('click', callbackFunction);
  226. }
  227.  
  228. async function _addCategoryHeader(categoryName) {
  229. if (!window.location.href.includes('/help/userscripts/')) throw Error('Attempted to add setting outside of settings page.');
  230. await _checkSettingsSetup();
  231. const settings = document.querySelector('.market_grid.profile.prefs.margin-auto');
  232. const footer = document.querySelector('#universal-userscript-preferences>.market_grid.profile.prefs.margin-auto>.footer.small-gap');
  233. if (!settings) {
  234. console.error('Settings not found.');
  235. return;
  236. }
  237.  
  238. const headers = Array.from(settings.querySelectorAll('.header'));
  239. let header = headers.find(header => header.textContent.trim() === categoryName);
  240.  
  241. if (!header) {
  242. header = document.createElement('div');
  243. header.classList.add('header');
  244. header.innerHTML = `<strong>${categoryName}</strong>`;
  245. const insertionPoint = headers.find(existingHeader => existingHeader.textContent.trim().localeCompare(categoryName) > 0);
  246. if (insertionPoint) {
  247. insertionPoint.insertAdjacentElement('beforebegin', header);
  248. } else {
  249. footer.insertAdjacentElement('beforebegin', header);
  250. }
  251. }
  252.  
  253. return header;
  254. }
  255.  
  256. function _replaceBannerImage() {
  257. const bannerImage = document.getElementById('top-header-image');
  258. if (bannerImage) {
  259. bannerImage.src = 'https://grundoscafe.b-cdn.net/misc/banners/userinfo.gif';
  260. } else {
  261. console.error('Banner image not found.');
  262. }
  263. }
  264.  
  265. function _setupUniversalSettings() {
  266. const helpPages = /\/help\/(?:profile|siteprefs|sidebar|randomevents)\/|\/discord\//;
  267. const settings = window.location.href.includes('/help/userscripts/');
  268. const help = helpPages.test(window.location.href);
  269. if (help) {
  270. _addSettingsLink();
  271. } else if (settings) {
  272. _replaceBannerImage();
  273. const element = document.querySelector('main');
  274. if (element) {
  275. element.id = 'universal-userscript-preferences';
  276. element.innerHTML = `
  277. <h1>Userscript Preferences</h1>
  278. <nav class="center margin-1">
  279. <a href="/help/profile/">Edit Profile</a> |
  280. <a href="/help/siteprefs/">Site Preferences</a> |
  281. <a href="/help/sidebar/">Edit Sidebar</a> |
  282. <a href="/discord/">Discord</a> |
  283. <a href="/help/randomevents/">Random Event Log</a>
  284. </nav>
  285. <nav class="center margin-1" id="universal-userscript-navigation"><a href="/help/userscripts/">Userscript Preferences</a></nav>
  286. <p>Here you can adjust settings for participating userscripts.</p>
  287. <div id="universal-userscript-tooltip" style="display: none; background-color: var(--grid_odd); padding: 10px; border-radius: 5px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); z-index: 1000; margin-bottom: 1rem; border: 1px solid var(--grid_head);"></div>
  288. <div class="market_grid profile prefs margin-auto">
  289. <div class="footer small-gap">
  290. <input class="form-control half-width" type="submit" value="Update Preferences" id="universal-userscript-update">
  291. </div>
  292. </div>
  293. `;
  294. document.getElementById('universal-userscript-update').addEventListener('click', () => {
  295. const button = document.getElementById('universal-userscript-update');
  296. button.disabled = true;
  297. const originalText = button.value;
  298. button.value = 'Preferences Updated!';
  299. setTimeout(() => {
  300. button.disabled = false;
  301. button.value = originalText;
  302. }, 2000);
  303. });
  304. }
  305. }
  306. }
  307.  
  308. async function _checkSettingsSetup() {
  309. return new Promise((resolve, reject) => {
  310. const interval = setInterval(() => {
  311. if (document.getElementById('universal-userscript-preferences') !== null) {
  312. clearInterval(interval);
  313. clearTimeout(timeout);
  314. resolve(true);
  315. }
  316. }, 50);
  317.  
  318. const timeout = setTimeout(() => {
  319. clearInterval(interval);
  320. reject(new Error('Timeout: universal-userscript-preferences element not found within 10 seconds'));
  321. }, 10000);
  322. });
  323. }
  324.  
  325. if (!document.getElementById('universal-userscript-navigation')) {
  326. _setupUniversalSettings();
  327. }