content.js 7.0 KB

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