@@ -24,6 +24,7 @@ var Doctum = {
2424 doctumSearchAutoCompleteProgressBarPercent: 0,
2525 /** @var autoComplete|null */
2626 autoCompleteJS: null,
27+ querySearchSecurityRegex: /([^a-zA-Z:\\\\_\s])/gi,
2728 buildTreeNode: function (treeNode, htmlNode, treeOpenLevel) {
2829 var ulNode = document.createElement('ul');
2930 for (var childKey in treeNode.c) {
@@ -151,41 +152,65 @@ var Doctum = {
151152 document.getElementById('search-form').submit();
152153 });
153154 Doctum.doctumSearchAutoComplete.addEventListener('navigate', function (event) {
155+ Doctum.markInProgress();
154156 // Set selection in text box
155157 if (typeof event.detail.selection.value === 'object') {
156158 Doctum.doctumSearchAutoComplete.value = event.detail.selection.value.n;
157159 }
158160 });
161+ Doctum.doctumSearchAutoComplete.addEventListener('results', function (event) {
162+ Doctum.markProgressFinished();
163+ });
159164 });
160165 }
161166 // Check if the lib is loaded
162167 if (typeof autoComplete === 'function') {
163168 Doctum.bootAutoComplete();
164169 }
165170 },
166- loadAutoCompleteData: function (query) {
167- return new Promise(function (resolve, reject) {
168- if (Doctum.autoCompleteData !== null) {
169- resolve(Doctum.autoCompleteData);
170- return;
171- }
171+ markInProgress: function () {
172172 Doctum.doctumSearchAutoCompleteProgressBarContainer.className = 'search-bar';
173173 Doctum.doctumSearchAutoCompleteProgressBar.className = 'progress-bar indeterminate';
174174 if (typeof DoctumSearch === 'object' && DoctumSearch.pageFullyLoaded) {
175175 DoctumSearch.doctumSearchPageAutoCompleteProgressBarContainer.className = 'search-bar';
176176 DoctumSearch.doctumSearchPageAutoCompleteProgressBar.className = 'progress-bar indeterminate';
177177 }
178+ },
179+ markProgressFinished: function () {
180+ Doctum.doctumSearchAutoCompleteProgressBarContainer.className = 'search-bar hidden';
181+ Doctum.doctumSearchAutoCompleteProgressBar.className = 'progress-bar';
182+ if (typeof DoctumSearch === 'object' && DoctumSearch.pageFullyLoaded) {
183+ DoctumSearch.doctumSearchPageAutoCompleteProgressBarContainer.className = 'search-bar hidden';
184+ DoctumSearch.doctumSearchPageAutoCompleteProgressBar.className = 'progress-bar';
185+ }
186+ },
187+ makeProgess: function () {
188+ Doctum.makeProgressOnProgressBar(
189+ Doctum.doctumSearchAutoCompleteProgressBarPercent,
190+ Doctum.doctumSearchAutoCompleteProgressBar
191+ );
192+ if (typeof DoctumSearch === 'object' && DoctumSearch.pageFullyLoaded) {
193+ Doctum.makeProgressOnProgressBar(
194+ Doctum.doctumSearchAutoCompleteProgressBarPercent,
195+ DoctumSearch.doctumSearchPageAutoCompleteProgressBar
196+ );
197+ }
198+ },
199+ loadAutoCompleteData: function (query) {
200+ return new Promise(function (resolve, reject) {
201+ if (Doctum.autoCompleteData !== null) {
202+ resolve(Doctum.autoCompleteData);
203+ return;
204+ }
205+ Doctum.markInProgress();
178206 function reqListener() {
179207 Doctum.autoCompleteLoading = false;
180208 Doctum.autoCompleteData = JSON.parse(this.responseText).items;
181- Doctum.doctumSearchAutoCompleteProgressBarContainer.className = 'search-bar hidden';
182- Doctum.doctumSearchAutoCompleteProgressBar.className = 'progress-bar';
183- if (typeof DoctumSearch === 'object' && DoctumSearch.pageFullyLoaded) {
184- DoctumSearch.doctumSearchPageAutoCompleteProgressBarContainer.className = 'search-bar hidden';
185- DoctumSearch.doctumSearchPageAutoCompleteProgressBar.className = 'progress-bar';
186- }
209+ Doctum.markProgressFinished();
187210
188- resolve(Doctum.autoCompleteData);
211+ setTimeout(function () {
212+ resolve(Doctum.autoCompleteData);
213+ }, 50);// Let the UI render once before sending the results for processing. This gives time to the progress bar to hide
189214 }
190215 function reqError(err) {
191216 Doctum.autoCompleteLoading = false;
@@ -200,26 +225,12 @@ var Doctum = {
200225 oReq.onprogress = function (pe) {
201226 if (pe.lengthComputable) {
202227 Doctum.doctumSearchAutoCompleteProgressBarPercent = parseInt(pe.loaded / pe.total * 100, 10);
203- Doctum.makeProgressOnProgressBar(
204- Doctum.doctumSearchAutoCompleteProgressBarPercent,
205- Doctum.doctumSearchAutoCompleteProgressBar
206- );
207- if (typeof DoctumSearch === 'object' && DoctumSearch.pageFullyLoaded) {
208- Doctum.makeProgressOnProgressBar(
209- Doctum.doctumSearchAutoCompleteProgressBarPercent,
210- DoctumSearch.doctumSearchPageAutoCompleteProgressBar
211- );
212- }
228+ Doctum.makeProgess();
213229 }
214- }
230+ };
215231 oReq.onloadend = function (_) {
216- Doctum.doctumSearchAutoCompleteProgressBarContainer.className = 'search-bar hidden';
217- Doctum.doctumSearchAutoCompleteProgressBar.className = 'progress-bar';
218- if (typeof DoctumSearch === 'object' && DoctumSearch.pageFullyLoaded) {
219- DoctumSearch.doctumSearchPageAutoCompleteProgressBarContainer.className = 'search-bar hidden';
220- DoctumSearch.doctumSearchPageAutoCompleteProgressBar.className = 'progress-bar';
221- }
222- }
232+ Doctum.markProgressFinished();
233+ };
223234 oReq.open('get', Doctum.autoCompleteDataUrl, true);
224235 oReq.send();
225236 });
@@ -237,27 +248,78 @@ var Doctum = {
237248 progressBar.setAttribute(
238249 'aria-valuenow', percentage
239250 );
251+ },
252+ searchEngine: function (query, record) {
253+ if (typeof query !== 'string') {
254+ return '';
255+ }
256+ // replace all (mode = g) spaces and non breaking spaces (\s) by pipes
257+ // g = global mode to mark also the second word searched
258+ // i = case insensitive
259+ // how this function works:
260+ // First: search if the query has the keywords in sequence
261+ // Second: replace the keywords by a mark and leave all the text in between non marked
262+ {% endverbatim -%}
263+ {#
264+ Case 1: search for "net sample"
265+ Data: net_sample
266+ Result <mark>net</mark>_<mark>sample</mark>
267+ Case 1: search for "n t sa"
268+ Data: net_sample, ample, glamples, notDateSa
269+ Result <mark>n</mark>e<mark>t</mark>_<mark>sa</mark>mple, <mark>n</mark>o<mark>t</mark>Da<mark>t</mark>e<mark>Sa</mark>
270+ #}
271+ {% verbatim %}
272+ if (record.match(new RegExp('(' + query.replace(/\s/g, ').*(') + ')', 'gi')) === null) {
273+ return '';// Does not match
274+ }
240275
276+ var replacedRecord = record.replace(new RegExp('(' + query.replace(/\s/g, '|') + ')', 'gi'), function (group) {
277+ return '<mark class =" auto-complete-highlight" >' + group + '</mark >';
278+ });
279+
280+ if (replacedRecord !== record) {
281+ return replacedRecord;// This should not happen but just in case there was no match done
282+ }
283+
284+ return '';
285+ },
286+ /**
287+ * Clean the search query
288+ *
289+ * @param string query
290+ * @return string
291+ */
292+ cleanSearchQuery: function (query) {
293+ // replace any chars that could lead to injecting code in our regex
294+ // remove start or end spaces
295+ // replace backslashes by an escaped version, use case in search: \myRootFunction
296+ return query.replace(Doctum.querySearchSecurityRegex, '').trim().replace(/\\/g, '\\\\');
241297 },
242298 bootAutoComplete: function () {
243299 Doctum.autoCompleteJS = new autoComplete(
244300 {
245301 selector: '#doctum-search-auto-complete',
246- searchEngine: 'loose',
302+ searchEngine: function (query, record) {
303+ return Doctum.searchEngine(query, record);
304+ },
247305 submit: true,
248306 data: {
249307 src: function (q) {
308+ Doctum.markInProgress();
250309 return Doctum.loadAutoCompleteData(q);
251310 },
252311 keys: ['n'],// Data 'Object' key to be searched
253312 cache: false, // Is not compatible with async fetch of data
254313 },
314+ query: (input) => {
315+ return Doctum.cleanSearchQuery(input);
316+ },
255317 resultsList: {
256318 tag: 'ul',
257319 class: 'auto-complete-dropdown-menu',
258320 destination: '#auto-complete-results',
259321 position: 'afterbegin',
260- maxResults: 99999 ,
322+ maxResults: 500 ,
261323 noResults: false,
262324 },
263325 resultItem: {
0 commit comments