entry_page.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  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 = Object.entries(this.entry.doc.links).map(([link, url]) => {
  85. return `<a href="${url}" class="_links-link">${EntryPage.LINKS[link]}</a>`;
  86. });
  87. return `<p class="_links">${links.join("")}</p>${content}`;
  88. }
  89. empty() {
  90. if (this.subview != null) {
  91. this.subview.deactivate();
  92. }
  93. this.subview = null;
  94. if (this.hiddenView != null) {
  95. this.hiddenView.deactivate();
  96. }
  97. this.hiddenView = null;
  98. this.resetClass();
  99. super.empty(...arguments);
  100. }
  101. subViewClass() {
  102. return (
  103. app.views[`${$.classify(this.entry.doc.type)}Page`] || app.views.BasePage
  104. );
  105. }
  106. getTitle() {
  107. return (
  108. this.entry.doc.fullName +
  109. (this.entry.isIndex() ? " documentation" : ` / ${this.entry.name}`)
  110. );
  111. }
  112. beforeRoute() {
  113. this.cache();
  114. this.abort();
  115. }
  116. onRoute(context) {
  117. const isSameFile = context.entry.filePath() === this.entry?.filePath?.();
  118. this.entry = context.entry;
  119. if (!isSameFile) {
  120. this.restore() || this.load();
  121. }
  122. }
  123. load() {
  124. this.loading();
  125. this.xhr = this.entry.loadFile(
  126. (response) => this.onSuccess(response),
  127. () => this.onError(),
  128. );
  129. }
  130. abort() {
  131. if (this.xhr) {
  132. this.xhr.abort();
  133. this.xhr = this.entry = null;
  134. }
  135. }
  136. onSuccess(response) {
  137. if (!this.activated) {
  138. return;
  139. }
  140. this.xhr = null;
  141. this.render(this.prepareContent(response));
  142. }
  143. onError() {
  144. this.xhr = null;
  145. this.render(this.tmpl("pageLoadError"));
  146. this.resetClass();
  147. this.addClass(this.constructor.errorClass);
  148. if (app.serviceWorker != null) {
  149. app.serviceWorker.update();
  150. }
  151. }
  152. cache() {
  153. let path;
  154. if (
  155. this.xhr ||
  156. !this.entry ||
  157. this.cacheMap[(path = this.entry.filePath())]
  158. ) {
  159. return;
  160. }
  161. this.cacheMap[path] = this.el.innerHTML;
  162. this.cacheStack.push(path);
  163. while (this.cacheStack.length > app.config.history_cache_size) {
  164. delete this.cacheMap[this.cacheStack.shift()];
  165. }
  166. }
  167. restore() {
  168. const path = this.entry.filePath();
  169. if (this.cacheMap[[path]]) {
  170. this.render(this.cacheMap[path], true);
  171. return true;
  172. }
  173. }
  174. onClick(event) {
  175. const target = $.eventTarget(event);
  176. if (target.hasAttribute("data-retry")) {
  177. $.stopEvent(event);
  178. this.load();
  179. } else if (target.classList.contains("_pre-clip")) {
  180. $.stopEvent(event);
  181. navigator.clipboard.writeText(target.parentNode.textContent).then(
  182. () => target.classList.add("_pre-clip-success"),
  183. () => target.classList.add("_pre-clip-error"),
  184. );
  185. setTimeout(() => (target.className = "_pre-clip"), 2000);
  186. }
  187. }
  188. onAltC() {
  189. const link = this.find("._attribution:last-child ._attribution-link");
  190. if (!link) {
  191. return;
  192. }
  193. console.log(link.href + location.hash);
  194. navigator.clipboard.writeText(link.href + location.hash);
  195. }
  196. onAltO() {
  197. const link = this.find("._attribution:last-child ._attribution-link");
  198. if (!link) {
  199. return;
  200. }
  201. this.delay(() => $.popup(link.href + location.hash));
  202. }
  203. };