diff --git a/languages/en.yml b/languages/en.yml index b13e744..1084d52 100644 --- a/languages/en.yml +++ b/languages/en.yml @@ -34,4 +34,5 @@ insight: pages: 'Pages' categories: 'Categories' tags: 'Tags' + untitled: '(Untitled)' diff --git a/languages/es.yml b/languages/es.yml index 9ef59dc..4e49c7e 100644 --- a/languages/es.yml +++ b/languages/es.yml @@ -34,3 +34,4 @@ insight: pages: 'Pages' categories: 'Categorias' tags: 'Etiquetas' + untitled: '(Untitled)' diff --git a/languages/fr.yml b/languages/fr.yml index e9411e0..28018e9 100644 --- a/languages/fr.yml +++ b/languages/fr.yml @@ -33,3 +33,4 @@ insight: pages: 'Pages' categories: 'Catégories' tags: 'Tags' + untitled: '(Untitled)' diff --git a/languages/id.yml b/languages/id.yml index d4814fb..b24d502 100644 --- a/languages/id.yml +++ b/languages/id.yml @@ -32,3 +32,4 @@ insight: pages: 'Pages' categories: 'kategori' tags: 'tag' + untitled: '(Untitled)' diff --git a/languages/ja.yml b/languages/ja.yml index 3152d1d..faf9ded 100644 --- a/languages/ja.yml +++ b/languages/ja.yml @@ -33,3 +33,4 @@ insight: pages: 'Pages' categories: 'カテゴリ' tags: 'タグ' + untitled: '(Untitled)' diff --git a/languages/ko.yml b/languages/ko.yml index 50535ec..88a5b05 100644 --- a/languages/ko.yml +++ b/languages/ko.yml @@ -34,3 +34,4 @@ insight: pages: 'Pages' categories: '카테고리' tags: '태그' + untitled: '(Untitled)' diff --git a/languages/ru.yml b/languages/ru.yml index 76ca98e..179c8bb 100644 --- a/languages/ru.yml +++ b/languages/ru.yml @@ -34,3 +34,4 @@ insight: pages: 'Pages' categories: 'категории' tags: 'тэги' + untitled: '(Untitled)' diff --git a/languages/zh-CN.yml b/languages/zh-CN.yml index 8823250..6bdc98e 100644 --- a/languages/zh-CN.yml +++ b/languages/zh-CN.yml @@ -34,3 +34,4 @@ insight: pages: '页面' categories: '分类' tags: '标签' + untitled: '(未命名)' diff --git a/languages/zh-TW.yml b/languages/zh-TW.yml index 13409e8..f5affd7 100644 --- a/languages/zh-TW.yml +++ b/languages/zh-TW.yml @@ -33,3 +33,4 @@ insight: pages: 'Pages' categories: '分類' tags: '標籤' + untitled: '(Untitled)' diff --git a/layout/search/insight.ejs b/layout/search/insight.ejs index 3876b5c..a35ef8b 100644 --- a/layout/search/insight.ejs +++ b/layout/search/insight.ejs @@ -11,192 +11,19 @@ \ No newline at end of file +(function (window) { + var INSIGHT_CONFIG = { + TRANSLATION: { + POSTS: '<%= __("insight.posts") %>', + PAGES: '<%= __("insight.pages") %>', + CATEGORIES: '<%= __("insight.categories") %>', + TAGS: '<%= __("insight.tags") %>', + UNTITLED: '<%= __("insight.untitled") %>', + }, + ROOT_URL: '<%= config.root %>', + CONTENT_URL: '<%- url_for("/content.json")%>', + }; + window.INSIGHT_CONFIG = INSIGHT_CONFIG; +})(window); + +<%- js('js/insight') %> \ No newline at end of file diff --git a/source/css/_partial/insight.styl b/source/css/_partial/insight.styl index c9879ec..39c3d1d 100644 --- a/source/css/_partial/insight.styl +++ b/source/css/_partial/insight.styl @@ -40,7 +40,9 @@ $ins-full-screen border: none outline: none font-size: 16px + box-shadow: none font-weight: 200 + border-radius: 0 background: white line-height: 20px box-sizing: border-box diff --git a/source/js/insight.js b/source/js/insight.js new file mode 100644 index 0000000..add29a4 --- /dev/null +++ b/source/js/insight.js @@ -0,0 +1,191 @@ +/** + * Insight search plugin + * @author PPOffice { @link https://github.com/ppoffice } + */ +(function ($, CONFIG) { + $main = $('.ins-search'); + $container = $('.ins-section-container'); + $main.parent().remove('.ins-search'); + $('body').append($main); + + $(document).on('click focus', '.search-form-input', function () { + $main.addClass('show'); + $main.find('.ins-search-input').focus(); + }).on('click', '.ins-search-item', function () { + location.href=$(this).attr('data-url'); + }).on('click', '.ins-close', function () { + $main.removeClass('show'); + }); + + 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(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), 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 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 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 = 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])); + } + } + + $.getJSON(CONFIG.CONTENT_URL, function (json) { + if (location.hash.trim() == '#ins-search') { + $main.addClass('show'); + } + $('.ins-search-input').on('input', function () { + var keywords = $(this).val(); + searchResultToDOM(search(json, keywords)); + }); + $('.ins-search-input').trigger('input'); + }); +})(jQuery, window.INSIGHT_CONFIG); \ No newline at end of file