Skip to content

Commit 823e970

Browse files
committed
split search
1 parent af1cb3a commit 823e970

File tree

7 files changed

+1409
-419
lines changed

7 files changed

+1409
-419
lines changed

layout/_partial/scripts/index.ejs

-10
Original file line numberDiff line numberDiff line change
@@ -76,16 +76,6 @@
7676
<%- js(['js/app.js']) %>
7777
<% } %>
7878

79-
<% if (theme.search.enable) { %>
80-
<% if (theme.search.js) { %>
81-
<%- js(theme.search.js) %>
82-
<% } else if(theme.use_cdn) { %>
83-
<%- js(['https://cdn.jsdelivr.net/npm/hexo-theme-volantis@'+theme.info.theme_version+'/source/js/search.min.js']) %>
84-
<% } else { %>
85-
<%- js(['js/search.js']) %>
86-
<% } %>
87-
<% } %>
88-
8979
<!-- optional -->
9080
<% if (theme.search && theme.search.enable) { %>
9181
<%- partial('search') %>

layout/_partial/scripts/search.ejs

+17-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
<script>
22
const SearchServiceimagePath="https://cdn.jsdelivr.net/gh/volantis-x/cdn-volantis@master/img/";
33
const ROOT = ("<%- config.root %>" || "/").endsWith('/') ? ("<%- config.root %>" || "/") : ("<%- config.root %>/" || "/" );
4+
5+
$('.input.u-search-input').one('focus',function(){
6+
<% if (theme.search.js) { %>
7+
loadScript('<%- theme.search.js %>',setSearchService);
8+
<% } else if(theme.use_cdn) { %>
9+
loadScript('<%- 'https://cdn.jsdelivr.net/npm/hexo-theme-volantis@'+theme.info.theme_version+'/source/js/search/'+theme.search.service+'.min.js' %>',setSearchService);
10+
<% } else { %>
11+
loadScript('<%- '/js/search/'+theme.search.service+'.js' %>',setSearchService);
12+
<% } %>
13+
})
14+
415
function listenSearch(){
516
<% if(theme.search.service === 'baidu') { %>
617
const BAIDU_API_ID = "<%- theme.search.baidu.apiId %>";
@@ -42,8 +53,10 @@ function listenSearch(){
4253
});
4354
<%}%>
4455
}
45-
document.addEventListener("DOMContentLoaded", listenSearch);
46-
<% if(theme.cover.layout_scheme=='search'){%>
47-
document.addEventListener("pjax:success", listenSearch);
48-
<% } %>
56+
function setSearchService() {
57+
listenSearch();
58+
<% if(theme.cover.layout_scheme=='search'){%>
59+
document.addEventListener("pjax:success", listenSearch);
60+
<% } %>
61+
}
4962
</script>

source/js/search/algolia.js

+348
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,348 @@
1+
var SearchService = "";
2+
3+
(function($) {
4+
/**
5+
* A super class of common logics for all search services
6+
* @param options : (object)
7+
*/
8+
SearchService = function(options) {
9+
var self = this;
10+
11+
self.config = $.extend({
12+
per_page: 10,
13+
selectors: {
14+
body: "body",
15+
form: ".u-search-form",
16+
input: ".u-search-input",
17+
container: "#u-search",
18+
modal: "#u-search .modal",
19+
modal_body: "#u-search .modal-body",
20+
modal_footer: "#u-search .modal-footer",
21+
modal_overlay: "#u-search .modal-overlay",
22+
modal_results: "#u-search .modal-results",
23+
modal_metadata: "#u-search .modal-metadata",
24+
modal_error: "#u-search .modal-error",
25+
modal_loading_bar: "#u-search .modal-loading-bar",
26+
modal_ajax_content: "#u-search .modal-ajax-content",
27+
modal_logo: '#u-search .modal-footer .logo',
28+
btn_close: "#u-search .btn-close",
29+
btn_next: "#u-search .btn-next",
30+
btn_prev: "#u-search .btn-prev"
31+
},
32+
brands: {
33+
'hexo': {logo: '', url: ''},
34+
'google': {logo: 'google.svg', url: 'https://cse.google.com'},
35+
'algolia': {logo: 'algolia.svg', url: 'https://www.algolia.com'},
36+
'baidu': {logo: 'baidu.svg', url: 'http://zn.baidu.com/cse/home/index'},
37+
'azure': {logo: 'azure.svg', url: 'https://azure.microsoft.com/en-us/services/search/'}
38+
},
39+
imagePath: "https://cdn.jsdelivr.net/gh/volantis-x/cdn-volantis@master/img/"
40+
}, options);
41+
42+
self.dom = {};
43+
self.percentLoaded = 0;
44+
self.open = false;
45+
self.queryText = "";
46+
self.nav = {
47+
next: -1,
48+
prev: -1,
49+
total: 0,
50+
current: 1
51+
};
52+
53+
self.parseSelectors = function() {
54+
for (var key in self.config.selectors) {
55+
self.dom[key] = $(self.config.selectors[key]);
56+
}
57+
};
58+
59+
self.beforeQuery = function() {
60+
if (!self.open) {
61+
self.dom.container.fadeIn();
62+
// self.dom.body.addClass('modal-active');
63+
// 上面的是去除了文章的滚动条,我觉得没必要
64+
}
65+
self.dom.input.each(function(index,elem) {
66+
$(elem).val(self.queryText);
67+
});
68+
document.activeElement.blur();
69+
self.dom.modal_error.hide();
70+
self.dom.modal_ajax_content.removeClass('loaded');
71+
self.startLoading();
72+
};
73+
74+
self.afterQuery = function() {
75+
self.dom.modal_body.scrollTop(0);
76+
self.dom.modal_ajax_content.addClass('loaded');
77+
self.stopLoading();
78+
};
79+
80+
/**
81+
* Perform a complete serach operation including UI updates and query
82+
* @param startIndex {int} start index or page number
83+
*/
84+
self.search = function(startIndex, callback) {
85+
self.beforeQuery();
86+
if (self.search instanceof Function) {
87+
self.query(self.queryText, startIndex, function() {
88+
self.afterQuery();
89+
});
90+
}
91+
else {
92+
console.log("query() does not exist.");
93+
self.onQueryError(self.queryText, '');
94+
self.afterQuery();
95+
}
96+
};
97+
98+
/**
99+
* Query error handler
100+
* @param queryText: (string)
101+
* @param status: (string)
102+
*/
103+
self.onQueryError = function(queryText, status) {
104+
var errMsg = "";
105+
if (status === "success") errMsg = "No result found for \"" +queryText+ "\".";
106+
else if (status === "timeout") errMsg = "Unfortunate timeout.";
107+
else errMsg = "Mysterious failure.";
108+
self.dom.modal_results.html("");
109+
self.dom.modal_error.html(errMsg);
110+
self.dom.modal_error.show();
111+
};
112+
113+
self.nextPage = function() {
114+
if (self.nav.next !== -1) {
115+
self.search(self.nav.next);
116+
}
117+
};
118+
119+
self.prevPage = function() {
120+
if (self.nav.prev !== -1) {
121+
self.search(self.nav.prev);
122+
}
123+
};
124+
125+
self.getUrlRelativePath = function (url) {
126+
var arrUrl = url.split("//");
127+
var start = arrUrl[1].indexOf("/");
128+
var relUrl = arrUrl[1].substring(start);
129+
if (relUrl.indexOf("?") != -1) {
130+
relUrl = relUrl.split("?")[0];
131+
}
132+
return relUrl;
133+
}
134+
135+
/**
136+
* Generate html for one result
137+
* @param url : (string) url
138+
* @param title : (string) title
139+
* @param digest : (string) digest
140+
*/
141+
self.buildResult = function (url, title, digest) {
142+
var result = self.getUrlRelativePath(url);
143+
var html = "";
144+
html = "<li>";
145+
html += "<a class='result' href='" + result + "'>";
146+
html += "<span class='title'>" + title + "</span>";
147+
if (digest !== "") html += "<span class='digest'>" + digest + "</span>";
148+
html += "</a>";
149+
html += "</li>";
150+
return html;
151+
};
152+
153+
/**
154+
* Close the modal, resume body scrolling
155+
* no param
156+
*/
157+
self.close = function() {
158+
self.open = false;
159+
self.dom.container.fadeOut();
160+
self.dom.body.removeClass('modal-active');
161+
};
162+
163+
/**
164+
* Searchform submit event handler
165+
* @param queryText : (string) the query text
166+
*/
167+
self.onSubmit = function(event) {
168+
event.preventDefault();
169+
self.queryText = $(this).find('.u-search-input').val();
170+
if (self.queryText) {
171+
self.search(1);
172+
}
173+
};
174+
175+
/**
176+
* Start loading bar animation
177+
* no param
178+
*/
179+
self.startLoading = function() {
180+
self.dom.modal_loading_bar.show();
181+
self.loadingTimer = setInterval(function() {
182+
self.percentLoaded = Math.min(self.percentLoaded+5,95);
183+
self.dom.modal_loading_bar.css('width', self.percentLoaded+'%');
184+
}, 100);
185+
};
186+
187+
/**
188+
* Stop loading bar animation
189+
* no param
190+
*/
191+
self.stopLoading = function() {
192+
clearInterval(self.loadingTimer);
193+
self.dom.modal_loading_bar.css('width', '100%');
194+
self.dom.modal_loading_bar.fadeOut();
195+
setTimeout(function() {
196+
self.percentLoaded = 0;
197+
self.dom.modal_loading_bar.css('width', '0%');
198+
}, 300);
199+
};
200+
201+
/**
202+
* Add service branding
203+
* @param service {String} service name
204+
*/
205+
self.addLogo = function(service) {
206+
var html = "";
207+
if (self.config.brands[service] && self.config.brands[service].logo) {
208+
html += "<a href='" +self.config.brands[service].url+ "' class='" +service+ "'>";
209+
html += '<img src="' +self.config.imagePath+self.config.brands[service].logo+ '" />';
210+
html += "</a>";
211+
self.dom.modal_logo.html(html);
212+
}
213+
};
214+
215+
self.destroy = function() {
216+
self.dom.form.each(function(index,elem) {
217+
$(elem).off('submit');
218+
});
219+
self.dom.modal_overlay.off('click');
220+
self.dom.btn_close.off('click');
221+
self.dom.btn_next.off('click');
222+
self.dom.btn_prev.off('click');
223+
self.dom.container.remove();
224+
};
225+
226+
/**
227+
* Load template and register event handlers
228+
* no param
229+
*/
230+
self.init = function() {
231+
$('body').append(template);
232+
self.parseSelectors();
233+
self.dom.modal_footer.show();
234+
self.dom.form.each(function(index,elem) {
235+
$(elem).on('submit', self.onSubmit);
236+
});
237+
self.dom.modal_overlay.on('click', self.close);
238+
self.dom.btn_close.on('click', self.close);
239+
self.dom.btn_next.on('click', self.nextPage);
240+
self.dom.btn_prev.on('click', self.prevPage);
241+
};
242+
243+
self.init();
244+
};
245+
246+
var template = '<div id="u-search"><div class="modal"> <header class="modal-header" class="clearfix"><form id="u-search-modal-form" class="u-search-form" name="uSearchModalForm"> <input type="text" id="u-search-modal-input" class="u-search-input" /> <button type="submit" id="u-search-modal-btn-submit" class="u-search-btn-submit"> <span class="fas fa-search"></span> </button></form> <a class="btn-close"> <span class="fas fa-times"></span> </a><div class="modal-loading"><div class="modal-loading-bar"></div></div> </header> <main class="modal-body"><ul class="modal-results modal-ajax-content"></ul> </main> <footer class="modal-footer clearfix"><div class="modal-metadata modal-ajax-content"> <strong class="range"></strong> of <strong class="total"></strong></div><div class="modal-error"></div> <div class="logo"></div> <a class="nav btn-next modal-ajax-content"> <span class="text">NEXT</span> <span class="fal fa-chevron-right"></span> </a> <a class="nav btn-prev modal-ajax-content"> <span class="fal fa-chevron-left"></span> <span class="text">PREV</span> </a> </footer></div><div class="modal-overlay"></div></div>';
247+
})(jQuery);
248+
249+
var AlgoliaSearch;
250+
(function($) {
251+
'use strict';
252+
253+
/**
254+
* Search by Algolia Search
255+
* @param options : (object)
256+
*/
257+
AlgoliaSearch = function(options) {
258+
SearchService.apply(this, arguments);
259+
var self = this;
260+
var endpoint = "https://" +self.config.appId+ "-dsn.algolia.net/1/indexes/" + self.config.indexName;
261+
self.addLogo('algolia');
262+
263+
/**
264+
* Generate result list html
265+
* @param data : (array) result items
266+
*/
267+
self.buildResultList = function(data) {
268+
var html = "";
269+
$.each(data, function(index, row) {
270+
var url = row.permalink || row.path || "";
271+
if (!row.permalink && row.path) {
272+
url = ROOT + url;
273+
}
274+
var title = row.title;
275+
var digest = "";
276+
html += self.buildResult(url, title, digest, index+1);
277+
});
278+
html += "<script>try{pjax.refresh(document.querySelector('#u-search'));document.addEventListener('pjax:send',function(){$('#u-search').fadeOut(500);$('body').removeClass('modal-active')});}catch(e){$('#u-search').fadeOut(500);}</script>";
279+
return html;
280+
};
281+
282+
/**
283+
* Generate metadata after a successful query
284+
* @param data : (object) the raw search response data
285+
*/
286+
self.buildMetadata = function(data) {
287+
self.nav.current = data.page * data.hitsPerPage + 1;
288+
self.nav.currentCount = data.hits.length;
289+
self.nav.total = parseInt(data.nbHits);
290+
self.dom.modal_metadata.children('.total').html(self.nav.total);
291+
self.dom.modal_metadata.children('.range').html(self.nav.current + "-" + (self.nav.current+self.nav.currentCount-1));
292+
if (self.nav.total > 0) {
293+
self.dom.modal_metadata.show();
294+
}
295+
else {
296+
self.dom.modal_metadata.hide();
297+
}
298+
299+
if (data.page < data.nbPages-1) {
300+
self.nav.next = (data.page+1)+1;
301+
self.dom.btn_next.show();
302+
}
303+
else {
304+
self.nav.next = -1;
305+
self.dom.btn_next.hide();
306+
}
307+
if (data.page > 0) {
308+
self.nav.prev = (data.page+1)-1;
309+
self.dom.btn_prev.show();
310+
}
311+
else {
312+
self.nav.prev = -1;
313+
self.dom.btn_prev.hide();
314+
}
315+
};
316+
317+
/**
318+
* Send a GET request
319+
* @param queryText : (string) the query text
320+
* @param page : (int) the current page (start from 1)
321+
* @param callback : (function)
322+
*/
323+
self.query = function(queryText, page, callback) {
324+
$.get(endpoint, {
325+
query: queryText,
326+
page: page-1,
327+
hitsPerPage: self.config.per_page,
328+
"x-algolia-application-id": self.config.appId,
329+
"x-algolia-api-key": self.config.apiKey
330+
}, function(data, status) {
331+
if (status === 'success' && data.hits && data.hits.length > 0) {
332+
var results = self.buildResultList(data.hits);
333+
self.dom.modal_results.html(results);
334+
}
335+
else {
336+
self.onQueryError(queryText, status);
337+
}
338+
self.buildMetadata(data);
339+
if (callback) {
340+
callback(data);
341+
}
342+
});
343+
};
344+
345+
return self;
346+
};
347+
348+
})(jQuery);

0 commit comments

Comments
 (0)