content.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  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. $.highlight(el, { className: "_highlight" });
  97. } else {
  98. this.scrollTo(this.scrollMap[this.routeCtx.state.id]);
  99. }
  100. }
  101. onReady() {
  102. this.hideLoading();
  103. }
  104. onBootError() {
  105. this.hideLoading();
  106. this.html(this.tmpl("bootError"));
  107. }
  108. onEntryLoading() {
  109. this.showLoading();
  110. if (this.scrollToTargetTimeout) {
  111. clearTimeout(this.scrollToTargetTimeout);
  112. this.scrollToTargetTimeout = null;
  113. }
  114. }
  115. onEntryLoaded() {
  116. this.hideLoading();
  117. if (this.scrollToTargetTimeout) {
  118. clearTimeout(this.scrollToTargetTimeout);
  119. this.scrollToTargetTimeout = null;
  120. }
  121. this.scrollToTarget();
  122. }
  123. beforeRoute(context) {
  124. this.cacheScrollPosition();
  125. this.routeCtx = context;
  126. this.scrollToTargetTimeout = this.delay(this.scrollToTarget);
  127. }
  128. cacheScrollPosition() {
  129. if (!this.routeCtx || this.routeCtx.hash) {
  130. return;
  131. }
  132. if (this.routeCtx.path === "/") {
  133. return;
  134. }
  135. if (this.scrollMap[this.routeCtx.state.id] == null) {
  136. this.scrollStack.push(this.routeCtx.state.id);
  137. while (this.scrollStack.length > app.config.history_cache_size) {
  138. delete this.scrollMap[this.scrollStack.shift()];
  139. }
  140. }
  141. this.scrollMap[this.routeCtx.state.id] = this.scrollEl.scrollTop;
  142. }
  143. afterRoute(route, context) {
  144. if (route !== "entry" && route !== "type") {
  145. resetFavicon();
  146. }
  147. switch (route) {
  148. case "root":
  149. this.show(this.rootPage);
  150. break;
  151. case "entry":
  152. this.show(this.entryPage);
  153. break;
  154. case "type":
  155. this.show(this.typePage);
  156. break;
  157. case "settings":
  158. this.show(this.settingsPage);
  159. break;
  160. case "offline":
  161. this.show(this.offlinePage);
  162. break;
  163. default:
  164. this.show(this.staticPage);
  165. }
  166. this.view.onRoute(context);
  167. app.document.setTitle(
  168. typeof this.view.getTitle === "function"
  169. ? this.view.getTitle()
  170. : undefined,
  171. );
  172. }
  173. onClick(event) {
  174. const link = $.closestLink($.eventTarget(event), this.el);
  175. if (link && this.isExternalUrl(link.getAttribute("href"))) {
  176. $.stopEvent(event);
  177. $.popup(link);
  178. }
  179. }
  180. onAltF(event) {
  181. if (
  182. !document.activeElement ||
  183. !$.hasChild(this.el, document.activeElement)
  184. ) {
  185. this.find("a:not(:empty)")?.focus();
  186. return $.stopEvent(event);
  187. }
  188. }
  189. findTargetByHash(hash) {
  190. let el = (() => {
  191. try {
  192. return $.id(decodeURIComponent(hash));
  193. } catch (error) {}
  194. })();
  195. if (!el) {
  196. el = (() => {
  197. try {
  198. return $.id(hash);
  199. } catch (error1) {}
  200. })();
  201. }
  202. return el;
  203. }
  204. isExternalUrl(url) {
  205. return url?.startsWith("http:") || url?.startsWith("https:");
  206. }
  207. };