settings.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. app.Settings = class Settings {
  2. static PREFERENCE_KEYS = [
  3. "hideDisabled",
  4. "hideIntro",
  5. "manualUpdate",
  6. "fastScroll",
  7. "arrowScroll",
  8. "analyticsConsent",
  9. "docs",
  10. "dark", // legacy
  11. "theme",
  12. "layout",
  13. "size",
  14. "tips",
  15. "noAutofocus",
  16. "autoInstall",
  17. "spaceScroll",
  18. "spaceTimeout",
  19. ];
  20. static INTERNAL_KEYS = ["count", "schema", "version", "news"];
  21. static LAYOUTS = [
  22. "_max-width",
  23. "_sidebar-hidden",
  24. "_native-scrollbars",
  25. "_text-justify-hyphenate",
  26. ];
  27. static defaults = {
  28. count: 0,
  29. hideDisabled: false,
  30. hideIntro: false,
  31. news: 0,
  32. manualUpdate: false,
  33. schema: 1,
  34. analyticsConsent: false,
  35. theme: "auto",
  36. spaceScroll: 1,
  37. spaceTimeout: 0.5,
  38. };
  39. constructor() {
  40. this.store = new CookiesStore();
  41. this.cache = {};
  42. this.autoSupported =
  43. window.matchMedia("(prefers-color-scheme)").media !== "not all";
  44. if (this.autoSupported) {
  45. this.darkModeQuery = window.matchMedia("(prefers-color-scheme: dark)");
  46. this.darkModeQuery.addListener(() => this.setTheme(this.get("theme")));
  47. }
  48. }
  49. get(key) {
  50. let left;
  51. if (this.cache.hasOwnProperty(key)) {
  52. return this.cache[key];
  53. }
  54. this.cache[key] =
  55. (left = this.store.get(key)) != null
  56. ? left
  57. : this.constructor.defaults[key];
  58. if (key === "theme" && this.cache[key] === "auto" && !this.darkModeQuery) {
  59. return (this.cache[key] = "default");
  60. } else {
  61. return this.cache[key];
  62. }
  63. }
  64. set(key, value) {
  65. this.store.set(key, value);
  66. delete this.cache[key];
  67. if (key === "theme") {
  68. this.setTheme(value);
  69. }
  70. }
  71. del(key) {
  72. this.store.del(key);
  73. delete this.cache[key];
  74. }
  75. hasDocs() {
  76. try {
  77. return !!this.store.get("docs");
  78. } catch (error) {}
  79. }
  80. getDocs() {
  81. return this.store.get("docs")?.split("/") || app.config.default_docs;
  82. }
  83. setDocs(docs) {
  84. this.set("docs", docs.join("/"));
  85. }
  86. getTips() {
  87. return this.store.get("tips")?.split("/") || [];
  88. }
  89. setTips(tips) {
  90. this.set("tips", tips.join("/"));
  91. }
  92. setLayout(name, enable) {
  93. this.toggleLayout(name, enable);
  94. const layout = (this.store.get("layout") || "").split(" ");
  95. $.arrayDelete(layout, "");
  96. if (enable) {
  97. if (!layout.includes(name)) {
  98. layout.push(name);
  99. }
  100. } else {
  101. $.arrayDelete(layout, name);
  102. }
  103. if (layout.length > 0) {
  104. this.set("layout", layout.join(" "));
  105. } else {
  106. this.del("layout");
  107. }
  108. }
  109. hasLayout(name) {
  110. const layout = (this.store.get("layout") || "").split(" ");
  111. return layout.includes(name);
  112. }
  113. setSize(value) {
  114. this.set("size", value);
  115. }
  116. dump() {
  117. return this.store.dump();
  118. }
  119. export() {
  120. const data = this.dump();
  121. for (var key of Settings.INTERNAL_KEYS) {
  122. delete data[key];
  123. }
  124. return data;
  125. }
  126. import(data) {
  127. let key, value;
  128. const object = this.export();
  129. for (key in object) {
  130. value = object[key];
  131. if (!data.hasOwnProperty(key)) {
  132. this.del(key);
  133. }
  134. }
  135. for (key in data) {
  136. value = data[key];
  137. if (Settings.PREFERENCE_KEYS.includes(key)) {
  138. this.set(key, value);
  139. }
  140. }
  141. }
  142. reset() {
  143. this.store.reset();
  144. this.cache = {};
  145. }
  146. initLayout() {
  147. if (this.get("dark") === 1) {
  148. this.set("theme", "dark");
  149. this.del("dark");
  150. }
  151. this.setTheme(this.get("theme"));
  152. for (var layout of app.Settings.LAYOUTS) {
  153. this.toggleLayout(layout, this.hasLayout(layout));
  154. }
  155. this.initSidebarWidth();
  156. }
  157. setTheme(theme) {
  158. if (theme === "auto") {
  159. theme = this.darkModeQuery.matches ? "dark" : "default";
  160. }
  161. const { classList } = document.documentElement;
  162. classList.remove("_theme-default", "_theme-dark");
  163. classList.add("_theme-" + theme);
  164. this.updateColorMeta();
  165. }
  166. updateColorMeta() {
  167. const color = getComputedStyle(document.documentElement)
  168. .getPropertyValue("--headerBackground")
  169. .trim();
  170. $("meta[name=theme-color]").setAttribute("content", color);
  171. }
  172. toggleLayout(layout, enable) {
  173. const { classList } = document.body;
  174. // sidebar is always shown for settings; its state is updated in app.views.Settings
  175. if (layout !== "_sidebar-hidden" || !app.router?.isSettings) {
  176. classList.toggle(layout, enable);
  177. }
  178. classList.toggle("_overlay-scrollbars", $.overlayScrollbarsEnabled());
  179. }
  180. initSidebarWidth() {
  181. const size = this.get("size");
  182. if (size) {
  183. document.documentElement.style.setProperty("--sidebarWidth", size + "px");
  184. }
  185. }
  186. };