search.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. // TODO: This file was created by bulk-decaffeinate.
  2. // Sanity-check the conversion and remove this comment.
  3. /*
  4. * decaffeinate suggestions:
  5. * DS102: Remove unnecessary code created because of implicit returns
  6. * DS103: Rewrite code to no longer use __guard__, or convert again using --optional-chaining
  7. * DS206: Consider reworking classes to avoid initClass
  8. * DS207: Consider shorter variations of null checks
  9. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
  10. */
  11. (function () {
  12. let SEARCH_PARAM = undefined;
  13. let HASH_RGX = undefined;
  14. app.views.Search = class Search extends app.View {
  15. static initClass() {
  16. SEARCH_PARAM = app.config.search_param;
  17. this.el = "._search";
  18. this.activeClass = "_search-active";
  19. this.elements = {
  20. input: "._search-input",
  21. resetLink: "._search-clear",
  22. };
  23. this.events = {
  24. input: "onInput",
  25. click: "onClick",
  26. submit: "onSubmit",
  27. };
  28. this.shortcuts = {
  29. typing: "focus",
  30. altG: "google",
  31. altS: "stackoverflow",
  32. altD: "duckduckgo",
  33. };
  34. this.routes = { after: "afterRoute" };
  35. HASH_RGX = new RegExp(`^#${SEARCH_PARAM}=(.*)`);
  36. }
  37. init() {
  38. this.addSubview((this.scope = new app.views.SearchScope(this.el)));
  39. this.searcher = new app.Searcher();
  40. this.searcher
  41. .on("results", (results) => this.onResults(results))
  42. .on("end", () => this.onEnd());
  43. this.scope.on("change", () => this.onScopeChange());
  44. app.on("ready", () => this.onReady());
  45. $.on(window, "hashchange", () => this.searchUrl());
  46. $.on(window, "focus", (event) => this.onWindowFocus(event));
  47. }
  48. focus() {
  49. if (document.activeElement === this.input) {
  50. return;
  51. }
  52. if (app.settings.get("noAutofocus")) {
  53. return;
  54. }
  55. this.input.focus();
  56. }
  57. autoFocus() {
  58. if (app.isMobile() || $.isAndroid() || $.isIOS()) {
  59. return;
  60. }
  61. if (
  62. (document.activeElement != null
  63. ? document.activeElement.tagName
  64. : undefined) === "INPUT"
  65. ) {
  66. return;
  67. }
  68. if (app.settings.get("noAutofocus")) {
  69. return;
  70. }
  71. this.input.focus();
  72. }
  73. onWindowFocus(event) {
  74. if (event.target === window) {
  75. return this.autoFocus();
  76. }
  77. }
  78. getScopeDoc() {
  79. if (this.scope.isActive()) {
  80. return this.scope.getScope();
  81. }
  82. }
  83. reset(force) {
  84. if (force || !this.input.value) {
  85. this.scope.reset();
  86. }
  87. this.el.reset();
  88. this.onInput();
  89. this.autoFocus();
  90. }
  91. onReady() {
  92. this.value = "";
  93. this.delay(this.onInput);
  94. }
  95. onInput() {
  96. if (
  97. this.value == null || // ignore events pre-"ready"
  98. this.value === this.input.value
  99. ) {
  100. return;
  101. }
  102. this.value = this.input.value;
  103. if (this.value.length) {
  104. this.search();
  105. } else {
  106. this.clear();
  107. }
  108. }
  109. search(url) {
  110. if (url == null) {
  111. url = false;
  112. }
  113. this.addClass(this.constructor.activeClass);
  114. this.trigger("searching");
  115. this.hasResults = null;
  116. this.flags = { urlSearch: url, initialResults: true };
  117. this.searcher.find(
  118. this.scope.getScope().entries.all(),
  119. "text",
  120. this.value,
  121. );
  122. }
  123. searchUrl() {
  124. let value;
  125. if (location.pathname === "/") {
  126. this.scope.searchUrl();
  127. } else if (!app.router.isIndex()) {
  128. return;
  129. }
  130. if (!(value = this.extractHashValue())) {
  131. return;
  132. }
  133. this.input.value = this.value = value;
  134. this.input.setSelectionRange(value.length, value.length);
  135. this.search(true);
  136. return true;
  137. }
  138. clear() {
  139. this.removeClass(this.constructor.activeClass);
  140. this.trigger("clear");
  141. }
  142. externalSearch(url) {
  143. let value;
  144. if ((value = this.value)) {
  145. if (this.scope.name()) {
  146. value = `${this.scope.name()} ${value}`;
  147. }
  148. $.popup(`${url}${encodeURIComponent(value)}`);
  149. this.reset();
  150. }
  151. }
  152. google() {
  153. this.externalSearch("https://www.google.com/search?q=");
  154. }
  155. stackoverflow() {
  156. this.externalSearch("https://stackoverflow.com/search?q=");
  157. }
  158. duckduckgo() {
  159. this.externalSearch("https://duckduckgo.com/?t=devdocs&q=");
  160. }
  161. onResults(results) {
  162. if (results.length) {
  163. this.hasResults = true;
  164. }
  165. this.trigger("results", results, this.flags);
  166. this.flags.initialResults = false;
  167. }
  168. onEnd() {
  169. if (!this.hasResults) {
  170. this.trigger("noresults");
  171. }
  172. }
  173. onClick(event) {
  174. if (event.target === this.resetLink) {
  175. $.stopEvent(event);
  176. this.reset();
  177. }
  178. }
  179. onSubmit(event) {
  180. $.stopEvent(event);
  181. }
  182. onScopeChange() {
  183. this.value = "";
  184. this.onInput();
  185. }
  186. afterRoute(name, context) {
  187. if (
  188. (app.shortcuts.eventInProgress != null
  189. ? app.shortcuts.eventInProgress.name
  190. : undefined) === "escape"
  191. ) {
  192. return;
  193. }
  194. if (!context.init && app.router.isIndex()) {
  195. this.reset(true);
  196. }
  197. if (context.hash) {
  198. this.delay(this.searchUrl);
  199. }
  200. $.requestAnimationFrame(() => this.autoFocus());
  201. }
  202. extractHashValue() {
  203. let value;
  204. if ((value = this.getHashValue()) != null) {
  205. app.router.replaceHash();
  206. return value;
  207. }
  208. }
  209. getHashValue() {
  210. try {
  211. return __guard__(
  212. HASH_RGX.exec($.urlDecode(location.hash)),
  213. (x) => x[1],
  214. );
  215. } catch (error) {}
  216. }
  217. };
  218. app.views.Search.initClass();
  219. return app.views.Search;
  220. })();
  221. function __guard__(value, transform) {
  222. return typeof value !== "undefined" && value !== null
  223. ? transform(value)
  224. : undefined;
  225. }