search_scope.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  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.SearchScope = class SearchScope extends app.View {
  15. static initClass() {
  16. SEARCH_PARAM = app.config.search_param;
  17. this.elements = {
  18. input: "._search-input",
  19. tag: "._search-tag",
  20. };
  21. this.events = {
  22. click: "onClick",
  23. keydown: "onKeydown",
  24. textInput: "onTextInput",
  25. };
  26. this.routes = { after: "afterRoute" };
  27. HASH_RGX = new RegExp(`^#${SEARCH_PARAM}=(.+?) .`);
  28. }
  29. init() {
  30. this.placeholder = this.input.getAttribute("placeholder");
  31. this.searcher = new app.SynchronousSearcher({
  32. fuzzy_min_length: 2,
  33. max_results: 1,
  34. });
  35. this.searcher.on("results", (results) => this.onResults(results));
  36. }
  37. getScope() {
  38. return this.doc || app;
  39. }
  40. isActive() {
  41. return !!this.doc;
  42. }
  43. name() {
  44. return this.doc != null ? this.doc.name : undefined;
  45. }
  46. search(value, searchDisabled) {
  47. if (searchDisabled == null) {
  48. searchDisabled = false;
  49. }
  50. if (this.doc) {
  51. return;
  52. }
  53. this.searcher.find(app.docs.all(), "text", value);
  54. if (!this.doc && searchDisabled) {
  55. this.searcher.find(app.disabledDocs.all(), "text", value);
  56. }
  57. }
  58. searchUrl() {
  59. let value;
  60. if ((value = this.extractHashValue())) {
  61. this.search(value, true);
  62. }
  63. }
  64. onResults(results) {
  65. let doc;
  66. if (!(doc = results[0])) {
  67. return;
  68. }
  69. if (app.docs.contains(doc)) {
  70. this.selectDoc(doc);
  71. } else {
  72. this.redirectToDoc(doc);
  73. }
  74. }
  75. selectDoc(doc) {
  76. const previousDoc = this.doc;
  77. if (doc === previousDoc) {
  78. return;
  79. }
  80. this.doc = doc;
  81. this.tag.textContent = doc.fullName;
  82. this.tag.style.display = "block";
  83. this.input.removeAttribute("placeholder");
  84. this.input.value = this.input.value.slice(this.input.selectionStart);
  85. this.input.style.paddingLeft = this.tag.offsetWidth + 10 + "px";
  86. $.trigger(this.input, "input");
  87. this.trigger("change", this.doc, previousDoc);
  88. }
  89. redirectToDoc(doc) {
  90. const { hash } = location;
  91. app.router.replaceHash("");
  92. location.assign(doc.fullPath() + hash);
  93. }
  94. reset() {
  95. if (!this.doc) {
  96. return;
  97. }
  98. const previousDoc = this.doc;
  99. this.doc = null;
  100. this.tag.textContent = "";
  101. this.tag.style.display = "none";
  102. this.input.setAttribute("placeholder", this.placeholder);
  103. this.input.style.paddingLeft = "";
  104. this.trigger("change", null, previousDoc);
  105. }
  106. doScopeSearch(event) {
  107. this.search(this.input.value.slice(0, this.input.selectionStart));
  108. if (this.doc) {
  109. $.stopEvent(event);
  110. }
  111. }
  112. onClick(event) {
  113. if (event.target === this.tag) {
  114. this.reset();
  115. $.stopEvent(event);
  116. }
  117. }
  118. onKeydown(event) {
  119. if (event.which === 8) {
  120. // backspace
  121. if (this.doc && this.input.selectionEnd === 0) {
  122. this.reset();
  123. $.stopEvent(event);
  124. }
  125. } else if (!this.doc && this.input.value && !$.isChromeForAndroid()) {
  126. if (event.ctrlKey || event.metaKey || event.altKey || event.shiftKey) {
  127. return;
  128. }
  129. if (
  130. event.which === 9 || // tab
  131. (event.which === 32 && app.isMobile())
  132. ) {
  133. // space
  134. this.doScopeSearch(event);
  135. }
  136. }
  137. }
  138. onTextInput(event) {
  139. if (!$.isChromeForAndroid()) {
  140. return;
  141. }
  142. if (!this.doc && this.input.value && event.data === " ") {
  143. this.doScopeSearch(event);
  144. }
  145. }
  146. extractHashValue() {
  147. let value;
  148. if ((value = this.getHashValue())) {
  149. const newHash = $.urlDecode(location.hash).replace(
  150. `#${SEARCH_PARAM}=${value} `,
  151. `#${SEARCH_PARAM}=`,
  152. );
  153. app.router.replaceHash(newHash);
  154. return value;
  155. }
  156. }
  157. getHashValue() {
  158. try {
  159. return __guard__(
  160. HASH_RGX.exec($.urlDecode(location.hash)),
  161. (x) => x[1],
  162. );
  163. } catch (error) {}
  164. }
  165. afterRoute(name, context) {
  166. if (!app.isSingleDoc() && context.init && context.doc) {
  167. this.selectDoc(context.doc);
  168. }
  169. }
  170. };
  171. app.views.SearchScope.initClass();
  172. return app.views.SearchScope;
  173. })();
  174. function __guard__(value, transform) {
  175. return typeof value !== "undefined" && value !== null
  176. ? transform(value)
  177. : undefined;
  178. }