content.js 6.1 KB

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