entry_page.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. app.views.EntryPage = class EntryPage extends app.View {
  2. static className = "_page";
  3. static errorClass = "_page-error";
  4. static events = { click: "onClick" };
  5. static shortcuts = {
  6. altC: "onAltC",
  7. altO: "onAltO",
  8. };
  9. static routes = { before: "beforeRoute" };
  10. static LINKS = {
  11. home: "Homepage",
  12. code: "Source code",
  13. };
  14. init() {
  15. this.cacheMap = {};
  16. this.cacheStack = [];
  17. }
  18. deactivate() {
  19. if (super.deactivate(...arguments)) {
  20. this.empty();
  21. this.entry = null;
  22. }
  23. }
  24. loading() {
  25. this.empty();
  26. this.trigger("loading");
  27. }
  28. render(content, fromCache) {
  29. if (content == null) {
  30. content = "";
  31. }
  32. if (fromCache == null) {
  33. fromCache = false;
  34. }
  35. if (!this.activated) {
  36. return;
  37. }
  38. this.empty();
  39. this.subview = new (this.subViewClass())(this.el, this.entry);
  40. $.batchUpdate(this.el, () => {
  41. this.subview.render(content, fromCache);
  42. if (!fromCache) {
  43. this.addCopyButtons();
  44. }
  45. });
  46. if (app.disabledDocs.findBy("slug", this.entry.doc.slug)) {
  47. this.hiddenView = new app.views.HiddenPage(this.el, this.entry);
  48. }
  49. setFaviconForDoc(this.entry.doc);
  50. this.delay(this.polyfillMathML);
  51. this.trigger("loaded");
  52. }
  53. addCopyButtons() {
  54. if (!this.copyButton) {
  55. this.copyButton = document.createElement("button");
  56. this.copyButton.innerHTML = '<svg><use xlink:href="#icon-copy"/></svg>';
  57. this.copyButton.type = "button";
  58. this.copyButton.className = "_pre-clip";
  59. this.copyButton.title = "Copy to clipboard";
  60. this.copyButton.setAttribute("aria-label", "Copy to clipboard");
  61. }
  62. for (var el of this.findAllByTag("pre")) {
  63. el.appendChild(this.copyButton.cloneNode(true));
  64. }
  65. }
  66. polyfillMathML() {
  67. if (
  68. window.supportsMathML !== false ||
  69. !!this.polyfilledMathML ||
  70. !this.findByTag("math")
  71. ) {
  72. return;
  73. }
  74. this.polyfilledMathML = true;
  75. $.append(
  76. document.head,
  77. `<link rel="stylesheet" href="${app.config.mathml_stylesheet}">`,
  78. );
  79. }
  80. prepareContent(content) {
  81. if (!this.entry.isIndex() || !this.entry.doc.links) {
  82. return content;
  83. }
  84. const links = (() => {
  85. const result = [];
  86. for (var link in this.entry.doc.links) {
  87. var url = this.entry.doc.links[link];
  88. result.push(
  89. `<a href="${url}" class="_links-link">${EntryPage.LINKS[link]}</a>`,
  90. );
  91. }
  92. return result;
  93. })();
  94. return `<p class="_links">${links.join("")}</p>${content}`;
  95. }
  96. empty() {
  97. if (this.subview != null) {
  98. this.subview.deactivate();
  99. }
  100. this.subview = null;
  101. if (this.hiddenView != null) {
  102. this.hiddenView.deactivate();
  103. }
  104. this.hiddenView = null;
  105. this.resetClass();
  106. super.empty(...arguments);
  107. }
  108. subViewClass() {
  109. return (
  110. app.views[`${$.classify(this.entry.doc.type)}Page`] || app.views.BasePage
  111. );
  112. }
  113. getTitle() {
  114. return (
  115. this.entry.doc.fullName +
  116. (this.entry.isIndex() ? " documentation" : ` / ${this.entry.name}`)
  117. );
  118. }
  119. beforeRoute() {
  120. this.cache();
  121. this.abort();
  122. }
  123. onRoute(context) {
  124. const isSameFile = context.entry.filePath() === this.entry?.filePath?.();
  125. this.entry = context.entry;
  126. if (!isSameFile) {
  127. this.restore() || this.load();
  128. }
  129. }
  130. load() {
  131. this.loading();
  132. this.xhr = this.entry.loadFile(
  133. (response) => this.onSuccess(response),
  134. () => this.onError(),
  135. );
  136. }
  137. abort() {
  138. if (this.xhr) {
  139. this.xhr.abort();
  140. this.xhr = this.entry = null;
  141. }
  142. }
  143. onSuccess(response) {
  144. if (!this.activated) {
  145. return;
  146. }
  147. this.xhr = null;
  148. this.render(this.prepareContent(response));
  149. }
  150. onError() {
  151. this.xhr = null;
  152. this.render(this.tmpl("pageLoadError"));
  153. this.resetClass();
  154. this.addClass(this.constructor.errorClass);
  155. if (app.serviceWorker != null) {
  156. app.serviceWorker.update();
  157. }
  158. }
  159. cache() {
  160. let path;
  161. if (
  162. this.xhr ||
  163. !this.entry ||
  164. this.cacheMap[(path = this.entry.filePath())]
  165. ) {
  166. return;
  167. }
  168. this.cacheMap[path] = this.el.innerHTML;
  169. this.cacheStack.push(path);
  170. while (this.cacheStack.length > app.config.history_cache_size) {
  171. delete this.cacheMap[this.cacheStack.shift()];
  172. }
  173. }
  174. restore() {
  175. let path;
  176. if (this.cacheMap[(path = this.entry.filePath())]) {
  177. this.render(this.cacheMap[path], true);
  178. return true;
  179. }
  180. }
  181. onClick(event) {
  182. const target = $.eventTarget(event);
  183. if (target.hasAttribute("data-retry")) {
  184. $.stopEvent(event);
  185. this.load();
  186. } else if (target.classList.contains("_pre-clip")) {
  187. $.stopEvent(event);
  188. target.classList.add(
  189. $.copyToClipboard(target.parentNode.textContent)
  190. ? "_pre-clip-success"
  191. : "_pre-clip-error",
  192. );
  193. setTimeout(() => (target.className = "_pre-clip"), 2000);
  194. }
  195. }
  196. onAltC() {
  197. let link;
  198. if (!(link = this.find("._attribution:last-child ._attribution-link"))) {
  199. return;
  200. }
  201. console.log(link.href + location.hash);
  202. navigator.clipboard.writeText(link.href + location.hash);
  203. }
  204. onAltO() {
  205. let link;
  206. if (!(link = this.find("._attribution:last-child ._attribution-link"))) {
  207. return;
  208. }
  209. this.delay(() => $.popup(link.href + location.hash));
  210. }
  211. };