123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240 |
- /**
- * 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 $('<section>').addClass('ins-section')
- .append($('<header>').addClass('ins-section-header').text(title));
- }
- function searchItem (icon, title, slug, preview, url) {
- return $('<div>').addClass('ins-selectable').addClass('ins-search-item')
- .append($('<header>').append($('<i>').addClass('fa').addClass('fa-' + icon)).append(title != null && title != '' ? title : CONFIG.TRANSLATION['UNTITLED'])
- .append(slug ? $('<span>').addClass('ins-slug').text(slug) : null))
- .append(preview ? $('<p>').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), CONFIG.ROOT_URL + item.path);
- });
- break;
- case 'CATEGORIES':
- case 'TAGS':
- $searchItems = array.map(function (item) {
- return searchItem(type === 'CATEGORIES' ? 'folder' : 'tag', item.name, item.slug, null, item.permalink);
- });
- break;
- default:
- return null;
- }
- return section(sectionTitle).append($searchItems);
- }
- function extractToSet (json, key) {
- var values = {};
- var entries = json.pages.concat(json.posts);
- entries.forEach(function (entry) {
- if (entry[key]) {
- entry[key].forEach(function (value) {
- values[value.name] = value;
- });
- }
- });
- var result = [];
- for (var key in values) {
- result.push(values[key]);
- }
- return result;
- }
- 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<String> fields Object's fields to find matches
- */
- function filter (keywords, obj, fields) {
- var result = false;
- 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<String> fields Object's fields to find matches
- * @param Array<Integer> 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 = extractToSet(json, 'tags');
- var categories = extractToSet(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');
- });
- $(document).on('click focus', '.search-form-input', function () {
- $main.addClass('show');
- $main.find('.ins-search-input').focus();
- }).on('click', '.ins-search-item', function () {
- gotoLink($(this));
- }).on('click', '.ins-close', function () {
- $main.removeClass('show');
- }).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;
- }
- });
- })(jQuery, window.INSIGHT_CONFIG);
|