lunar-search.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. import lunr from "@generated/lunr.client";
  2. lunr.tokenizer.separator = /[\s\-/]+/;
  3. class LunrSearchAdapter {
  4. constructor(searchDocs, searchIndex) {
  5. this.searchDocs = searchDocs;
  6. this.lunrIndex = lunr.Index.load(searchIndex);
  7. }
  8. getLunrResult(input) {
  9. return this.lunrIndex.query(function (query) {
  10. const tokens = lunr.tokenizer(input);
  11. query.term(tokens, {
  12. boost: 10
  13. });
  14. query.term(tokens, {
  15. wildcard: lunr.Query.wildcard.TRAILING
  16. });
  17. });
  18. }
  19. getHit(doc, formattedTitle, formattedContent) {
  20. return {
  21. hierarchy: {
  22. lvl0: doc.pageTitle || doc.title,
  23. lvl1: doc.type === 0 ? null : doc.title
  24. },
  25. url: doc.url,
  26. _snippetResult: formattedContent ? {
  27. content: {
  28. value: formattedContent,
  29. matchLevel: "full"
  30. }
  31. } : null,
  32. _highlightResult: {
  33. hierarchy: {
  34. lvl0: {
  35. value: doc.type === 0 ? formattedTitle || doc.title : doc.pageTitle,
  36. },
  37. lvl1:
  38. doc.type === 0
  39. ? null
  40. : {
  41. value: formattedTitle || doc.title
  42. }
  43. }
  44. }
  45. };
  46. }
  47. getTitleHit(doc, position, length) {
  48. const start = position[0];
  49. const end = position[0] + length;
  50. let formattedTitle = doc.title.substring(0, start) + '<span class="algolia-docsearch-suggestion--highlight">' + doc.title.substring(start, end) + '</span>' + doc.title.substring(end, doc.title.length);
  51. return this.getHit(doc, formattedTitle)
  52. }
  53. getKeywordHit(doc, position, length) {
  54. const start = position[0];
  55. const end = position[0] + length;
  56. let formattedTitle = doc.title + '<br /><i>Keywords: ' + doc.keywords.substring(0, start) + '<span class="algolia-docsearch-suggestion--highlight">' + doc.keywords.substring(start, end) + '</span>' + doc.keywords.substring(end, doc.keywords.length) + '</i>'
  57. return this.getHit(doc, formattedTitle)
  58. }
  59. getContentHit(doc, position) {
  60. const start = position[0];
  61. const end = position[0] + position[1];
  62. let previewStart = start;
  63. let previewEnd = end;
  64. let ellipsesBefore = true;
  65. let ellipsesAfter = true;
  66. for (let k = 0; k < 3; k++) {
  67. const nextSpace = doc.content.lastIndexOf(' ', previewStart - 2);
  68. const nextDot = doc.content.lastIndexOf('.', previewStart - 2);
  69. if ((nextDot > 0) && (nextDot > nextSpace)) {
  70. previewStart = nextDot + 1;
  71. ellipsesBefore = false;
  72. break;
  73. }
  74. if (nextSpace < 0) {
  75. previewStart = 0;
  76. ellipsesBefore = false;
  77. break;
  78. }
  79. previewStart = nextSpace + 1;
  80. }
  81. for (let k = 0; k < 10; k++) {
  82. const nextSpace = doc.content.indexOf(' ', previewEnd + 1);
  83. const nextDot = doc.content.indexOf('.', previewEnd + 1);
  84. if ((nextDot > 0) && (nextDot < nextSpace)) {
  85. previewEnd = nextDot;
  86. ellipsesAfter = false;
  87. break;
  88. }
  89. if (nextSpace < 0) {
  90. previewEnd = doc.content.length;
  91. ellipsesAfter = false;
  92. break;
  93. }
  94. previewEnd = nextSpace;
  95. }
  96. let preview = doc.content.substring(previewStart, start);
  97. if (ellipsesBefore) {
  98. preview = '... ' + preview;
  99. }
  100. preview += '<span class="algolia-docsearch-suggestion--highlight">' + doc.content.substring(start, end) + '</span>';
  101. preview += doc.content.substring(end, previewEnd);
  102. if (ellipsesAfter) {
  103. preview += ' ...';
  104. }
  105. return this.getHit(doc, null, preview);
  106. }
  107. search(input) {
  108. return new Promise((resolve, rej) => {
  109. const results = this.getLunrResult(input);
  110. const hits = [];
  111. results.length > 5 && (results.length = 5);
  112. this.titleHitsRes = []
  113. this.contentHitsRes = []
  114. results.forEach(result => {
  115. const doc = this.searchDocs[result.ref];
  116. const { metadata } = result.matchData;
  117. for (let i in metadata) {
  118. if (metadata[i].title) {
  119. if (!this.titleHitsRes.includes(result.ref)) {
  120. const position = metadata[i].title.position[0]
  121. hits.push(this.getTitleHit(doc, position, input.length));
  122. this.titleHitsRes.push(result.ref);
  123. }
  124. } else if (metadata[i].content) {
  125. const position = metadata[i].content.position[0]
  126. hits.push(this.getContentHit(doc, position))
  127. } else if (metadata[i].keywords) {
  128. const position = metadata[i].keywords.position[0]
  129. hits.push(this.getKeywordHit(doc, position, input.length));
  130. this.titleHitsRes.push(result.ref);
  131. }
  132. }
  133. });
  134. hits.length > 5 && (hits.length = 5);
  135. resolve(hits);
  136. });
  137. }
  138. }
  139. export default LunrSearchAdapter;