insight.js 7.1 KB

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