insight.ejs 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. <div class="ins-search">
  2. <div class="ins-search-mask"></div>
  3. <div class="ins-search-container">
  4. <div class="ins-input-wrapper">
  5. <input type="text" class="ins-search-input" placeholder="<%= __('insight.hint') %>" />
  6. <span class="ins-close ins-selectable"><i class="fa fa-times-circle"></i></span>
  7. </div>
  8. <div class="ins-section-wrapper">
  9. <div class="ins-section-container"></div>
  10. </div>
  11. </div>
  12. </div>
  13. <script>
  14. (function ($) {
  15. $main = $('.ins-search');
  16. $container = $('.ins-section-container');
  17. $main.parent().remove('.ins-search');
  18. $('body').append($main);
  19. $(document).on('click focus', '.search-form-input', function () {
  20. $main.addClass('show');
  21. $main.find('.ins-search-input').focus();
  22. }).on('click', '.ins-search-item', function () {
  23. location.href=$(this).attr('data-url');
  24. }).on('click', '.ins-close', function () {
  25. $main.removeClass('show');
  26. });
  27. function section (title) {
  28. return $('<section>').addClass('ins-section')
  29. .append($('<header>').addClass('ins-section-header').text(title));
  30. }
  31. function searchItem (icon, title, slug, preview, url) {
  32. return $('<div>').addClass('ins-selectable').addClass('ins-search-item')
  33. .append($('<header>').append($('<i>').addClass('fa').addClass('fa-' + icon)).append(title)
  34. .append(slug ? $('<span>').addClass('ins-slug').text(slug) : null))
  35. .append(preview ? $('<p>').addClass('ins-search-preview').text(preview) : null)
  36. .attr('data-url', url);
  37. }
  38. function sectionFactory (type, array) {
  39. var sectionTitle;
  40. var $searchItems;
  41. if (array.length == 0) return null;
  42. switch (type) {
  43. case 'POSTS':
  44. case 'PAGES':
  45. sectionTitle = type == 'POSTS' ? '<%= __("insight.posts") %>' : '<%= __("insight.pages") %>';
  46. $searchItems = array.map(function (item) {
  47. // Use config.root instead of permalink to fix url issue
  48. return searchItem('file', item.title, null, item.text.slice(0, 150), '<%= config.root %>' + item.path);
  49. });
  50. break;
  51. case 'CATEGORIES':
  52. case 'TAGS':
  53. sectionTitle = type == 'CATEGORIES' ? '<%= __("insight.categories") %>' : '<%= __("insight.tags") %>';
  54. $searchItems = array.map(function (item) {
  55. return searchItem(type == 'CATEGORIES' ? 'folder' : 'tag', item.name, item.slug, null, item.permalink);
  56. });
  57. break;
  58. default:
  59. return null;
  60. }
  61. return section(sectionTitle).append($searchItems);
  62. }
  63. function extractToSet (json, key) {
  64. var values = {};
  65. var entries = json.pages.concat(json.posts);
  66. entries.forEach(function (entry) {
  67. if (entry[key]) {
  68. entry[key].forEach(function (value) {
  69. values[value.name] = value;
  70. });
  71. }
  72. });
  73. var result = [];
  74. for (var key in values) {
  75. result.push(values[key]);
  76. }
  77. return result;
  78. }
  79. function parseKeywords (keywords) {
  80. return keywords.split(' ').filter(function (keyword) {
  81. return !!keyword;
  82. }).map(function (keyword) {
  83. return keyword.toUpperCase();
  84. });
  85. }
  86. /**
  87. * Judge if a given post/page/category/tag contains all of the keywords.
  88. * @param Object obj Object to be weighted
  89. * @param Array<String> fields Object's fields to find matches
  90. */
  91. function filter (keywords, obj, fields) {
  92. var result = false;
  93. var keywordArray = parseKeywords(keywords);
  94. var containKeywords = keywordArray.filter(function (keyword) {
  95. var containFields = fields.filter(function (field) {
  96. if (!obj.hasOwnProperty(field))
  97. return false;
  98. if (obj[field].toUpperCase().indexOf(keyword) > -1)
  99. return true;
  100. });
  101. if (containFields.length > 0)
  102. return true;
  103. return false;
  104. });
  105. return containKeywords.length == keywordArray.length;
  106. }
  107. function filterFactory (keywords) {
  108. return {
  109. POST: function (obj) {
  110. return filter(keywords, obj, ['title', 'text']);
  111. },
  112. PAGE: function (obj) {
  113. return filter(keywords, obj, ['title', 'text']);
  114. },
  115. CATEGORY: function (obj) {
  116. return filter(keywords, obj, ['name', 'slug']);
  117. },
  118. TAG: function (obj) {
  119. return filter(keywords, obj, ['name', 'slug']);
  120. }
  121. };
  122. }
  123. /**
  124. * Calculate the weight of a matched post/page/category/tag.
  125. * @param Object obj Object to be weighted
  126. * @param Array<String> fields Object's fields to find matches
  127. * @param Array<Integer> weights Weight of every field
  128. */
  129. function weight (keywords, obj, fields, weights) {
  130. var value = 0;
  131. parseKeywords(keywords).forEach(function (keyword) {
  132. var pattern = new RegExp(keyword, 'img'); // Global, Multi-line, Case-insensitive
  133. fields.forEach(function (field, index) {
  134. if (obj.hasOwnProperty(field)) {
  135. var matches = obj[field].match(pattern);
  136. value += matches ? matches.length * weights[index] : 0;
  137. }
  138. });
  139. });
  140. return value;
  141. }
  142. function weightFactory (keywords) {
  143. return {
  144. POST: function (obj) {
  145. return weight(keywords, obj, ['title', 'text'], [3, 1]);
  146. },
  147. PAGE: function (obj) {
  148. return weight(keywords, obj, ['title', 'text'], [3, 1]);
  149. },
  150. CATEGORY: function (obj) {
  151. return weight(keywords, obj, ['name', 'slug'], [1, 1]);
  152. },
  153. TAG: function (obj) {
  154. return weight(keywords, obj, ['name', 'slug'], [1, 1]);
  155. }
  156. };
  157. }
  158. function search (json, keywords) {
  159. var WEIGHTS = weightFactory(keywords);
  160. var FILTERS = filterFactory(keywords);
  161. var posts = json.posts;
  162. var pages = json.pages;
  163. var tags = extractToSet(json, 'tags');
  164. var categories = extractToSet(json, 'categories');
  165. return {
  166. posts: posts.filter(FILTERS.POST).sort(function (a, b) { return WEIGHTS.POST(b) - WEIGHTS.POST(a); }).slice(0, 5),
  167. pages: pages.filter(FILTERS.PAGE).sort(function (a, b) { return WEIGHTS.PAGE(b) - WEIGHTS.PAGE(a); }).slice(0, 5),
  168. categories: categories.filter(FILTERS.CATEGORY).sort(function (a, b) { return WEIGHTS.CATEGORY(b) - WEIGHTS.CATEGORY(a); }).slice(0, 5),
  169. tags: tags.filter(FILTERS.TAG).sort(function (a, b) { return WEIGHTS.TAG(b) - WEIGHTS.TAG(a); }).slice(0, 5)
  170. };
  171. }
  172. function searchResultToDOM (searchResult) {
  173. $container.empty();
  174. for (var key in searchResult) {
  175. $container.append(sectionFactory(key.toUpperCase(), searchResult[key]));
  176. }
  177. }
  178. $.getJSON('<%- url_for("/content.json")%>', function (json) {
  179. if (location.hash.trim() == '#ins-search') {
  180. $main.addClass('show');
  181. }
  182. $('.ins-search-input').on('input', function () {
  183. var keywords = $(this).val();
  184. searchResultToDOM(search(json, keywords));
  185. });
  186. $('.ins-search-input').trigger('input');
  187. });
  188. })(jQuery);
  189. </script>