diff --git a/_config.yml b/_config.yml
index 28eb7823a..bcea6d489 100755
--- a/_config.yml
+++ b/_config.yml
@@ -468,6 +468,8 @@ algolia_search:
# Local search
local_search:
enable: false
+ # show top n results per article, show all results by setting to -1
+ top_n_per_article: 1
# External URL with BASE64 encrypt & decrypt
# Usage: {% exturl text url "title" %}
diff --git a/layout/_third-party/search/localsearch.swig b/layout/_third-party/search/localsearch.swig
index 3ff807ec3..39e151870 100644
--- a/layout/_third-party/search/localsearch.swig
+++ b/layout/_third-party/search/localsearch.swig
@@ -4,18 +4,29 @@
var isfetched = false;
// Search DB path;
var search_path = "{{ config.search.path }}";
- if (search_path.length == 0) {
+ if (search_path.length === 0) {
search_path = "search.xml";
}
var path = "{{ config.root }}" + search_path;
// monitor main search box;
+ var onPopupClose = function (e) {
+ $('.popup').hide();
+ $('#local-search-input').val('');
+ $('.search-result-list').remove();
+ $(".local-search-pop-overlay").remove();
+ $('body').css('overflow', '');
+ }
+
function proceedsearch() {
$("body")
.append('
')
.css('overflow', 'hidden');
+ $('.search-popup-overlay').click(onPopupClose);
$('.popup').toggle();
+ $('#local-search-input').focus();
}
+
// search function;
var searchFunc = function(path, search_id, content_id) {
'use strict';
@@ -37,69 +48,161 @@
var $input = document.getElementById(search_id);
var $resultContent = document.getElementById(content_id);
$input.addEventListener('input', function(){
- var matchcounts = 0;
- var str='';
var keywords = this.value.trim().toLowerCase().split(/[\s\-]+/);
- $resultContent.innerHTML = "";
- if (this.value.trim().length > 1) {
+ var resultItems = [];
+ if (this.value.trim().length > 0) {
// perform local searching
datas.forEach(function(data) {
var isMatch = false;
- var content_index = [];
- var data_title = data.title.trim().toLowerCase();
- var data_content = data.content.trim().replace(/<[^>]+>/g,"").toLowerCase();
- var data_url = decodeURIComponent(data.url);
- var index_title = -1;
- var index_content = -1;
- var first_occur = -1;
- // only match artiles with not empty titles and contents
- if(data_title != '') {
+ var hitCountInArticle = 0;
+ var title = data.title.trim();
+ var titleInLowerCase = title.toLowerCase();
+ var content = data.content.trim().replace(/<[^>]+>/g,"");
+ var contentInLowerCase = content.toLowerCase();
+ var articleUrl = decodeURIComponent(data.url);
+ var indexOfTitle = [];
+ var indexOfContent = [];
+ // only match articles with not empty titles
+ if(title != '') {
keywords.forEach(function(keyword, i) {
- index_title = data_title.indexOf(keyword);
- index_content = data_content.indexOf(keyword);
- if( index_title >= 0 || index_content >= 0 ){
- isMatch = true;
- if (i == 0) {
- first_occur = index_content;
+ function getIndexByWord(word, text, caseSensitive) {
+ var wordLen = word.length;
+ if (wordLen === 0) {
+ return [];
+ }
+ var startPosition = 0, position = [], index = [];
+ if (!caseSensitive) {
+ text = text.toLowerCase();
+ word = word.toLowerCase();
+ }
+ while ((position = text.indexOf(word, startPosition)) > -1) {
+ index.push({position: position, word: word});
+ startPosition = position + wordLen;
}
+ return index;
}
+ indexOfTitle = indexOfTitle.concat(getIndexByWord(keyword, titleInLowerCase, false));
+ indexOfContent = indexOfContent.concat(getIndexByWord(keyword, contentInLowerCase, false));
});
+ if (indexOfTitle.length > 0 || indexOfContent.length > 0) {
+ isMatch = true;
+ hitCountInArticle = indexOfTitle.length + indexOfContent.length;
+ }
}
+
// show search results
+
if (isMatch) {
- matchcounts += 1;
- str += "- "+ data_title +"";
- var content = data.content.trim().replace(/<[^>]+>/g,"");
- if (first_occur >= 0) {
+ var resultItem = '';
+
+ function highlightKeyword(text, start, end, index) {
+ var item = index[index.length - 1];
+ var position = item.position;
+ var word = item.word;
+
+ var matchText = text.substring(start, end);
+ var matchResult = [];
+ var prevEnd = 0;
+ while (position + word.length <= end && index.length != 0) {
+
+ // highlight keyword
+
+ var wordBegin = position - start;
+ var wordEnd = position - start + word.length;
+ matchResult.push(matchText.substring(prevEnd, wordBegin));
+ matchResult.push("" + matchText.substring(wordBegin, wordEnd) + "");
+
+ // move to next position of hit
+
+ index.pop();
+ prevEnd = wordEnd;
+ while (index.length != 0) {
+ item = index[index.length - 1];
+ position = item.position;
+ word = item.word;
+ if (prevEnd > position - start) {
+ index.pop();
+ } else {
+ break;
+ }
+ }
+ }
+ matchResult.push(matchText.substring(prevEnd));
+ return matchResult.join('');
+ }
+
+ // sort index by position of keyword
+
+ indexOfTitle.sort(function (itemLeft, itemRight) {
+ return itemRight.position - itemLeft.position;
+ });
+
+ indexOfContent.sort(function (itemLeft, itemRight) {
+ return itemRight.position - itemLeft.position;
+ });
+
+ // highlight title
+
+ if (indexOfTitle.length != 0) {
+ resultItem += "
- " + highlightKeyword(title, 0, title.length, indexOfTitle) + "";
+ } else {
+ resultItem += "
- " + title + "";
+ }
+
+ // highlight content
+
+ var resultUpperBound = parseInt({{ theme.local_search.top_n_per_article }});
+ var withoutUpperBound = false;
+ if (resultUpperBound === -1) {
+ withoutUpperBound = true;
+ }
+ var currentResultNum = 0;
+ while (indexOfContent.length != 0 && (withoutUpperBound || (currentResultNum < resultUpperBound))) {
+ var item = indexOfContent[indexOfContent.length - 1];
+ var position = item.position;
+ var word = item.word;
// cut out 100 characters
- var start = first_occur - 20;
- var end = first_occur + 80;
+ var start = position - 20;
+ var end = position + 80;
if(start < 0){
start = 0;
}
- if(start == 0){
- end = 50;
+ if (end < position + word.length) {
+ end = position + word.length;
}
if(end > content.length){
end = content.length;
}
- var match_content = content.substring(start, end);
- // highlight all keywords
- keywords.forEach(function(keyword){
- var regS = new RegExp(keyword, "gi");
- match_content = match_content.replace(regS, ""+keyword+"");
- });
-
- str += "
" + match_content +"...
"
+ resultItem += "" +
+ "" + highlightKeyword(content, start, end, indexOfContent) +
+ "...
" + "";
+ currentResultNum++;
}
- str += " ";
+ resultItem += "";
+ resultItems.push({item: resultItem, hitCount: hitCountInArticle, id: resultItems.length});
+ }
+ })
+ };
+ if (keywords.length === 1 && keywords[0] === "") {
+ $resultContent.innerHTML = '
'
+ } else if (resultItems.length === 0) {
+ $resultContent.innerHTML = '
'
+ } else {
+ resultItems.sort(function (resultLeft, resultRight) {
+ if (resultLeft.hitCount != resultRight.hitCount) {
+ return resultRight.hitCount - resultLeft.hitCount;
+ } else {
+ return resultLeft.id - resultRight.id;
}
- })};
- str += "
";
- if (matchcounts == 0) { str = '
' }
- if (keywords == "") { str = '
' }
- $resultContent.innerHTML = str;
+ });
+ var searchResultList = '';
+ resultItems.forEach(function (result, i) {
+ searchResultList += result.item;
+ })
+ searchResultList += "
";
+ $resultContent.innerHTML = searchResultList;
+ }
});
proceedsearch();
}
@@ -108,18 +211,14 @@
// handle and trigger popup window;
$('.popup-trigger').click(function(e) {
e.stopPropagation();
- if (isfetched == false) {
+ if (isfetched === false) {
searchFunc(path, 'local-search-input', 'local-search-result');
} else {
proceedsearch();
};
});
- $('.popup-btn-close').click(function(e){
- $('.popup').hide();
- $(".local-search-pop-overlay").remove();
- $('body').css('overflow', '');
- });
+ $('.popup-btn-close').click(onPopupClose);
$('.popup').click(function(e){
e.stopPropagation();
});
diff --git a/source/css/_common/components/third-party/localsearch.styl b/source/css/_common/components/third-party/localsearch.styl
index a253a2ccd..85f43cf9a 100644
--- a/source/css/_common/components/third-party/localsearch.styl
+++ b/source/css/_common/components/third-party/localsearch.styl
@@ -43,7 +43,6 @@
.search-keyword
border-bottom: 1px dashed #f00
- font-size: 14px
font-weight: bold
color: #f00
@@ -62,7 +61,7 @@
.local-search-input-wrapper
display: inline-block
- width: calc(100% - 60px)
+ width: calc(100% - 90px)
height: 36px
line-height: 36px
padding: 0 5px
@@ -83,6 +82,8 @@
color: #999
height: 36px
width: 18px
+ padding-left: 10px
+ padding-right: 10px
.search-icon
float: left
@@ -91,7 +92,6 @@
border-left: 1px solid #eee
float: right
cursor: pointer
- padding-left: 10px
#no-result
position: absolute