content.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. // TODO: This file was created by bulk-decaffeinate.
  2. // Sanity-check the conversion and remove this comment.
  3. /*
  4. * decaffeinate suggestions:
  5. * DS102: Remove unnecessary code created because of implicit returns
  6. * DS103: Rewrite code to no longer use __guard__, or convert again using --optional-chaining
  7. * DS104: Avoid inline assignments
  8. * DS204: Change includes calls to have a more natural evaluation order
  9. * DS205: Consider reworking code to avoid use of IIFEs
  10. * DS206: Consider reworking classes to avoid initClass
  11. * DS207: Consider shorter variations of null checks
  12. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
  13. */
  14. app.views.Content = class Content extends app.View {
  15. static initClass() {
  16. this.el = "._content";
  17. this.loadingClass = "_content-loading";
  18. this.events = { click: "onClick" };
  19. this.shortcuts = {
  20. altUp: "scrollStepUp",
  21. altDown: "scrollStepDown",
  22. pageUp: "scrollPageUp",
  23. pageDown: "scrollPageDown",
  24. pageTop: "scrollToTop",
  25. pageBottom: "scrollToBottom",
  26. altF: "onAltF",
  27. };
  28. this.routes = {
  29. before: "beforeRoute",
  30. after: "afterRoute",
  31. };
  32. }
  33. init() {
  34. this.scrollEl = app.isMobile()
  35. ? document.scrollingElement || document.body
  36. : this.el;
  37. this.scrollMap = {};
  38. this.scrollStack = [];
  39. this.rootPage = new app.views.RootPage();
  40. this.staticPage = new app.views.StaticPage();
  41. this.settingsPage = new app.views.SettingsPage();
  42. this.offlinePage = new app.views.OfflinePage();
  43. this.typePage = new app.views.TypePage();
  44. this.entryPage = new app.views.EntryPage();
  45. this.entryPage
  46. .on("loading", () => this.onEntryLoading())
  47. .on("loaded", () => this.onEntryLoaded());
  48. app
  49. .on("ready", () => this.onReady())
  50. .on("bootError", () => this.onBootError());
  51. }
  52. show(view) {
  53. this.hideLoading();
  54. if (view !== this.view) {
  55. if (this.view != null) {
  56. this.view.deactivate();
  57. }
  58. this.html((this.view = view));
  59. this.view.activate();
  60. }
  61. }
  62. showLoading() {
  63. this.addClass(this.constructor.loadingClass);
  64. }
  65. isLoading() {
  66. return this.el.classList.contains(this.constructor.loadingClass);
  67. }
  68. hideLoading() {
  69. this.removeClass(this.constructor.loadingClass);
  70. }
  71. scrollTo(value) {
  72. this.scrollEl.scrollTop = value || 0;
  73. }
  74. smoothScrollTo(value) {
  75. if (app.settings.get("fastScroll")) {
  76. this.scrollTo(value);
  77. } else {
  78. $.smoothScroll(this.scrollEl, value || 0);
  79. }
  80. }
  81. scrollBy(n) {
  82. this.smoothScrollTo(this.scrollEl.scrollTop + n);
  83. }
  84. scrollToTop() {
  85. this.smoothScrollTo(0);
  86. }
  87. scrollToBottom() {
  88. this.smoothScrollTo(this.scrollEl.scrollHeight);
  89. }
  90. scrollStepUp() {
  91. this.scrollBy(-80);
  92. }
  93. scrollStepDown() {
  94. this.scrollBy(80);
  95. }
  96. scrollPageUp() {
  97. this.scrollBy(40 - this.scrollEl.clientHeight);
  98. }
  99. scrollPageDown() {
  100. this.scrollBy(this.scrollEl.clientHeight - 40);
  101. }
  102. scrollToTarget() {
  103. let el;
  104. if (
  105. this.routeCtx.hash &&
  106. (el = this.findTargetByHash(this.routeCtx.hash))
  107. ) {
  108. $.scrollToWithImageLock(el, this.scrollEl, "top", {
  109. margin: this.scrollEl === this.el ? 0 : $.offset(this.el).top,
  110. });
  111. $.highlight(el, { className: "_highlight" });
  112. } else {
  113. this.scrollTo(this.scrollMap[this.routeCtx.state.id]);
  114. }
  115. }
  116. onReady() {
  117. this.hideLoading();
  118. }
  119. onBootError() {
  120. this.hideLoading();
  121. this.html(this.tmpl("bootError"));
  122. }
  123. onEntryLoading() {
  124. this.showLoading();
  125. if (this.scrollToTargetTimeout) {
  126. clearTimeout(this.scrollToTargetTimeout);
  127. this.scrollToTargetTimeout = null;
  128. }
  129. }
  130. onEntryLoaded() {
  131. this.hideLoading();
  132. if (this.scrollToTargetTimeout) {
  133. clearTimeout(this.scrollToTargetTimeout);
  134. this.scrollToTargetTimeout = null;
  135. }
  136. this.scrollToTarget();
  137. }
  138. beforeRoute(context) {
  139. this.cacheScrollPosition();
  140. this.routeCtx = context;
  141. this.scrollToTargetTimeout = this.delay(this.scrollToTarget);
  142. }
  143. cacheScrollPosition() {
  144. if (!this.routeCtx || this.routeCtx.hash) {
  145. return;
  146. }
  147. if (this.routeCtx.path === "/") {
  148. return;
  149. }
  150. if (this.scrollMap[this.routeCtx.state.id] == null) {
  151. this.scrollStack.push(this.routeCtx.state.id);
  152. while (this.scrollStack.length > app.config.history_cache_size) {
  153. delete this.scrollMap[this.scrollStack.shift()];
  154. }
  155. }
  156. this.scrollMap[this.routeCtx.state.id] = this.scrollEl.scrollTop;
  157. }
  158. afterRoute(route, context) {
  159. if (route !== "entry" && route !== "type") {
  160. resetFavicon();
  161. }
  162. switch (route) {
  163. case "root":
  164. this.show(this.rootPage);
  165. break;
  166. case "entry":
  167. this.show(this.entryPage);
  168. break;
  169. case "type":
  170. this.show(this.typePage);
  171. break;
  172. case "settings":
  173. this.show(this.settingsPage);
  174. break;
  175. case "offline":
  176. this.show(this.offlinePage);
  177. break;
  178. default:
  179. this.show(this.staticPage);
  180. }
  181. this.view.onRoute(context);
  182. app.document.setTitle(
  183. typeof this.view.getTitle === "function"
  184. ? this.view.getTitle()
  185. : undefined,
  186. );
  187. }
  188. onClick(event) {
  189. const link = $.closestLink($.eventTarget(event), this.el);
  190. if (link && this.isExternalUrl(link.getAttribute("href"))) {
  191. $.stopEvent(event);
  192. $.popup(link);
  193. }
  194. }
  195. onAltF(event) {
  196. if (
  197. !document.activeElement ||
  198. !$.hasChild(this.el, document.activeElement)
  199. ) {
  200. __guard__(this.find("a:not(:empty)"), (x) => x.focus());
  201. return $.stopEvent(event);
  202. }
  203. }
  204. findTargetByHash(hash) {
  205. let el = (() => {
  206. try {
  207. return $.id(decodeURIComponent(hash));
  208. } catch (error) {}
  209. })();
  210. if (!el) {
  211. el = (() => {
  212. try {
  213. return $.id(hash);
  214. } catch (error1) {}
  215. })();
  216. }
  217. return el;
  218. }
  219. isExternalUrl(url) {
  220. let needle;
  221. return (
  222. (needle = __guard__(url, (x) => x.slice(0, 6))),
  223. ["http:/", "https:"].includes(needle)
  224. );
  225. }
  226. };
  227. app.views.Content.initClass();
  228. function __guard__(value, transform) {
  229. return typeof value !== "undefined" && value !== null
  230. ? transform(value)
  231. : undefined;
  232. }