Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
676 lars 1
/*
2
 * searchtools.js
3
 * ~~~~~~~~~~~~~~~~
4
 *
5
 * Sphinx JavaScript utilities for the full-text search.
6
 *
7
 * :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS.
8
 * :license: BSD, see LICENSE for details.
9
 *
10
 */
11
 
12
if (!Scorer) {
13
  /**
14
   * Simple result scoring code.
15
   */
16
  var Scorer = {
17
    // Implement the following function to further tweak the score for each result
18
    // The function takes a result array [filename, title, anchor, descr, score]
19
    // and returns the new score.
20
    /*
21
    score: function(result) {
22
      return result[4];
23
    },
24
    */
25
 
26
    // query matches the full name of an object
27
    objNameMatch: 11,
28
    // or matches in the last dotted part of the object name
29
    objPartialMatch: 6,
30
    // Additive scores depending on the priority of the object
31
    objPrio: {0:  15,   // used to be importantResults
32
              1:  5,   // used to be objectResults
33
              2: -5},  // used to be unimportantResults
34
    //  Used when the priority is not in the mapping.
35
    objPrioDefault: 0,
36
 
37
    // query found in title
38
    title: 15,
39
    partialTitle: 7,
40
    // query found in terms
41
    term: 5,
42
    partialTerm: 2
43
  };
44
}
45
 
46
if (!splitQuery) {
47
  function splitQuery(query) {
48
    return query.split(/\s+/);
49
  }
50
}
51
 
52
/**
53
 * Search Module
54
 */
55
var Search = {
56
 
57
  _index : null,
58
  _queued_query : null,
59
  _pulse_status : -1,
60
 
61
  htmlToText : function(htmlString) {
62
      var htmlElement = document.createElement('span');
63
      htmlElement.innerHTML = htmlString;
64
      $(htmlElement).find('.headerlink').remove();
65
      docContent = $(htmlElement).find('[role=main]')[0];
66
      if(docContent === undefined) {
67
          console.warn("Content block not found. Sphinx search tries to obtain it " +
68
                       "via '[role=main]'. Could you check your theme or template.");
69
          return "";
70
      }
71
      return docContent.textContent || docContent.innerText;
72
  },
73
 
74
  init : function() {
75
      var params = $.getQueryParameters();
76
      if (params.q) {
77
          var query = params.q[0];
78
          $('input[name="q"]')[0].value = query;
79
          this.performSearch(query);
80
      }
81
  },
82
 
83
  loadIndex : function(url) {
84
    $.ajax({type: "GET", url: url, data: null,
85
            dataType: "script", cache: true,
86
            complete: function(jqxhr, textstatus) {
87
              if (textstatus != "success") {
88
                document.getElementById("searchindexloader").src = url;
89
              }
90
            }});
91
  },
92
 
93
  setIndex : function(index) {
94
    var q;
95
    this._index = index;
96
    if ((q = this._queued_query) !== null) {
97
      this._queued_query = null;
98
      Search.query(q);
99
    }
100
  },
101
 
102
  hasIndex : function() {
103
      return this._index !== null;
104
  },
105
 
106
  deferQuery : function(query) {
107
      this._queued_query = query;
108
  },
109
 
110
  stopPulse : function() {
111
      this._pulse_status = 0;
112
  },
113
 
114
  startPulse : function() {
115
    if (this._pulse_status >= 0)
116
        return;
117
    function pulse() {
118
      var i;
119
      Search._pulse_status = (Search._pulse_status + 1) % 4;
120
      var dotString = '';
121
      for (i = 0; i < Search._pulse_status; i++)
122
        dotString += '.';
123
      Search.dots.text(dotString);
124
      if (Search._pulse_status > -1)
125
        window.setTimeout(pulse, 500);
126
    }
127
    pulse();
128
  },
129
 
130
  /**
131
   * perform a search for something (or wait until index is loaded)
132
   */
133
  performSearch : function(query) {
134
    // create the required interface elements
135
    this.out = $('#search-results');
136
    this.title = $('<h2>' + _('Searching') + '</h2>').appendTo(this.out);
137
    this.dots = $('<span></span>').appendTo(this.title);
138
    this.status = $('<p class="search-summary">&nbsp;</p>').appendTo(this.out);
139
    this.output = $('<ul class="search"/>').appendTo(this.out);
140
 
141
    $('#search-progress').text(_('Preparing search...'));
142
    this.startPulse();
143
 
144
    // index already loaded, the browser was quick!
145
    if (this.hasIndex())
146
      this.query(query);
147
    else
148
      this.deferQuery(query);
149
  },
150
 
151
  /**
152
   * execute search (requires search index to be loaded)
153
   */
154
  query : function(query) {
155
    var i;
156
 
157
    // stem the searchterms and add them to the correct list
158
    var stemmer = new Stemmer();
159
    var searchterms = [];
160
    var excluded = [];
161
    var hlterms = [];
162
    var tmp = splitQuery(query);
163
    var objectterms = [];
164
    for (i = 0; i < tmp.length; i++) {
165
      if (tmp[i] !== "") {
166
          objectterms.push(tmp[i].toLowerCase());
167
      }
168
 
169
      if ($u.indexOf(stopwords, tmp[i].toLowerCase()) != -1 || tmp[i] === "") {
170
        // skip this "word"
171
        continue;
172
      }
173
      // stem the word
174
      var word = stemmer.stemWord(tmp[i].toLowerCase());
175
      // prevent stemmer from cutting word smaller than two chars
176
      if(word.length < 3 && tmp[i].length >= 3) {
177
        word = tmp[i];
178
      }
179
      var toAppend;
180
      // select the correct list
181
      if (word[0] == '-') {
182
        toAppend = excluded;
183
        word = word.substr(1);
184
      }
185
      else {
186
        toAppend = searchterms;
187
        hlterms.push(tmp[i].toLowerCase());
188
      }
189
      // only add if not already in the list
190
      if (!$u.contains(toAppend, word))
191
        toAppend.push(word);
192
    }
193
    var highlightstring = '?highlight=' + $.urlencode(hlterms.join(" "));
194
 
195
    // console.debug('SEARCH: searching for:');
196
    // console.info('required: ', searchterms);
197
    // console.info('excluded: ', excluded);
198
 
199
    // prepare search
200
    var terms = this._index.terms;
201
    var titleterms = this._index.titleterms;
202
 
203
    // array of [filename, title, anchor, descr, score]
204
    var results = [];
205
    $('#search-progress').empty();
206
 
207
    // lookup as object
208
    for (i = 0; i < objectterms.length; i++) {
209
      var others = [].concat(objectterms.slice(0, i),
210
                             objectterms.slice(i+1, objectterms.length));
211
      results = results.concat(this.performObjectSearch(objectterms[i], others));
212
    }
213
 
214
    // lookup as search terms in fulltext
215
    results = results.concat(this.performTermsSearch(searchterms, excluded, terms, titleterms));
216
 
217
    // let the scorer override scores with a custom scoring function
218
    if (Scorer.score) {
219
      for (i = 0; i < results.length; i++)
220
        results[i][4] = Scorer.score(results[i]);
221
    }
222
 
223
    // now sort the results by score (in opposite order of appearance, since the
224
    // display function below uses pop() to retrieve items) and then
225
    // alphabetically
226
    results.sort(function(a, b) {
227
      var left = a[4];
228
      var right = b[4];
229
      if (left > right) {
230
        return 1;
231
      } else if (left < right) {
232
        return -1;
233
      } else {
234
        // same score: sort alphabetically
235
        left = a[1].toLowerCase();
236
        right = b[1].toLowerCase();
237
        return (left > right) ? -1 : ((left < right) ? 1 : 0);
238
      }
239
    });
240
 
241
    // for debugging
242
    //Search.lastresults = results.slice();  // a copy
243
    //console.info('search results:', Search.lastresults);
244
 
245
    // print the results
246
    var resultCount = results.length;
247
    function displayNextItem() {
248
      // results left, load the summary and display it
249
      if (results.length) {
250
        var item = results.pop();
251
        var listItem = $('<li style="display:none"></li>');
252
        var requestUrl = "";
253
        var linkUrl = "";
254
        if (DOCUMENTATION_OPTIONS.BUILDER === 'dirhtml') {
255
          // dirhtml builder
256
          var dirname = item[0] + '/';
257
          if (dirname.match(/\/index\/$/)) {
258
            dirname = dirname.substring(0, dirname.length-6);
259
          } else if (dirname == 'index/') {
260
            dirname = '';
261
          }
262
          requestUrl = DOCUMENTATION_OPTIONS.URL_ROOT + dirname;
263
          linkUrl = requestUrl;
264
 
265
        } else {
266
          // normal html builders
267
          requestUrl = DOCUMENTATION_OPTIONS.URL_ROOT + item[0] + DOCUMENTATION_OPTIONS.FILE_SUFFIX;
268
          linkUrl = item[0] + DOCUMENTATION_OPTIONS.LINK_SUFFIX;
269
        }
270
        listItem.append($('<a/>').attr('href',
271
            linkUrl +
272
            highlightstring + item[2]).html(item[1]));
273
        if (item[3]) {
274
          listItem.append($('<span> (' + item[3] + ')</span>'));
275
          Search.output.append(listItem);
276
          listItem.slideDown(5, function() {
277
            displayNextItem();
278
          });
279
        } else if (DOCUMENTATION_OPTIONS.HAS_SOURCE) {
280
          $.ajax({url: requestUrl,
281
                  dataType: "text",
282
                  complete: function(jqxhr, textstatus) {
283
                    var data = jqxhr.responseText;
284
                    if (data !== '' && data !== undefined) {
285
                      listItem.append(Search.makeSearchSummary(data, searchterms, hlterms));
286
                    }
287
                    Search.output.append(listItem);
288
                    listItem.slideDown(5, function() {
289
                      displayNextItem();
290
                    });
291
                  }});
292
        } else {
293
          // no source available, just display title
294
          Search.output.append(listItem);
295
          listItem.slideDown(5, function() {
296
            displayNextItem();
297
          });
298
        }
299
      }
300
      // search finished, update title and status message
301
      else {
302
        Search.stopPulse();
303
        Search.title.text(_('Search Results'));
304
        if (!resultCount)
305
          Search.status.text(_('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.'));
306
        else
307
            Search.status.text(_('Search finished, found %s page(s) matching the search query.').replace('%s', resultCount));
308
        Search.status.fadeIn(500);
309
      }
310
    }
311
    displayNextItem();
312
  },
313
 
314
  /**
315
   * search for object names
316
   */
317
  performObjectSearch : function(object, otherterms) {
318
    var filenames = this._index.filenames;
319
    var docnames = this._index.docnames;
320
    var objects = this._index.objects;
321
    var objnames = this._index.objnames;
322
    var titles = this._index.titles;
323
 
324
    var i;
325
    var results = [];
326
 
327
    for (var prefix in objects) {
328
      for (var name in objects[prefix]) {
329
        var fullname = (prefix ? prefix + '.' : '') + name;
330
        var fullnameLower = fullname.toLowerCase()
331
        if (fullnameLower.indexOf(object) > -1) {
332
          var score = 0;
333
          var parts = fullnameLower.split('.');
334
          // check for different match types: exact matches of full name or
335
          // "last name" (i.e. last dotted part)
336
          if (fullnameLower == object || parts[parts.length - 1] == object) {
337
            score += Scorer.objNameMatch;
338
          // matches in last name
339
          } else if (parts[parts.length - 1].indexOf(object) > -1) {
340
            score += Scorer.objPartialMatch;
341
          }
342
          var match = objects[prefix][name];
343
          var objname = objnames[match[1]][2];
344
          var title = titles[match[0]];
345
          // If more than one term searched for, we require other words to be
346
          // found in the name/title/description
347
          if (otherterms.length > 0) {
348
            var haystack = (prefix + ' ' + name + ' ' +
349
                            objname + ' ' + title).toLowerCase();
350
            var allfound = true;
351
            for (i = 0; i < otherterms.length; i++) {
352
              if (haystack.indexOf(otherterms[i]) == -1) {
353
                allfound = false;
354
                break;
355
              }
356
            }
357
            if (!allfound) {
358
              continue;
359
            }
360
          }
361
          var descr = objname + _(', in ') + title;
362
 
363
          var anchor = match[3];
364
          if (anchor === '')
365
            anchor = fullname;
366
          else if (anchor == '-')
367
            anchor = objnames[match[1]][1] + '-' + fullname;
368
          // add custom score for some objects according to scorer
369
          if (Scorer.objPrio.hasOwnProperty(match[2])) {
370
            score += Scorer.objPrio[match[2]];
371
          } else {
372
            score += Scorer.objPrioDefault;
373
          }
374
          results.push([docnames[match[0]], fullname, '#'+anchor, descr, score, filenames[match[0]]]);
375
        }
376
      }
377
    }
378
 
379
    return results;
380
  },
381
 
382
  /**
383
   * search for full-text terms in the index
384
   */
385
  performTermsSearch : function(searchterms, excluded, terms, titleterms) {
386
    var docnames = this._index.docnames;
387
    var filenames = this._index.filenames;
388
    var titles = this._index.titles;
389
 
390
    var i, j, file;
391
    var fileMap = {};
392
    var scoreMap = {};
393
    var results = [];
394
 
395
    // perform the search on the required terms
396
    for (i = 0; i < searchterms.length; i++) {
397
      var word = searchterms[i];
398
      var files = [];
399
      var _o = [
400
        {files: terms[word], score: Scorer.term},
401
        {files: titleterms[word], score: Scorer.title}
402
      ];
403
      // add support for partial matches
404
      if (word.length > 2) {
405
        for (var w in terms) {
406
          if (w.match(word) && !terms[word]) {
407
            _o.push({files: terms[w], score: Scorer.partialTerm})
408
          }
409
        }
410
        for (var w in titleterms) {
411
          if (w.match(word) && !titleterms[word]) {
412
              _o.push({files: titleterms[w], score: Scorer.partialTitle})
413
          }
414
        }
415
      }
416
 
417
      // no match but word was a required one
418
      if ($u.every(_o, function(o){return o.files === undefined;})) {
419
        break;
420
      }
421
      // found search word in contents
422
      $u.each(_o, function(o) {
423
        var _files = o.files;
424
        if (_files === undefined)
425
          return
426
 
427
        if (_files.length === undefined)
428
          _files = [_files];
429
        files = files.concat(_files);
430
 
431
        // set score for the word in each file to Scorer.term
432
        for (j = 0; j < _files.length; j++) {
433
          file = _files[j];
434
          if (!(file in scoreMap))
435
            scoreMap[file] = {};
436
          scoreMap[file][word] = o.score;
437
        }
438
      });
439
 
440
      // create the mapping
441
      for (j = 0; j < files.length; j++) {
442
        file = files[j];
443
        if (file in fileMap && fileMap[file].indexOf(word) === -1)
444
          fileMap[file].push(word);
445
        else
446
          fileMap[file] = [word];
447
      }
448
    }
449
 
450
    // now check if the files don't contain excluded terms
451
    for (file in fileMap) {
452
      var valid = true;
453
 
454
      // check if all requirements are matched
455
      var filteredTermCount = // as search terms with length < 3 are discarded: ignore
456
        searchterms.filter(function(term){return term.length > 2}).length
457
      if (
458
        fileMap[file].length != searchterms.length &&
459
        fileMap[file].length != filteredTermCount
460
      ) continue;
461
 
462
      // ensure that none of the excluded terms is in the search result
463
      for (i = 0; i < excluded.length; i++) {
464
        if (terms[excluded[i]] == file ||
465
            titleterms[excluded[i]] == file ||
466
            $u.contains(terms[excluded[i]] || [], file) ||
467
            $u.contains(titleterms[excluded[i]] || [], file)) {
468
          valid = false;
469
          break;
470
        }
471
      }
472
 
473
      // if we have still a valid result we can add it to the result list
474
      if (valid) {
475
        // select one (max) score for the file.
476
        // for better ranking, we should calculate ranking by using words statistics like basic tf-idf...
477
        var score = $u.max($u.map(fileMap[file], function(w){return scoreMap[file][w]}));
478
        results.push([docnames[file], titles[file], '', null, score, filenames[file]]);
479
      }
480
    }
481
    return results;
482
  },
483
 
484
  /**
485
   * helper function to return a node containing the
486
   * search summary for a given text. keywords is a list
487
   * of stemmed words, hlwords is the list of normal, unstemmed
488
   * words. the first one is used to find the occurrence, the
489
   * latter for highlighting it.
490
   */
491
  makeSearchSummary : function(htmlText, keywords, hlwords) {
492
    var text = Search.htmlToText(htmlText);
493
    var textLower = text.toLowerCase();
494
    var start = 0;
495
    $.each(keywords, function() {
496
      var i = textLower.indexOf(this.toLowerCase());
497
      if (i > -1)
498
        start = i;
499
    });
500
    start = Math.max(start - 120, 0);
501
    var excerpt = ((start > 0) ? '...' : '') +
502
      $.trim(text.substr(start, 240)) +
503
      ((start + 240 - text.length) ? '...' : '');
504
    var rv = $('<div class="context"></div>').text(excerpt);
505
    $.each(hlwords, function() {
506
      rv = rv.highlightText(this, 'highlighted');
507
    });
508
    return rv;
509
  }
510
};
511
 
512
$(document).ready(function() {
513
  Search.init();
514
});