settings.js 4.6 KB

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