1
|
|
|
/* |
2
|
|
|
* FullTextSearch - Full text search framework for Nextcloud |
3
|
|
|
* |
4
|
|
|
* This file is licensed under the Affero General Public License version 3 or |
5
|
|
|
* later. See the COPYING file. |
6
|
|
|
* |
7
|
|
|
* @author Maxence Lange <[email protected]> |
8
|
|
|
* @copyright 2018 |
9
|
|
|
* @license GNU AGPL version 3 or any later version |
10
|
|
|
* |
11
|
|
|
* This program is free software: you can redistribute it and/or modify |
12
|
|
|
* it under the terms of the GNU Affero General Public License as |
13
|
|
|
* published by the Free Software Foundation, either version 3 of the |
14
|
|
|
* License, or (at your option) any later version. |
15
|
|
|
* |
16
|
|
|
* This program is distributed in the hope that it will be useful, |
17
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
18
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
19
|
|
|
* GNU Affero General Public License for more details. |
20
|
|
|
* |
21
|
|
|
* You should have received a copy of the GNU Affero General Public License |
22
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
23
|
|
|
* |
24
|
|
|
*/ |
25
|
|
|
|
26
|
|
|
/** global: OCA */ |
27
|
|
|
/** global: api */ |
28
|
|
|
/** global: search */ |
29
|
|
|
/** global: result */ |
30
|
|
|
/** global: settings */ |
31
|
|
|
|
32
|
|
|
|
33
|
|
|
|
34
|
|
|
var curr = { |
35
|
|
|
providerResult: [], |
36
|
|
|
page: 1, |
37
|
|
|
lastRequest: '', |
38
|
|
|
lastRequestTimer: null, |
39
|
|
|
lastRequestTimerQueued: false, |
40
|
|
|
lastRequestTimerForcing: false, |
41
|
|
|
|
42
|
|
|
setProviderResult: function (id, value) { |
43
|
|
|
curr.providerResult[id] = value; |
44
|
|
|
}, |
45
|
|
|
|
46
|
|
|
getProviderResult: function (id) { |
47
|
|
|
var current = curr.providerResult[id]; |
48
|
|
|
if (!current) { |
49
|
|
|
current = []; |
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
return current; |
53
|
|
|
} |
54
|
|
|
|
55
|
|
|
}; |
56
|
|
|
|
57
|
|
|
|
58
|
|
|
var nav = { |
59
|
|
|
|
60
|
|
|
manageDivProviderNavigation: function (divProviderNavigation, request, meta) { |
61
|
|
|
|
62
|
|
|
var maxPage = Math.ceil(meta.total / request.size); |
63
|
|
|
|
64
|
|
|
divProviderNavigation.attr('data-time', meta.time); |
65
|
|
|
divProviderNavigation.attr('data-page', request.page); |
66
|
|
|
divProviderNavigation.attr('data-options', JSON.stringify(request.options)); |
67
|
|
|
divProviderNavigation.attr('data-search', request.search); |
68
|
|
|
divProviderNavigation.attr('data-max-page', maxPage); |
69
|
|
|
divProviderNavigation.attr('data-size', request.size); |
70
|
|
|
divProviderNavigation.attr('data-total', meta.total); |
71
|
|
|
|
72
|
|
|
var providerTitle = divProviderNavigation.attr('data-provider-title'); |
73
|
|
|
var left = "the search in " + providerTitle + " for '" + request.search + "' returned " + |
74
|
|
|
meta.total + " results in " + meta.time + "ms"; |
75
|
|
|
divProviderNavigation.find('.provider_navigation_left').text(left); |
76
|
|
|
|
77
|
|
|
if (maxPage > 1) { |
78
|
|
|
divProviderNavigation.find('.provider_navigation_curr').text(request.page + ' / ' + |
79
|
|
|
maxPage).stop().fadeTo(200, 1); |
80
|
|
|
|
81
|
|
|
divProviderNavigation.find('.provider_navigation_prev').stop().fadeTo(200, |
82
|
|
|
(request.page > 1) ? 1 : 0); |
83
|
|
|
divProviderNavigation.find('.provider_navigation_next').stop().fadeTo(200, |
84
|
|
|
(request.page < maxPage) ? 1 : 0); |
85
|
|
|
} else { |
86
|
|
|
divProviderNavigation.find('.provider_navigation_prev').stop().fadeTo(200, 0); |
87
|
|
|
divProviderNavigation.find('.provider_navigation_curr').stop().fadeTo(200, 0); |
88
|
|
|
divProviderNavigation.find('.provider_navigation_next').stop().fadeTo(200, 0); |
89
|
|
|
} |
90
|
|
|
}, |
91
|
|
|
|
92
|
|
|
|
93
|
|
|
manageDivProviderResult: function (divProviderResult, newResult, oldResult) { |
94
|
|
|
//replaceWith(); |
95
|
|
|
nav.divProviderResultAddItems(divProviderResult, newResult, oldResult); |
96
|
|
|
if (oldResult) { |
97
|
|
|
nav.divProviderResultRemoveItems(divProviderResult, newResult, oldResult); |
98
|
|
|
nav.divProviderResultMoveItems(divProviderResult, newResult, oldResult); |
99
|
|
|
} |
100
|
|
|
}, |
101
|
|
|
|
102
|
|
|
|
103
|
|
|
divProviderResultAddItems: function (divProviderResult, newResult, oldResult) { |
104
|
|
|
|
105
|
|
|
var precItem = null; |
106
|
|
|
for (var i = 0; i < newResult.length; i++) { |
107
|
|
|
var entry = newResult[i]; |
108
|
|
|
if (result.getResultIndex(entry.id, oldResult) > -1) { |
109
|
|
|
precItem = nav.getDivResult(entry.id, divProviderResult); |
110
|
|
|
nav.fillDivResult(precItem, entry); |
111
|
|
|
continue; |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
var divResult = nav.generateDivResult(entry, nav.generateTemplateEntry(entry)); |
115
|
|
|
if (precItem === null) { |
116
|
|
|
divProviderResult.prepend(divResult); |
117
|
|
|
} else { |
118
|
|
|
precItem.after(divResult); |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
divResult.slideDown(settings.delay_result, function () { |
|
|
|
|
122
|
|
|
$(this).children('.result_template').fadeTo(settings.delay_result, 1); |
123
|
|
|
}); |
124
|
|
|
|
125
|
|
|
precItem = divResult; |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
}, |
129
|
|
|
|
130
|
|
|
|
131
|
|
|
divProviderResultRemoveItems: function (divProviderResult, newResult, oldResult) { |
132
|
|
|
for (var i = 0; i < oldResult.length; i++) { |
133
|
|
|
var entry = oldResult[i]; |
134
|
|
|
if (result.getResultIndex(entry.id, newResult) === -1) { |
135
|
|
|
var divResult = nav.getDivResult(entry.id, divProviderResult); |
136
|
|
|
divResult.fadeTo(settings.delay_result, 0, function () { |
|
|
|
|
137
|
|
|
$(this).slideUp(settings.delay_result, function () { |
138
|
|
|
$(this).remove(); |
139
|
|
|
}); |
140
|
|
|
}); |
141
|
|
|
} |
142
|
|
|
} |
143
|
|
|
}, |
144
|
|
|
|
145
|
|
|
|
146
|
|
|
divProviderResultMoveItems: function (divProviderResult, newResult, oldResult) { |
147
|
|
|
|
148
|
|
|
var precId = ''; |
149
|
|
|
|
150
|
|
|
oldResult = result.recalibrateResult(oldResult, newResult); |
151
|
|
|
newResult = result.recalibrateResult(newResult, oldResult); |
152
|
|
|
for (var i = 0; i < newResult.length; i++) { |
153
|
|
|
var entry = newResult[i]; |
154
|
|
|
|
155
|
|
|
var pos = result.getResultIndex(entry.id, oldResult); |
156
|
|
|
if (pos > -1 && pos !== i) { |
157
|
|
|
nav.animateMoveDivResult(entry.id, divProviderResult, precId); |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
precId = newResult[i].id; |
161
|
|
|
} |
162
|
|
|
}, |
163
|
|
|
|
164
|
|
|
|
165
|
|
|
animateMoveDivResult: function (entryId, divProviderResult, precId) { |
166
|
|
|
|
167
|
|
|
var divResult = nav.getDivResult(entryId, divProviderResult); |
168
|
|
|
|
169
|
|
|
if (precId === '') { |
170
|
|
|
divResult.fadeTo(settings.delay_result, 0.35, function () { |
171
|
|
|
$(this).prependTo(divProviderResult).fadeTo(100, 1); |
172
|
|
|
}); |
173
|
|
|
} else { |
174
|
|
|
var precItem = nav.getDivResult(precId, divProviderResult); |
175
|
|
|
divResult.fadeTo(settings.delay_result, 0.35, function () { |
176
|
|
|
$(this).insertAfter(precItem).fadeTo(100, 1); |
177
|
|
|
}); |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
}, |
181
|
|
|
|
182
|
|
|
|
183
|
|
|
getDivProvider: function (providerId, providerName) { |
184
|
|
|
var ret = null; |
185
|
|
|
settings.resultContainer.children('.provider_header').each(function () { |
186
|
|
|
if ($(this).attr('data-id') === providerId) { |
187
|
|
|
ret = $(this); |
188
|
|
|
} |
189
|
|
|
}); |
190
|
|
|
|
191
|
|
|
if (ret === null) { |
192
|
|
|
ret = nav.generateDivProvider(providerId, providerName); |
193
|
|
|
settings.resultContainer.append(ret); |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
return ret; |
197
|
|
|
}, |
198
|
|
|
|
199
|
|
|
|
200
|
|
|
getDivResult: function (resultId, divProviderResult) { |
201
|
|
|
var ret = null; |
202
|
|
|
divProviderResult.children('.result_entry').each(function () { |
203
|
|
|
if ($(this).attr('data-id') === resultId) { |
204
|
|
|
ret = $(this); |
205
|
|
|
} |
206
|
|
|
}); |
207
|
|
|
|
208
|
|
|
return ret; |
209
|
|
|
}, |
210
|
|
|
|
211
|
|
|
|
212
|
|
|
fillDivResult: function (divResult, entry) { |
213
|
|
|
divResult.find('#title').text(entry.title); |
214
|
|
|
|
215
|
|
|
divResult.find('#source').html(' '); |
216
|
|
|
if (entry.info.source !== '') { |
217
|
|
|
divResult.find('#source').text(entry.info.source); |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
if (settings.options.show_hash === '1') { |
221
|
|
|
divResult.find('#source').text(entry.hash); |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
nav.fillDivResultExcepts(divResult, entry); |
225
|
|
|
|
226
|
|
|
if (entry.link !== '') { |
227
|
|
|
divResult.on('click', function () { |
228
|
|
|
window.open(entry.link, '_self'); |
229
|
|
|
}); |
230
|
|
|
divResult.find('div').each(function () { |
231
|
|
|
$(this).css('cursor', 'pointer'); |
232
|
|
|
}); |
233
|
|
|
} |
234
|
|
|
}, |
235
|
|
|
|
236
|
|
|
|
237
|
|
|
/** |
238
|
|
|
* @namespace entry.excerpts |
239
|
|
|
*/ |
240
|
|
|
fillDivResultExcepts: function (divResult, entry) { |
241
|
|
|
if (entry.excerpts === null) { |
242
|
|
|
return; |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
if (entry.excerpts.length > 0) { |
246
|
|
|
divResult.find('#line1').text(entry.excerpts[0]); |
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
if (entry.excerpts.length > 1) { |
250
|
|
|
divResult.find('#line2').text(entry.excerpts[1]); |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
}, |
254
|
|
|
|
255
|
|
|
onEntryGenerated: function (divResult) { |
256
|
|
|
|
257
|
|
|
nav.deleteEmptyDiv(divResult, '#line1'); |
258
|
|
|
nav.deleteEmptyDiv(divResult, '#line2'); |
259
|
|
|
|
260
|
|
|
if (settings.parentHasMethod('onEntryGenerated')) { |
261
|
|
|
settings.parent.onEntryGenerated(divResult); |
262
|
|
|
} |
263
|
|
|
}, |
264
|
|
|
|
265
|
|
|
onSearchRequest: function (data) { |
266
|
|
|
if (settings.parentHasMethod('onSearchRequest')) { |
267
|
|
|
settings.parent.onSearchRequest(data); |
268
|
|
|
} |
269
|
|
|
}, |
270
|
|
|
|
271
|
|
|
onSearchReset: function () { |
272
|
|
|
if (settings.parentHasMethod('onSearchReset')) { |
273
|
|
|
settings.parent.onSearchReset(); |
274
|
|
|
} |
275
|
|
|
}, |
276
|
|
|
|
277
|
|
|
onResultDisplayed: function (data) { |
278
|
|
|
if (settings.parentHasMethod('onResultDisplayed')) { |
279
|
|
|
settings.parent.onResultDisplayed(data); |
280
|
|
|
} |
281
|
|
|
}, |
282
|
|
|
|
283
|
|
|
onResultClose: function () { |
284
|
|
|
if (settings.parentHasMethod('onResultClose')) { |
285
|
|
|
settings.parent.onResultClose(); |
286
|
|
|
} |
287
|
|
|
}, |
288
|
|
|
|
289
|
|
|
onError: function (data) { |
290
|
|
|
if (settings.parentHasMethod('onError')) { |
291
|
|
|
settings.parent.onError(data); |
292
|
|
|
} |
293
|
|
|
}, |
294
|
|
|
|
295
|
|
|
deleteEmptyDiv: function (entry, divId) { |
296
|
|
|
var div = entry.find(divId); |
297
|
|
|
if (div.text() === '') { |
298
|
|
|
div.remove(); |
299
|
|
|
} |
300
|
|
|
}, |
301
|
|
|
|
302
|
|
|
|
303
|
|
|
generateTemplateEntry: function (document) { |
|
|
|
|
304
|
|
|
var divTemplate = settings.entryTemplate; |
305
|
|
|
if (divTemplate === null) { |
306
|
|
|
divTemplate = settings.generateDefaultTemplate(); |
307
|
|
|
} |
308
|
|
|
|
309
|
|
|
if (!divTemplate.length) { |
310
|
|
|
console.log('FullTextSearch Error: template_entry is not defined'); |
|
|
|
|
311
|
|
|
return; |
312
|
|
|
} |
313
|
|
|
|
314
|
|
|
var tmpl = divTemplate.html(); |
315
|
|
|
// tmpl = tmpl.replace(/%%id%%/g, escapeHTML(document.id)); |
316
|
|
|
|
317
|
|
|
var div = $('<div>', {class: 'result_template'}); |
318
|
|
|
div.html(tmpl).fadeTo(0); |
319
|
|
|
|
320
|
|
|
return div; |
321
|
|
|
}, |
322
|
|
|
|
323
|
|
|
|
324
|
|
|
generateDivResult: function (entry, divResultContent) { |
325
|
|
|
var divResult = $('<div>', {class: 'result_entry'}); |
326
|
|
|
|
327
|
|
|
divResult.hide(); |
328
|
|
|
divResult.attr('data-id', entry.id); |
329
|
|
|
divResult.attr('data-link', entry.link); |
330
|
|
|
divResult.attr('data-source', entry.source); |
331
|
|
|
divResult.attr('data-info', JSON.stringify(entry.info)); |
332
|
|
|
divResult.attr('data-result', JSON.stringify(entry)); |
333
|
|
|
divResult.append(divResultContent); |
334
|
|
|
|
335
|
|
|
nav.fillDivResult(divResult, entry); |
336
|
|
|
|
337
|
|
|
return divResult; |
338
|
|
|
}, |
339
|
|
|
|
340
|
|
|
|
341
|
|
|
generateDivProvider: function (providerId, providerName) { |
342
|
|
|
|
343
|
|
|
var divProviderNavigation = $('<div>', {class: 'provider_navigation'}); |
344
|
|
|
divProviderNavigation.attr('data-provider-id', providerId); |
345
|
|
|
divProviderNavigation.attr('data-provider-title', providerName); |
346
|
|
|
divProviderNavigation.append($('<div>', {class: 'provider_navigation_left'})); |
347
|
|
|
|
348
|
|
|
var divProviderPagination = $('<div>', {class: 'provider_navigation_right'}); |
349
|
|
|
var divProviderPaginationPrev = $('<div>', {class: 'provider_navigation_prev'}).append( |
350
|
|
|
$('<div>', {class: 'provider_navigation_page'}).text('previous page')); |
351
|
|
|
|
352
|
|
|
divProviderPaginationPrev.on('click', function () { |
353
|
|
|
var prevPage = Number(divProviderNavigation.attr('data-page')) - 1; |
354
|
|
|
if (prevPage < 1) { |
355
|
|
|
return; |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
fullTextSearch.search({ |
|
|
|
|
359
|
|
|
providers: providerId, |
360
|
|
|
options: JSON.parse(divProviderNavigation.attr('data-options')), |
361
|
|
|
search: divProviderNavigation.attr('data-search'), |
362
|
|
|
page: prevPage, |
363
|
|
|
size: divProviderNavigation.attr('data-size') |
364
|
|
|
}); |
365
|
|
|
}); |
366
|
|
|
divProviderPagination.append(divProviderPaginationPrev); |
367
|
|
|
|
368
|
|
|
divProviderPagination.append($('<div>', {class: 'provider_navigation_curr'})); |
369
|
|
|
|
370
|
|
|
var divProviderPaginationNext = $('<div>', |
371
|
|
|
{class: 'provider_navigation_next'}).append( |
372
|
|
|
$('<div>', {class: 'provider_navigation_page'}).text('next page')); |
373
|
|
|
|
374
|
|
|
divProviderPaginationNext.on('click', function () { |
375
|
|
|
var nextPage = Number(divProviderNavigation.attr('data-page')) + 1; |
376
|
|
|
if (nextPage > Number(divProviderNavigation.attr('data-max-page'))) { |
377
|
|
|
return; |
378
|
|
|
} |
379
|
|
|
|
380
|
|
|
fullTextSearch.search({ |
|
|
|
|
381
|
|
|
providers: providerId, |
382
|
|
|
options: JSON.parse(divProviderNavigation.attr('data-options')), |
383
|
|
|
search: divProviderNavigation.attr('data-search'), |
384
|
|
|
page: nextPage, |
385
|
|
|
size: divProviderNavigation.attr('data-size') |
386
|
|
|
}); |
387
|
|
|
}); |
388
|
|
|
divProviderPagination.append(divProviderPaginationNext); |
389
|
|
|
|
390
|
|
|
if (settings.searchProviderId !== '') { |
391
|
|
|
var divProviderPaginationClose = $('<div>', |
392
|
|
|
{class: 'icon-close provider_navigation_close'}); |
393
|
|
|
divProviderPaginationClose.on('click', function () { |
394
|
|
|
nav.onResultClose(); |
395
|
|
|
}); |
396
|
|
|
divProviderPagination.append(divProviderPaginationClose); |
397
|
|
|
} |
398
|
|
|
|
399
|
|
|
|
400
|
|
|
divProviderNavigation.append(divProviderPagination); |
401
|
|
|
|
402
|
|
|
var divProviderResult = $('<div>', {class: 'provider_result'}); |
403
|
|
|
|
404
|
|
|
var divProvider = $('<div>', {class: 'provider_header'}); |
405
|
|
|
divProvider.hide(); |
406
|
|
|
divProvider.attr('data-id', providerId); |
407
|
|
|
divProvider.append(divProviderNavigation); |
408
|
|
|
divProvider.append(divProviderResult); |
409
|
|
|
|
410
|
|
|
return divProvider; |
411
|
|
|
} |
412
|
|
|
|
413
|
|
|
} |
414
|
|
|
; |
415
|
|
|
|
While making functions in a loop will not lead to any runtime error, the code might not behave as you expect as the variables in the scope are not imported by value, but by reference. Let’s take a look at an example:
If you would instead like to bind the function inside the loop to the value of the variable during that specific iteration, you can create the function from another function: