lunar-search.js 5.6 KB

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