R4 Settings

R4 Settings Library

ეს სკრიპტი არ უნდა იყოს პირდაპირ დაინსტალირებული. ეს ბიბლიოთეკაა, სხვა სკრიპტებისთვის უნდა ჩართეთ მეტა-დირექტივაში // @require https://update.greatest.deepsurf.us/scripts/482052/1502912/R4%20Settings.js.

  1. // ==UserScript==
  2. // @name R4 Settings
  3. // @description R4 Settings Library
  4. // @version 1.4.9
  5. // @grant GM.info
  6. // @grant GM.addStyle
  7. // @grant GM.xmlHttpRequest
  8. // @grant GM.setValue
  9. // @grant GM.getValue
  10. // @grant GM.deleteValue
  11. // @require https://update.greatest.deepsurf.us/scripts/482042/1298685/R4%20Images.js
  12. // @require https://update.greatest.deepsurf.us/scripts/482597/1301960/R4%20Utils.js
  13. // ==/UserScript==
  14.  
  15. /* ------------------------------------------------- */
  16. /* --------------Polyfills-------------------------- */
  17. /* ------------------------------------------------- */
  18.  
  19.  
  20. // Polyfill for Greasemonkey extension
  21.  
  22. function R4_addStyle (aCss) {
  23. let head = document.getElementsByTagName('head')[0];
  24. if (head) {
  25. let style = document.createElement('style');
  26. style.setAttribute('type', 'text/css');
  27. style.textContent = aCss;
  28. head.appendChild(style);
  29. return style;
  30. }
  31. return null;
  32. };
  33. if (GM.info?.scriptHandler === 'Userscripts') {
  34. console.log("Overriding GM.addStyle for Safari Userscripts extension.");
  35. GM.addStyle = R4_addStyle;
  36. }
  37. // Polyfill for Safari Userscripts extension
  38.  
  39. const original_GM_xmlHttpRequest = GM.xmlHttpRequest;
  40.  
  41. async function R4_xmlHttpRequest(details) {
  42.  
  43. if (details.onload) {
  44. const original_onload = details.onload;
  45. details.onload = (response) => {
  46. if (response.responseURL) {
  47. response.finalUrl = response.responseURL;
  48. }
  49. return original_onload(response);
  50. }
  51. }
  52.  
  53. return await original_GM_xmlHttpRequest(details);
  54. }
  55.  
  56. GM.xmlHttpRequest = R4_xmlHttpRequest;
  57.  
  58.  
  59. /* ------------------------------------------------- */
  60. /* --------------Settings--------------------------- */
  61. /* ------------------------------------------------- */
  62.  
  63. function R4Settings(options = {}) {
  64.  
  65. const utils = R4Utils();
  66. const images = R4Images();
  67.  
  68. GM.addStyle(`
  69. /* css */
  70.  
  71. /* Settings */
  72.  
  73. .r4-settings {
  74. position: relative;
  75. }
  76.  
  77. .r4-settings > ul {
  78. width: 350px;
  79. background: #313131;
  80. border-top: 0;
  81. position: absolute;
  82. top: 50px;
  83. left: 0px;
  84. white-space: nowrap;
  85. box-shadow: 0 5px 20px 0px #000;
  86. border-color: #222d33;
  87. border-style: solid;
  88. border-width: 3px 3px 3px 3px;
  89. padding: 5px 0 0 0;
  90. }
  91. .r4-settings > ul:before {
  92. content: '';
  93. display: block;
  94. position: absolute;
  95. top: -13px;
  96. left: 20px;
  97. width: 0;
  98. height: 0;
  99. border-left: 10px solid transparent;
  100. border-right: 10px solid transparent;
  101. border-bottom: 10px solid #222d33;
  102. }
  103.  
  104. .r4-settings > ul:after {
  105. content: '';
  106. display: block;
  107. position: absolute;
  108. top: -9px;
  109. left: 21px;
  110. width: 0;
  111. height: 0;
  112. border-left: 9px solid transparent;
  113. border-right: 9px solid transparent;
  114. border-bottom: 9px solid #313131;
  115. }
  116.  
  117. .r4-settings > ul > li,
  118. .r4-setting-submenu > ul > li {
  119. color: #777;
  120. font-size: 10px;
  121. font-weight: bold;
  122. margin: 0 !important;
  123. padding-left: 10px;
  124. padding-right: 10px;
  125. padding-top: 5px;
  126. padding-bottom: 5px;
  127. min-height: 30px;
  128. }
  129.  
  130.  
  131. .r4-settings > ul > li .r4-setting,
  132. .r4-setting-submenu > ul > li .r4-setting {
  133. display: inline-block;
  134. width: 100%;
  135. }
  136.  
  137. .r4-settings > ul > li .r4-tumbler,
  138. .r4-setting-submenu > ul > li .r4-tumbler {
  139. float: right;
  140. }
  141.  
  142. .r4-settings .r4-setting-header {
  143. text-align: center;
  144. }
  145.  
  146. .r4-settings .r4-setting-text-value {
  147. display: block;
  148. opacity: .5;
  149. }
  150.  
  151. .r4-settings .r4-setting-text-block {
  152. float: left;
  153. position: relative;
  154. padding-top: 5px;
  155. }
  156.  
  157. .r4-setting-submenu {
  158. position: relative;
  159. cursor: pointer;
  160. }
  161.  
  162. .r4-setting-submenu > ul {
  163. background: #212121;
  164. margin: 30px -10px 0;
  165. padding: 10px 0;
  166. cursor: auto;
  167. }
  168.  
  169. .r4-settings > ul > li:last-child .r4-setting-submenu > ul {
  170. margin-bottom: -5px;
  171. }
  172.  
  173. .r4-setting-submenu-arrow {
  174. float: right;
  175. width: 15px;
  176. height: 15px;
  177. margin-right: 10px;
  178. margin-top: 5px;
  179. background-size: 15px 15px;
  180. background-repeat: no-repeat;
  181. background-image: url(${images.arrow});
  182. filter: invert(100%) sepia(95%) saturate(21%) hue-rotate(280deg) brightness(106%) contrast(106%);
  183. transform: rotate(180deg);
  184. }
  185.  
  186. /* Tumbler */
  187.  
  188. .r4-tumbler {
  189. width: 38px;
  190. height: 30px;
  191. background-color: #000;
  192. border: #1d92b2;
  193. border-radius: 30px;
  194. display: flex;
  195. justify-content: space-between;
  196. align-items: center;
  197. padding: 0 6px;
  198. cursor: pointer;
  199. position: relative;
  200. user-select: none;
  201. box-sizing: content-box;
  202. }
  203. .r4-tumbler-point {
  204. border-radius: 50%;
  205. content: '';
  206. display: block;
  207. height: 20px;
  208. width: 20px;
  209. background-color: #999;
  210. background-clip: content-box;
  211. box-sizing: border-box;
  212. border-color: transparent;
  213. border-style: solid;
  214. border-width: 5px;
  215. }
  216. .r4-tumbler > .r4-tumbler-dot {
  217. position: absolute;
  218. height: 20px;
  219. width: 20px;
  220. border-radius: 50%;
  221. background-color: #fff;
  222. transition: transform .5s,background-color .5s;
  223. will-change: transform;
  224. }
  225.  
  226. /* Tumbler On-Off */
  227.  
  228. .r4-on-of-tumbler .r4-tumbler-point:nth-child(1) {
  229. background-color: green;
  230. }
  231. .r4-on-of-tumbler .r4-tumbler-point:nth-child(2) {
  232. background-color: indianred;
  233. }
  234.  
  235. /* Tumbler Settings */
  236.  
  237. .r4-tumbler-settings {
  238. width: 40px !important;
  239. }
  240. .r4-tumbler-settings .r4-tumbler-point {
  241. background-size: 15px 15px;
  242. background-repeat: no-repeat;
  243. background-position: center;
  244. border-width: 2px;
  245. }
  246. .r4-tumbler-settings .r4-tumbler-point:nth-child(1) {
  247. background-image: url('${images.settings}');
  248. background-color: transparent !important;
  249. }
  250. .r4-tumbler-settings .r4-tumbler-point:nth-child(2) {
  251. background-image: url('${images.settingsclose}');
  252. background-color: transparent !important;
  253. }
  254.  
  255. .r4-tumbler-settings-update,
  256. .r4-tumbler-settings-update:hover {
  257. height: 30px;
  258. background: #f4363630;
  259. position: absolute;
  260. left: 0;
  261. margin-left: 30px;
  262. margin-top: 5px;
  263. border-radius: 30px;
  264. color: #b44b44 !important;
  265. line-height: 30px;
  266. padding: 0 20px 0 40px;
  267. cursor: pointer;
  268. text-decoration: none;
  269. }
  270.  
  271. /* Tooltip */
  272.  
  273. .r4-tooltip {
  274. position: relative;
  275. display: inline-block;
  276. }
  277.  
  278. .r4-tooltip .tooltiptext {
  279. background: #313131;
  280. border-top: 0;
  281. position: absolute;
  282. top: -10px;
  283. left: 35px;
  284. white-space: nowrap;
  285. box-shadow: 0 5px 20px 0px #000;
  286. border-color: #222d33;
  287. border-style: solid;
  288. border-width: 3px;
  289. visibility: hidden;
  290. width: 300px;
  291. white-space: normal;
  292. padding: 15px;
  293. position: absolute;
  294. z-index: 3;
  295. }
  296.  
  297. .r4-tooltip:hover .tooltiptext {
  298. visibility: visible;
  299. }
  300.  
  301. .r4-tooltip .tooltiptext:before {
  302. content: '';
  303. display: block;
  304. position: absolute;
  305. left: -13px;
  306. top: 11px;
  307. width: 0;
  308. height: 0;
  309. border-top: 10px solid transparent;
  310. border-bottom: 10px solid transparent;
  311. border-right: 10px solid #222d33;
  312. }
  313.  
  314. .r4-tooltip .tooltiptext:after {
  315. content: '';
  316. display: block;
  317. position: absolute;
  318. left: -9px;
  319. top: 12px;
  320. width: 0;
  321. height: 0;
  322. border-top: 9px solid transparent;
  323. border-bottom: 9px solid transparent;
  324. border-right: 9px solid #222d33;
  325. }
  326.  
  327. .r4-tooltip-icon {
  328. border-radius: 50%;
  329. background: #777;
  330. width: 14px;
  331. height: 14px;
  332. display: inline-block;
  333. text-align: center;
  334. color: #000;
  335. text-transform: lowercase;
  336. cursor: pointer;
  337. font-family: monospace, monospace;
  338. font-size: 13px;
  339. margin: 8px;
  340. }
  341.  
  342. /* !css */
  343. `);
  344.  
  345. const state = {
  346. events: {
  347. start: {
  348. fired: false,
  349. },
  350. end: {
  351. fired: false,
  352. },
  353. }
  354. };
  355.  
  356. const elements = {
  357. tumbler: null,
  358. dropdown: null,
  359. };
  360.  
  361. buildSettings();
  362.  
  363. async function setSetting(name, value) {
  364. await GM.setValue(name, value);
  365. console.debug(`Saved setting ${name}: ${JSON.stringify(value)}`);
  366. }
  367.  
  368. async function deleteSetting(name) {
  369. await GM.deleteValue(name);
  370. }
  371.  
  372. async function getSetting(name) {
  373. const value = await GM.getValue(name);
  374. if (value === undefined) {
  375. return options.missingSettingHandler?.(name);
  376. }
  377. console.debug(`Got setting ${name}: ${JSON.stringify(value)}`);
  378. return value;
  379. }
  380.  
  381. async function setCongigSetting(config, option) {
  382. if (option.value === undefined) {
  383. await deleteSetting(config.name);
  384. }
  385. await setSetting(config.name, option.value);
  386. }
  387.  
  388. async function getConfigSetting(config) {
  389. return await getSetting(config.name);
  390. }
  391.  
  392. async function getCurrentOption(config) {
  393. const currentSetting = await getConfigSetting(config);
  394.  
  395. for (const tumblerOption of config.options) {
  396. const optionSetting = tumblerOption.value;
  397. if (optionSetting === currentSetting) {
  398. return tumblerOption;
  399. }
  400. }
  401.  
  402. const option = getDefaultOption(config);
  403. await setCongigSetting(config, option);
  404. return option;
  405. }
  406.  
  407. async function rotateSetting(config) {
  408. const currentOption = await getCurrentOption(config);
  409. const nextOption = getNextOption(config, currentOption);
  410. await setCongigSetting(config, nextOption);
  411. setBodyClass(config, nextOption);
  412. if (nextOption.reload === true) {
  413. document.location.reload();
  414. }
  415. if (nextOption.start) {
  416. nextOption.start();
  417. }
  418. if (nextOption.end) {
  419. nextOption.end();
  420. }
  421. }
  422.  
  423. function getDefaultOption(config) {
  424. for (const tumblerOption of config.options) {
  425. if (tumblerOption.default === true) {
  426. return tumblerOption;
  427. }
  428. }
  429. return config.options[0];
  430. }
  431.  
  432. function setBodyClass(config, option) {
  433. for (const tumblerOption of config.options) {
  434. if (tumblerOption.class) {
  435. document.body.classList.remove(tumblerOption.class);
  436. }
  437. }
  438.  
  439. if (option?.class) {
  440. document.body.classList.add(option.class);
  441. }
  442. }
  443.  
  444. function getNextOption(config, option) {
  445. let nextOptionIndex;
  446. if (option) {
  447. const currentOptionIndex = config.options.indexOf(option);
  448. if (currentOptionIndex < config.options.length - 1) {
  449. nextOptionIndex = currentOptionIndex + 1;
  450. } else {
  451. nextOptionIndex = 0;
  452. }
  453. } else {
  454. nextOptionIndex = 1;
  455. }
  456. return config.options[nextOptionIndex];
  457. }
  458.  
  459. function afterStart(callback) {
  460. if (state.events.start.fired === true) {
  461. callback();
  462. } else {
  463. document.addEventListener("R4SettingsStart", callback);
  464. }
  465. }
  466.  
  467. function afterEnd(callback) {
  468. if (state.events.end.fired === true) {
  469. callback();
  470. } else {
  471. document.addEventListener("R4SettingsEnd", callback);
  472. }
  473. }
  474.  
  475. async function initSetting(config) {
  476. const currentOption = await getCurrentOption(config);
  477. afterStart(() => {
  478. setBodyClass(config, currentOption);
  479. });
  480. if (config?.start) {
  481. afterStart(() => {
  482. config.start();
  483. });
  484. }
  485. if (currentOption?.start) {
  486. afterStart(() => {
  487. currentOption.start();
  488. });
  489. }
  490. if (config?.end) {
  491. afterEnd(() => {
  492. config.end();
  493. });
  494. }
  495. if (currentOption?.end) {
  496. afterEnd(() => {
  497. currentOption.end();
  498. });
  499. }
  500. }
  501.  
  502. function buildSettings() {
  503. elements.tumbler = buildTumbler({
  504. handler: toggle,
  505. name: "settings",
  506. classes: ["r4-settings", "pull-right"],
  507. options: [
  508. {
  509. class: null,
  510. },
  511. {
  512. class: "r4-settings-active",
  513. },
  514. ],
  515. });
  516.  
  517. elements.dropdown = utils.fromHTML(
  518. /* html */
  519. `
  520. <!-- html -->
  521. <ul class="hidden"></ul>
  522. <!-- !html -->
  523. `
  524. );
  525.  
  526. elements.tumbler.appendChild(elements.dropdown);
  527.  
  528. const header = utils.fromHTML(
  529. /* html */
  530. `
  531. <!-- html -->
  532. <div class="r4-setting-header"></div>
  533. <!-- !html -->
  534. `
  535. );
  536.  
  537. if (options.script_homepage) {
  538. header.appendChild(utils.fromHTML(
  539. /* html */
  540. `
  541. <!-- html -->
  542. <div class="r4-setting-label">
  543. <a href="${options.script_homepage}" target="_blank">
  544. ${GM.info.script.name}
  545. </a>
  546. </div>
  547. <!-- !html -->
  548. `
  549. ));
  550. header.appendChild(utils.fromHTML(
  551. /* html */
  552. `
  553. <!-- html -->
  554. <div class="r4-setting-text-value">
  555. <a href="${options.script_homepage}/feedback" target="_blank">
  556. ${options.feedback_text || "Feedback"}
  557. </a>
  558. </div>
  559. <!-- !html -->
  560. `
  561. ));
  562.  
  563. GM.xmlHttpRequest({
  564. method: "GET",
  565. url: options.script_homepage,
  566. onload(response) {
  567. console.debug(`Response ${response.status} for ${response.finalUrl}`, {response});
  568. if (response.status === 200) {
  569. const patern = /<a class="install-link" [^>]* data-script-version="(?<version>[^"]*)" [^>]* href="(?<href>[^"]*)"[^>]*>/;
  570. const results = patern.exec(response.responseText);
  571. if (!results?.groups) {
  572. console.debug(`Failed to parse install link`);
  573. return;
  574. }
  575. if (results.groups.version == GM.info.script.version) {
  576. return;
  577. }
  578. console.log(`New version ${results.groups.version} is available`);
  579.  
  580. elements.tumbler.insertBefore(utils.fromHTML(
  581. /* html */
  582. `
  583. <!-- html -->
  584. <a class="r4-tumbler-settings-update" href="${results.groups.href}" target="_blank">
  585. ${options.update_text || "Update"}
  586. </a>
  587. <!-- !html -->
  588. `
  589. ), elements.tumbler.firstChild);
  590. }
  591. },
  592. onerror(e) {
  593. console.debug(`Failed to request install link`);
  594. console.debug("Error:", {e});
  595. },
  596. });
  597.  
  598. } else {
  599. header.appendChild(utils.fromHTML(
  600. /* html */
  601. `
  602. <!-- html -->
  603. <div class="r4-setting-label">
  604. ${GM.info.script.name}
  605. </div>
  606. <!-- !html -->
  607. `
  608. ));
  609.  
  610. }
  611. header.appendChild(utils.fromHTML(
  612. /* html */
  613. `
  614. <!-- html -->
  615. <div class="r4-setting-text-value">
  616. ${options.version_text || "Version"}: ${GM.info.script.version}
  617. </div>
  618. <!-- !html -->
  619. `
  620. ));
  621.  
  622. addElementSetting(header);
  623.  
  624. document.addEventListener("click", close);
  625. }
  626.  
  627. function toggle(event) {
  628. elements.dropdown.classList.toggle("hidden");
  629. event.stopPropagation();
  630. event.preventDefault();
  631. }
  632.  
  633. function close(event) {
  634. if (!event.target.closest(".r4-settings")) {
  635. elements.dropdown.classList.add("hidden");
  636. }
  637. }
  638.  
  639. function findSubmenu(config) {
  640. const submenuAll = elements.tumbler.querySelectorAll(".r4-setting-submenu");
  641. const submenuFiltered = Array.from(submenuAll).find(
  642. (el) => el.querySelector(".r4-setting-label").textContent === config.submenu
  643. );
  644. if (submenuFiltered) {
  645. return submenuFiltered.querySelector("ul");
  646. }
  647. }
  648.  
  649. function createSubmenu(config) {
  650. const submenuItem = utils.fromHTML(
  651. /* html */
  652. `
  653. <!-- html -->
  654. <li>
  655. <div class="r4-setting r4-setting-submenu">
  656. <span class="r4-setting-submenu-arrow"></span>
  657. <ul class="hidden"></ul>
  658. </div>
  659. </li>
  660. <!-- !html -->
  661. `
  662. )
  663.  
  664. const submenuElem = submenuItem.querySelector(".r4-setting-submenu");
  665. submenuElem.insertBefore(
  666. buildSettingTextBlock(config.submenu),
  667. submenuElem.firstChild
  668. );
  669.  
  670. const submenu = submenuElem.querySelector("ul");
  671. submenu.addEventListener("click", (event) => {
  672. event.stopPropagation();
  673. });
  674.  
  675. submenuItem.addEventListener("click", (event) => {
  676. submenu.classList.toggle("hidden");
  677. });
  678.  
  679. elements.dropdown.appendChild(submenuItem);
  680. return submenu;
  681. }
  682.  
  683. function addElementSetting(element, config) {
  684. let container;
  685.  
  686. if (config?.submenu) {
  687. let submenu = findSubmenu(config);
  688. if (!submenu) {
  689. submenu = createSubmenu(config);
  690. }
  691. container = submenu;
  692. } else {
  693. const dropdown = elements.tumbler.querySelector("ul");
  694. container = dropdown;
  695. }
  696.  
  697. const item = document.createElement("li");
  698. item.appendChild(element);
  699. container.appendChild(item);
  700. }
  701.  
  702. function buildTumbler(config) {
  703. const optionsLength = config.options.length;
  704. const tumblerClassName = `r4-tumbler-${config.name}`;
  705.  
  706. GM.addStyle(`
  707. /* css */
  708.  
  709. .${tumblerClassName} {
  710. width: ${optionsLength * 15 + optionsLength * 5}px !important;
  711. }
  712.  
  713. /* !css */
  714. `);
  715.  
  716. const tumblerWrapper = utils.fromHTML(
  717. /* html */
  718. `
  719. <!-- html -->
  720. <div class="r4-tumbler-wrapper ${config.classes.join(" ")}">
  721. <div class="r4-tumbler ${tumblerClassName}"></div>
  722. </div>
  723. <!-- !html -->
  724. `
  725. );
  726.  
  727. const tumbler = tumblerWrapper.querySelector(".r4-tumbler");
  728. tumbler.addEventListener("click", config.handler);
  729.  
  730. for (let optionIndex = 0; optionIndex < optionsLength; optionIndex++) {
  731. const tumblerOption = config.options[optionIndex];
  732. const tumblerPoint = utils.fromHTML(
  733. /* html */
  734. `
  735. <!-- html -->
  736. <div class="r4-tumbler-point"></div>
  737. <!-- !html -->
  738. `
  739. );
  740. tumbler.appendChild(tumblerPoint);
  741.  
  742. if (tumblerOption.class) {
  743. GM.addStyle(`
  744. /* css */
  745.  
  746. .${tumblerOption.class} .${tumblerClassName} .r4-tumbler-dot {
  747. transform: translateX(${optionIndex * 100}%) !important;
  748. }
  749.  
  750. /* !css */
  751. `);
  752. } else {
  753. GM.addStyle(`
  754. /* css */
  755.  
  756. .${tumblerClassName} .r4-tumbler-dot {
  757. transform: translateX(${optionIndex * 100}%);
  758. }
  759.  
  760. /* !css */
  761. `);
  762. }
  763. }
  764.  
  765. tumbler.appendChild(utils.fromHTML(
  766. /* html */
  767. `
  768. <!-- html -->
  769. <div class="r4-tumbler-dot"></div>
  770. <!-- !html -->
  771. `
  772. ));
  773.  
  774. return tumblerWrapper;
  775. }
  776.  
  777. function buildSettingTextBlock(label) {
  778. return utils.fromHTML(
  779. /* html */
  780. `
  781. <!-- html -->
  782. <div class="r4-setting-text-block">
  783. <span class="r4-setting-label">${label}</span>
  784. </div>
  785. <!-- !html -->
  786. `
  787. );
  788. }
  789.  
  790. function buildTumblerSetting(config) {
  791. for (const tumplerOption of config.options) {
  792. if (tumplerOption.class === undefined && tumplerOption.value !== undefined && tumplerOption.value !== null) {
  793. tumplerOption.class = `${config.name}-${tumplerOption.value}`;
  794. }
  795. if (tumplerOption.value === undefined && tumplerOption.class !== undefined) {
  796. tumplerOption.value = tumplerOption.class;
  797. }
  798. }
  799.  
  800. initSetting(config);
  801. const originalHandler = config.handler;
  802. config.handler = async (event) => {
  803. await rotateSetting(config);
  804. originalHandler?.(event);
  805. };
  806.  
  807. const tumblerWrapper = buildTumbler(config);
  808. tumblerWrapper.classList.add("r4-setting");
  809.  
  810. const settingClass = `r4-setting-${config.name}`;
  811. tumblerWrapper.classList.add(settingClass);
  812.  
  813. const settingTextBlock = buildSettingTextBlock(config.label);
  814.  
  815. let textValueClassEmpty = null;
  816. for (const tumblerOption of config.options) {
  817. if (!tumblerOption.class) {
  818. const optionIndex = config.options.indexOf(tumblerOption);
  819. textValueClassEmpty = `r4-setting-text-value-${optionIndex + 1}`;
  820. }
  821. }
  822.  
  823. const emptySelectors = [];
  824. for (const tumblerOption of config.options) {
  825. if (tumblerOption.class) {
  826. emptySelectors.push(`body.${tumblerOption.class} .${settingClass} .${textValueClassEmpty}`);
  827. }
  828. }
  829.  
  830. for (const tumblerOption of config.options) {
  831. const optionIndex = config.options.indexOf(tumblerOption);
  832. const textValueClass = `r4-setting-text-value-${optionIndex + 1}`;
  833.  
  834. settingTextBlock.appendChild(utils.fromHTML(
  835. /* html */
  836. `
  837. <!-- html -->
  838. <span class="r4-setting-text-value ${textValueClass}">
  839. ${tumblerOption.text}
  840. </span>
  841. <!-- !html -->
  842. `
  843. ));
  844.  
  845. if (tumblerOption.class) {
  846. GM.addStyle(`
  847. /* css */
  848.  
  849. body:not(.${tumblerOption.class}) .${settingClass} .${textValueClass} {
  850. display: none !important;
  851. }
  852.  
  853. /* !css */
  854. `);
  855. } else {
  856. GM.addStyle(`
  857. /* css */
  858.  
  859. ${emptySelectors.join(",")} {
  860. display: none !important;
  861. }
  862.  
  863. /* !css */
  864. `);
  865. }
  866. }
  867.  
  868. tumblerWrapper.appendChild(settingTextBlock);
  869.  
  870. return tumblerWrapper;
  871. }
  872.  
  873. function createTumblerSetting(config, wrapSetting = tumblerSetting => tumblerSetting) {
  874. const tumblerSetting = buildTumblerSetting(config);
  875. addElementSetting(wrapSetting(tumblerSetting), config);
  876. }
  877.  
  878. if (document.body) {
  879. state.events.start.fired = true;
  880. } else {
  881. new MutationObserver((mutationList, observer) => {
  882. if (document.body && !state.events.start.fired) {
  883. document.dispatchEvent(new Event("R4SettingsStart"));
  884. state.events.start.fired = true;
  885. observer.disconnect();
  886. }
  887. }).observe(document.documentElement, {childList: true});
  888. }
  889.  
  890. if (/complete|interactive|loaded/.test(document.readyState)) {
  891. state.events.end.fired = true;
  892. } else {
  893. document.addEventListener("DOMContentLoaded", () => {
  894. document.dispatchEvent(new Event("R4SettingsEnd"));
  895. state.events.end.fired = true;
  896. });
  897. }
  898.  
  899. return {
  900. tumbler: elements.tumbler,
  901. buildTumblerSetting,
  902. createTumblerSetting,
  903. addElementSetting,
  904. setSetting,
  905. getSetting,
  906. afterStart,
  907. afterEnd,
  908. }
  909. }