feat: add insight search
parent
0c40390faf
commit
56b701b9bd
|
@ -43,6 +43,7 @@ widgets:
|
||||||
|
|
||||||
# Search
|
# Search
|
||||||
search:
|
search:
|
||||||
|
insight: true
|
||||||
swiftype: # enter swiftype install key here
|
swiftype: # enter swiftype install key here
|
||||||
baidu: false # you need to disable other search engines to use Baidu search, options: true, false
|
baidu: false # you need to disable other search engines to use Baidu search, options: true, false
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
<% if (theme.search.swiftype) { %>
|
<% if (theme.search.insight) { %>
|
||||||
|
<div class="search-form">
|
||||||
|
<input type="text" class="ins-search-input search-form-input" placeholder="<%= __('index.search') %>" />
|
||||||
|
</div>
|
||||||
|
<% } else if (theme.search.swiftype) { %>
|
||||||
<div class="search-form">
|
<div class="search-form">
|
||||||
<input type="text" class="st-default-search-input search-form-input" placeholder="<%= __('index.search') %>" />
|
<input type="text" class="st-default-search-input search-form-input" placeholder="<%= __('index.search') %>" />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
<div id="search-form-wrap">
|
<div id="search-form-wrap">
|
||||||
<% if (theme.search.swiftype) { %>
|
<% if (theme.search.insight) { %>
|
||||||
|
<form class="search-form">
|
||||||
|
<input type="text" class="ins-search-input search-form-input" placeholder="<%= __('index.search') %>" />
|
||||||
|
<button type="submit" class="search-form-submit"></button>
|
||||||
|
</form>
|
||||||
|
<%- partial('search/insight') %>
|
||||||
|
<% } else if (theme.search.swiftype) { %>
|
||||||
<form class="search-form">
|
<form class="search-form">
|
||||||
<input type="text" class="st-default-search-input search-form-input" placeholder="<%= __('index.search') %>" />
|
<input type="text" class="st-default-search-input search-form-input" placeholder="<%= __('index.search') %>" />
|
||||||
<button type="submit" class="search-form-submit"></button>
|
<button type="submit" class="search-form-submit"></button>
|
||||||
|
|
|
@ -0,0 +1,201 @@
|
||||||
|
<div class="ins-search">
|
||||||
|
<div class="ins-search-mask"></div>
|
||||||
|
<div class="ins-search-container">
|
||||||
|
<div class="ins-input-wrapper">
|
||||||
|
<input type="text" class="ins-search-input" placeholder="Type something..." />
|
||||||
|
<span class="ins-close ins-selectable"><i class="fa fa-times-circle"></i></span>
|
||||||
|
</div>
|
||||||
|
<div class="ins-section-wrapper">
|
||||||
|
<div class="ins-section-container"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
(function ($) {
|
||||||
|
$main = $('.ins-search');
|
||||||
|
$main.parent().remove('.ins-search');
|
||||||
|
$('body').append($main);
|
||||||
|
$container = $('.ins-section-container');
|
||||||
|
|
||||||
|
$(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 $('<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)
|
||||||
|
.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;
|
||||||
|
switch (type) {
|
||||||
|
case 'POSTS':
|
||||||
|
case 'PAGES':
|
||||||
|
sectionTitle = type == 'POSTS' ? 'Posts' : '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 %> + item.path);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'CATEGORIES':
|
||||||
|
case 'TAGS':
|
||||||
|
sectionTitle = type == 'CATEGORIES' ? 'Categories' : '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]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$.getJSON('<%- url_for("/content.json")%>', function (json) {
|
||||||
|
console.log(json)
|
||||||
|
$('.ins-search-input').on('input', function () {
|
||||||
|
var keywords = $(this).val();
|
||||||
|
searchResultToDOM(search(json, keywords));
|
||||||
|
});
|
||||||
|
$('.ins-search-input').trigger('input');
|
||||||
|
});
|
||||||
|
})(jQuery);
|
||||||
|
</script>
|
|
@ -152,6 +152,7 @@ $nav-link
|
||||||
color: #777
|
color: #777
|
||||||
|
|
||||||
.search-form-input,
|
.search-form-input,
|
||||||
|
.search-form-input.ins-search-input,
|
||||||
.search-form-input.st-ui-search-input,
|
.search-form-input.st-ui-search-input,
|
||||||
.search-form-input.st-default-search-input
|
.search-form-input.st-default-search-input
|
||||||
-webkit-appearance: textarea
|
-webkit-appearance: textarea
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
// Insight Search Styles
|
||||||
|
ins-container-width = 540px
|
||||||
|
ins-text-grey = #9a9a9a
|
||||||
|
ins-border-grey = #e2e2e2
|
||||||
|
ins-background-grey = #f7f7f7
|
||||||
|
ins-background-blue = #006BDE
|
||||||
|
|
||||||
|
$ins-full-screen
|
||||||
|
top: 0
|
||||||
|
left: 0
|
||||||
|
margin: 0
|
||||||
|
width: 100%
|
||||||
|
height: 100%
|
||||||
|
|
||||||
|
.ins-search
|
||||||
|
display: none
|
||||||
|
&.show
|
||||||
|
display: block
|
||||||
|
|
||||||
|
.ins-selectable
|
||||||
|
cursor: pointer
|
||||||
|
|
||||||
|
.ins-search-mask,
|
||||||
|
.ins-search-container
|
||||||
|
position: fixed
|
||||||
|
|
||||||
|
.ins-search-mask
|
||||||
|
top: 0
|
||||||
|
left: 0
|
||||||
|
width: 100%
|
||||||
|
height: 100%
|
||||||
|
z-index: 100
|
||||||
|
background: rgba(0,0,0,0.5)
|
||||||
|
|
||||||
|
.ins-input-wrapper
|
||||||
|
position: relative
|
||||||
|
|
||||||
|
.ins-search-input
|
||||||
|
width: 100%
|
||||||
|
border: none
|
||||||
|
outline: none
|
||||||
|
font-size: 16px
|
||||||
|
font-weight: 200
|
||||||
|
background: white
|
||||||
|
line-height: 20px
|
||||||
|
box-sizing: border-box
|
||||||
|
padding: 12px 28px 12px 20px
|
||||||
|
border-bottom: 1px solid ins-border-grey
|
||||||
|
font-family: "Microsoft Yahei Light", "Microsoft Yahei", Helvetica, Arial, sans-serif
|
||||||
|
|
||||||
|
.ins-close
|
||||||
|
top: 50%
|
||||||
|
right: 6px
|
||||||
|
width: 20px
|
||||||
|
height: 20px
|
||||||
|
font-size: 16px
|
||||||
|
margin-top: -11px
|
||||||
|
position: absolute
|
||||||
|
text-align: center
|
||||||
|
display: inline-block
|
||||||
|
&:hover
|
||||||
|
color: ins-background-blue
|
||||||
|
|
||||||
|
.ins-search-container
|
||||||
|
left: 50%
|
||||||
|
top: 100px
|
||||||
|
z-index: 101
|
||||||
|
bottom: 100px
|
||||||
|
box-sizing: border-box
|
||||||
|
width: ins-container-width
|
||||||
|
margin-left: -(ins-container-width/2)
|
||||||
|
@media screen and (max-width: 559px), screen and (max-height: 479px)
|
||||||
|
top: 0
|
||||||
|
left: 0
|
||||||
|
margin: 0
|
||||||
|
width: 100%
|
||||||
|
height: 100%
|
||||||
|
background: ins-background-grey
|
||||||
|
|
||||||
|
.ins-section-wrapper
|
||||||
|
left: 0
|
||||||
|
right: 0
|
||||||
|
top: 45px
|
||||||
|
bottom: 0
|
||||||
|
overflow-y: auto
|
||||||
|
position: absolute
|
||||||
|
|
||||||
|
.ins-section-container
|
||||||
|
background: ins-background-grey
|
||||||
|
|
||||||
|
.ins-section
|
||||||
|
font-size: 14px
|
||||||
|
line-height: 16px
|
||||||
|
.ins-section-header,
|
||||||
|
.ins-search-item
|
||||||
|
padding: 8px 15px
|
||||||
|
.ins-section-header
|
||||||
|
color: ins-text-grey
|
||||||
|
border-bottom: 1px solid ins-border-grey
|
||||||
|
.ins-slug
|
||||||
|
margin-left: 5px
|
||||||
|
color: ins-text-grey
|
||||||
|
&:before
|
||||||
|
content: '('
|
||||||
|
&:after
|
||||||
|
content: ')'
|
||||||
|
.ins-search-item
|
||||||
|
header,
|
||||||
|
.ins-search-preview
|
||||||
|
overflow: hidden
|
||||||
|
white-space: nowrap
|
||||||
|
text-overflow: ellipsis
|
||||||
|
header
|
||||||
|
.fa
|
||||||
|
margin-right: 8px
|
||||||
|
.ins-search-preview
|
||||||
|
height: 15px
|
||||||
|
font-size: 12px
|
||||||
|
color: ins-text-grey
|
||||||
|
margin: 5px 0 0 20px
|
||||||
|
&:hover
|
||||||
|
color: white
|
||||||
|
background: ins-background-blue
|
||||||
|
.ins-slug,
|
||||||
|
.ins-search-preview
|
||||||
|
color: white
|
|
@ -83,6 +83,7 @@ code
|
||||||
@import "_partial/timeline"
|
@import "_partial/timeline"
|
||||||
@import "_partial/footer"
|
@import "_partial/footer"
|
||||||
@import "_partial/sidebar"
|
@import "_partial/sidebar"
|
||||||
|
@import "_partial/insight"
|
||||||
@import "_highlight/index"
|
@import "_highlight/index"
|
||||||
|
|
||||||
if sidebar is left
|
if sidebar is left
|
||||||
|
|
Loading…
Reference in New Issue