1/** 2 * Insight search plugin 3 * @author PPOffice { @link https://github.com/ppoffice } 4 */ 5(function ($, CONFIG) { 6 var $main = $('.ins-search'); 7 var $input = $main.find('.ins-search-input'); 8 var $wrapper = $main.find('.ins-section-wrapper'); 9 var $container = $main.find('.ins-section-container'); 10 $main.parent().remove('.ins-search'); 11 $('body').append($main); 12 13 function section (title) { 14 return $('<section>').addClass('ins-section') 15 .append($('<header>').addClass('ins-section-header').text(title)); 16 } 17 18 function searchItem (icon, title, slug, preview, url) { 19 return $('<div>').addClass('ins-selectable').addClass('ins-search-item') 20 .append($('<header>').append($('<i>').addClass('fa').addClass('fa-' + icon)).append(title != null && title != '' ? title : CONFIG.TRANSLATION['UNTITLED']) 21 .append(slug ? $('<span>').addClass('ins-slug').text(slug) : null)) 22 .append(preview ? $('<p>').addClass('ins-search-preview').text(preview) : null) 23 .attr('data-url', url); 24 } 25 26 function sectionFactory (type, array) { 27 var sectionTitle; 28 var $searchItems; 29 if (array.length === 0) return null; 30 sectionTitle = CONFIG.TRANSLATION[type]; 31 switch (type) { 32 case 'POSTS': 33 case 'PAGES': 34 $searchItems = array.map(function (item) { 35 // Use config.root instead of permalink to fix url issue 36 return searchItem('file', item.title, null, item.text.slice(0, 150), CONFIG.ROOT_URL + item.path); 37 }); 38 break; 39 case 'CATEGORIES': 40 case 'TAGS': 41 $searchItems = array.map(function (item) { 42 return searchItem(type === 'CATEGORIES' ? 'folder' : 'tag', item.name, item.slug, null, item.permalink); 43 }); 44 break; 45 default: 46 return null; 47 } 48 return section(sectionTitle).append($searchItems); 49 } 50 51 function extractToSet (json, key) { 52 var values = {}; 53 var entries = json.pages.concat(json.posts); 54 entries.forEach(function (entry) { 55 if (entry[key]) { 56 entry[key].forEach(function (value) { 57 values[value.name] = value; 58 }); 59 } 60 }); 61 var result = []; 62 for (var key in values) { 63 result.push(values[key]); 64 } 65 return result; 66 } 67 68 function parseKeywords (keywords) { 69 return keywords.split(' ').filter(function (keyword) { 70 return !!keyword; 71 }).map(function (keyword) { 72 return keyword.toUpperCase(); 73 }); 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 98 function filterFactory (keywords) { 99 return { 100 POST: function (obj) { 101 return filter(keywords, obj, ['title', 'text']); 102 }, 103 PAGE: function (obj) { 104 return filter(keywords, obj, ['title', 'text']); 105 }, 106 CATEGORY: function (obj) { 107 return filter(keywords, obj, ['name', 'slug']); 108 }, 109 TAG: function (obj) { 110 return filter(keywords, obj, ['name', 'slug']); 111 } 112 }; 113 } 114 115 /** 116 * Calculate the weight of a matched post/page/category/tag. 117 * @param Object obj Object to be weighted 118 * @param Array<String> fields Object's fields to find matches 119 * @param Array<Integer> weights Weight of every field 120 */ 121 function weight (keywords, obj, fields, weights) { 122 var value = 0;
123 parseKeywords(keywords).forEach(function (keyword) { 124 var pattern = new RegExp(keyword, 'img'); // Global, Multi-line, Case-insensitive 125 fields.forEach(function (field, index) { 126 if (obj.hasOwnProperty(field)) { 127 var matches = obj[field].match(pattern); 128 value += matches ? matches.length * weights[index] : 0; 129 } 130 }); 131 }); 132 return value; 133 } 134 135 function weightFactory (keywords) { 136 return { 137 POST: function (obj) { 138 return weight(keywords, obj, ['title', 'text'], [3, 1]); 139 }, 140 PAGE: function (obj) { 141 return weight(keywords, obj, ['title', 'text'], [3, 1]); 142 }, 143 CATEGORY: function (obj) { 144 return weight(keywords, obj, ['name', 'slug'], [1, 1]); 145 }, 146 TAG: function (obj) { 147 return weight(keywords, obj, ['name', 'slug'], [1, 1]); 148 } 149 }; 150 } 151 152 function search (json, keywords) { 153 var WEIGHTS = weightFactory(keywords); 154 var FILTERS = filterFactory(keywords); 155 var posts = json.posts; 156 // var pages = json.pages; 157 var tags = extractToSet(json, 'tags'); 158 var categories = extractToSet(json, 'categories'); 159 return { 160 posts: posts.filter(FILTERS.POST).sort(function (a, b) { return WEIGHTS.POST(b) - WEIGHTS.POST(a); }).slice(0, 20), 161 // pages: pages.filter(FILTERS.PAGE).sort(function (a, b) { return WEIGHTS.PAGE(b) - WEIGHTS.PAGE(a); }).slice(0, 5), 162 categories: categories.filter(FILTERS.CATEGORY).sort(function (a, b) { return WEIGHTS.CATEGORY(b) - WEIGHTS.CATEGORY(a); }).slice(0, 5), 163 tags: tags.filter(FILTERS.TAG).sort(function (a, b) { return WEIGHTS.TAG(b) - WEIGHTS.TAG(a); }).slice(0, 5) 164 }; 165 } 166 167 function searchResultToDOM (searchResult) { 168 $container.empty(); 169 for (var key in searchResult) { 170 $container.append(sectionFactory(key.toUpperCase(), searchResult[key])); 171 } 172 } 173 174 function scrollTo ($item) { 175 if ($item.length === 0) return; 176 var wrapperHeight = $wrapper[0].clientHeight; 177 var itemTop = $item.position().top - $wrapper.scrollTop(); 178 var itemBottom = $item[0].clientHeight + $item.position().top; 179 if (itemBottom > wrapperHeight + $wrapper.scrollTop()) { 180 $wrapper.scrollTop(itemBottom - $wrapper[0].clientHeight); 181 } 182 if (itemTop < 0) { 183 $wrapper.scrollTop($item.position().top); 184 } 185 } 186 187 function selectItemByDiff (value) { 188 var $items = $.makeArray($container.find('.ins-selectable')); 189 var prevPosition = -1; 190 $items.forEach(function (item, index) { 191 if ($(item).hasClass('active')) { 192 prevPosition = index; 193 return; 194 } 195 }); 196 var nextPosition = ($items.length + prevPosition + value) % $items.length; 197 $($items[prevPosition]).removeClass('active'); 198 $($items[nextPosition]).addClass('active'); 199 scrollTo($($items[nextPosition])); 200 } 201 202 function gotoLink ($item) { 203 if ($item && $item.length) { 204 location.href = $item.attr('data-url'); 205 } 206 } 207 208 $.getJSON(CONFIG.CONTENT_URL, function (json) { 209 if (location.hash.trim() === '#ins-search') { 210 $main.addClass('show'); 211 } 212 $input.on('input', function () { 213 var keywords = $(this).val(); 214 searchResultToDOM(search(json, keywords)); 215 }); 216 $input.trigger('input'); 217 }); 218 219 220 $(document).on('click focus', '.search-field', function () { 221 $main.addClass('show'); 222 $main.find('.ins-search-input').focus(); 223 }).on('click focus', '.search-form-submit', function () { 224 $main.addClass('show'); 225 $main.find('.ins-search-input').focus(); 226 }).on('click', '.ins-search-item', function () { 227 gotoLink($(this)); 228 }).on('click', '.ins-close', function () { 229 $main.removeClass('show'); 230 }).on('keydown', function (e) { 231 if (!$main.hasClass('show')) return; 232 switch (e.keyCode) { 233 case 27: // ESC 234 $main.removeClass('show'); break; 235 case 38: // UP 236 selectItemByDiff(-1); break; 237 case 40: // DOWN 238 selectItemByDiff(1); break; 239 case 13: //ENTER 240 gotoLink($container.find('.ins-selectable.active').eq(0)); break; 241 } 242 }); 243})(jQuery, window.INSIGHT_CONFIG);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.