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