content.js 5.3 KB

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