/** * Insight search plugin * @author PPOffice { @link https://github.com/ppoffice } */ (function ($, CONFIG) { var $main = $('.ins-search'); var $input = $main.find('.ins-search-input'); var $wrapper = $main.find('.ins-section-wrapper'); var $container = $main.find('.ins-section-container'); $main.parent().remove('.ins-search'); $('body').append($main); function section (title) { return $('
').addClass('ins-section') .append($('
').addClass('ins-section-header').text(title)); } function searchItem (icon, title, slug, preview, url) { return $('
').addClass('ins-selectable').addClass('ins-search-item') .append($('
').append($('').addClass('fa').addClass('fa-' + icon)) .append($('').addClass('ins-title').text(title != null && title !== '' ? title : CONFIG.TRANSLATION['UNTITLED'])) .append(slug ? $('').addClass('ins-slug').text(slug) : null)) .append(preview ? $('

').addClass('ins-search-preview').text(preview) : null) .attr('data-url', url); } function sectionFactory (type, array) { var sectionTitle; var $searchItems; if (array.length === 0) return null; sectionTitle = CONFIG.TRANSLATION[type]; switch (type) { case 'POSTS': case 'PAGES': $searchItems = array.map(function (item) { // Use config.root instead of permalink to fix url issue return searchItem('file', item.title, null, item.text.slice(0, 150), item.link); }); break; case 'CATEGORIES': case 'TAGS': $searchItems = array.map(function (item) { return searchItem(type === 'CATEGORIES' ? 'folder' : 'tag', item.name, item.slug, null, item.link); }); break; default: return null; } return section(sectionTitle).append($searchItems); } function parseKeywords (keywords) { return keywords.split(' ').filter(function (keyword) { return !!keyword; }).map(function (keyword) { return keyword.toUpperCase(); }); } /** * Judge if a given post/page/category/tag contains all of the keywords. * @param Object obj Object to be weighted * @param Array fields Object's fields to find matches */ function filter (keywords, obj, fields) { var keywordArray = parseKeywords(keywords); var containKeywords = keywordArray.filter(function (keyword) { var containFields = fields.filter(function (field) { if (!obj.hasOwnProperty(field)) return false; if (obj[field].toUpperCase().indexOf(keyword) > -1) return true; }); if (containFields.length > 0) return true; return false; }); return containKeywords.length === keywordArray.length; } function filterFactory (keywords) { return { POST: function (obj) { return filter(keywords, obj, ['title', 'text']); }, PAGE: function (obj) { return filter(keywords, obj, ['title', 'text']); }, CATEGORY: function (obj) { return filter(keywords, obj, ['name', 'slug']); }, TAG: function (obj) { return filter(keywords, obj, ['name', 'slug']); } }; } /** * Calculate the weight of a matched post/page/category/tag. * @param Object obj Object to be weighted * @param Array fields Object's fields to find matches * @param Array weights Weight of every field */ function weight (keywords, obj, fields, weights) { var value = 0; parseKeywords(keywords).forEach(function (keyword) { var pattern = new RegExp(keyword, 'img'); // Global, Multi-line, Case-insensitive fields.forEach(function (field, index) { if (obj.hasOwnProperty(field)) { var matches = obj[field].match(pattern); value += matches ? matches.length * weights[index] : 0; } }); }); return value; } function weightFactory (keywords) { return { POST: function (obj) { return weight(keywords, obj, ['title', 'text'], [3, 1]); }, PAGE: function (obj) { return weight(keywords, obj, ['title', 'text'], [3, 1]); }, CATEGORY: function (obj) { return weight(keywords, obj, ['name', 'slug'], [1, 1]); }, TAG: function (obj) { return weight(keywords, obj, ['name', 'slug'], [1, 1]); } }; } function search (json, keywords) { var WEIGHTS = weightFactory(keywords); var FILTERS = filterFactory(keywords); var posts = json.posts; var pages = json.pages; var tags = json.tags; var categories = json.categories; return { posts: posts.filter(FILTERS.POST).sort(function (a, b) { return WEIGHTS.POST(b) - WEIGHTS.POST(a); }).slice(0, 5), pages: pages.filter(FILTERS.PAGE).sort(function (a, b) { return WEIGHTS.PAGE(b) - WEIGHTS.PAGE(a); }).slice(0, 5), categories: categories.filter(FILTERS.CATEGORY).sort(function (a, b) { return WEIGHTS.CATEGORY(b) - WEIGHTS.CATEGORY(a); }).slice(0, 5), tags: tags.filter(FILTERS.TAG).sort(function (a, b) { return WEIGHTS.TAG(b) - WEIGHTS.TAG(a); }).slice(0, 5) }; } function searchResultToDOM (searchResult) { $container.empty(); for (var key in searchResult) { $container.append(sectionFactory(key.toUpperCase(), searchResult[key])); } } function scrollTo ($item) { if ($item.length === 0) return; var wrapperHeight = $wrapper[0].clientHeight; var itemTop = $item.position().top - $wrapper.scrollTop(); var itemBottom = $item[0].clientHeight + $item.position().top; if (itemBottom > wrapperHeight + $wrapper.scrollTop()) { $wrapper.scrollTop(itemBottom - $wrapper[0].clientHeight); } if (itemTop < 0) { $wrapper.scrollTop($item.position().top); } } function selectItemByDiff (value) { var $items = $.makeArray($container.find('.ins-selectable')); var prevPosition = -1; $items.forEach(function (item, index) { if ($(item).hasClass('active')) { prevPosition = index; return; } }); var nextPosition = ($items.length + prevPosition + value) % $items.length; $($items[prevPosition]).removeClass('active'); $($items[nextPosition]).addClass('active'); scrollTo($($items[nextPosition])); } function gotoLink ($item) { if ($item && $item.length) { location.href = $item.attr('data-url'); } } $.getJSON(CONFIG.CONTENT_URL, function (json) { if (location.hash.trim() === '#ins-search') { $main.addClass('show'); } $input.on('input', function () { var keywords = $(this).val(); searchResultToDOM(search(json, keywords)); }); $input.trigger('input'); }); var touch = false; $(document).on('click focus', '.navbar-main .search', function () { $main.addClass('show'); $main.find('.ins-search-input').focus(); }).on('click touchend', '.ins-search-item', function (e) { if (e.type !== 'click' && !touch) { return; } gotoLink($(this)); touch = false; }).on('click touchend', '.ins-close', function (e) { if (e.type !== 'click' && !touch) { return; } $main.removeClass('show'); touch = false; }).on('keydown', function (e) { if (!$main.hasClass('show')) return; switch (e.keyCode) { case 27: // ESC $main.removeClass('show'); break; case 38: // UP selectItemByDiff(-1); break; case 40: // DOWN selectItemByDiff(1); break; case 13: //ENTER gotoLink($container.find('.ins-selectable.active').eq(0)); break; } }).on('touchstart', function (e) { touch = true; }).on('touchmove', function (e) { touch = false; }); })(jQuery, window.INSIGHT_CONFIG);