appscms-tools-theme 0.3.8 → 0.3.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ce1f4bee05af1dd3aab66e67492fbc83e02f92da8e23ea7169cbd4aa66d455be
4
- data.tar.gz: 208f6d3d2b4cbc4ef7ebe6fec05dfd62a30158b3f2316bae5e8efa16f5dc00f1
3
+ metadata.gz: 88cfba0cb62bde397a590bec1d34180450bfee4371da071422300d200521bc81
4
+ data.tar.gz: db91692d2dccdfb2effa1c2d688e0940ee288ed39adbf9c304e355d286a4fa9a
5
5
  SHA512:
6
- metadata.gz: '0097552704298cd533de70b7e2c130b5815578c7a7c84a88f3568a2964f45630e8b98e4df7d6b489c81a3dd73697d7205a81641ce9abbc5b1c6d61bd11fd0334'
7
- data.tar.gz: 23aeb0752abebd21d579e00649369fb65f99270aba9777e9f078f493e059b6e303e277381cb45fbc32e459dd99a7fa863f14bd997c91945b219c32ea0f38f961
6
+ metadata.gz: d9d274868df1619fbe0d967f1b13f9425723a94443f1bc5fc4e57ad299441c6492b95d9522be705898414be7bcb2e66d6fb35ccc3c1fe67fd2c179af4b12a345
7
+ data.tar.gz: 88a30f43ad42e2b92e8c74d63c85f868ae97a6a0805559f398f5ab698dd90e6e96822d18586aad25e05f1a3bdac072e325335a2a9727c779cb8000652a9cf510
@@ -30,4 +30,5 @@
30
30
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.0/css/all.min.css" />
31
31
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css">
32
32
  <link rel="stylesheet" href="{{'/assets/css/blog.css' | relative_url }}">
33
+ <script src="https://code.jquery.com/jquery-3.5.1.js"></script>
33
34
  </head>
@@ -24,6 +24,9 @@
24
24
  <a class="nav-link" href="/{{item.url}}">{{item.name}}</a>
25
25
  </li>
26
26
  {% endfor %}
27
+ <li>
28
+ {% include search-lunr.html %}
29
+ </li>
27
30
  </ul>
28
31
  </div>
29
32
  </nav>
@@ -0,0 +1,16 @@
1
+ <form class="bd-search m-0" onSubmit="return lunr_search(document.getElementById('lunrsearch').value);">
2
+ <input type="text" class="form-control text-small launch-modal-search py-4" id="lunrsearch" name="q" maxlength="255"
3
+ value="" placeholder="Type and enter..." style="border-radius: 50px; font-size: 12px;" />
4
+ </form>
5
+
6
+ <div id="lunrsearchresults">
7
+ <ul></ul>
8
+ </div>
9
+
10
+ <script src=" {{site.url}}/assets/js/lunr.js"></script>
11
+ <script src="{{site.url}}/assets/js/lunrsearchengine.js"></script>
12
+
13
+
14
+ <script>
15
+
16
+ </script>
data/assets/css/blog.css CHANGED
@@ -38,6 +38,9 @@ header {
38
38
  left: 0;
39
39
  z-index: 999;
40
40
  }
41
+ .navbar-nav {
42
+ align-items: center;
43
+ }
41
44
  .navbar-brand {
42
45
  font-size: 2.2rem;
43
46
  color: var(--txtColor) !important;
@@ -365,8 +368,57 @@ header {
365
368
  text-decoration: none;
366
369
  color: green;
367
370
  }
371
+ .lunrsearchresult {
372
+ font-size: 15px;
373
+ }
374
+ .lunrsearchresult .title {
375
+ color: #d9230f;
376
+ }
377
+ .lunrsearchresult .url {
378
+ color: silver;
379
+ }
380
+ .lunrsearchresult a {
381
+ display: block;
382
+ color: #777;
383
+ }
384
+
385
+ .lunrsearchresult:hover,
386
+ .lunrsearchresult:focus {
387
+ text-decoration: none;
388
+ }
389
+
390
+ .lunrsearchresult:hover .title {
391
+ text-decoration: underline;
392
+ }
393
+
394
+ .close {
395
+ float: right;
396
+ font-size: 2rem;
397
+ font-weight: 700;
398
+ line-height: 1;
399
+ color: #000;
400
+ text-shadow: 0 1px 0 #fff;
401
+ opacity: 0.5;
402
+ border: none;
403
+ background: transparent;
404
+ }
405
+ .close:focus {
406
+ outline: none !important;
407
+ }
368
408
 
369
409
  @media (max-width: 768px) {
410
+ .bd-search {
411
+ padding: 10px 25px;
412
+ }
413
+ }
414
+
415
+ @media (max-width: 768px) {
416
+ .navbar-nav {
417
+ align-items: unset;
418
+ }
419
+ .nav-item {
420
+ padding: 0 !important;
421
+ }
370
422
  .socialIons > .item-lists2 {
371
423
  flex-direction: row;
372
424
  align-items: center;
data/assets/js/lunr.js ADDED
@@ -0,0 +1,2977 @@
1
+ /**
2
+ * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.1.5
3
+ * Copyright (C) 2017 Oliver Nightingale
4
+ * @license MIT
5
+ */
6
+
7
+ ;(function(){
8
+
9
+ /**
10
+ * A convenience function for configuring and constructing
11
+ * a new lunr Index.
12
+ *
13
+ * A lunr.Builder instance is created and the pipeline setup
14
+ * with a trimmer, stop word filter and stemmer.
15
+ *
16
+ * This builder object is yielded to the configuration function
17
+ * that is passed as a parameter, allowing the list of fields
18
+ * and other builder parameters to be customised.
19
+ *
20
+ * All documents _must_ be added within the passed config function.
21
+ *
22
+ * @example
23
+ * var idx = lunr(function () {
24
+ * this.field('title')
25
+ * this.field('body')
26
+ * this.ref('id')
27
+ *
28
+ * documents.forEach(function (doc) {
29
+ * this.add(doc)
30
+ * }, this)
31
+ * })
32
+ *
33
+ * @see {@link lunr.Builder}
34
+ * @see {@link lunr.Pipeline}
35
+ * @see {@link lunr.trimmer}
36
+ * @see {@link lunr.stopWordFilter}
37
+ * @see {@link lunr.stemmer}
38
+ * @namespace {function} lunr
39
+ */
40
+ var lunr = function (config) {
41
+ var builder = new lunr.Builder
42
+
43
+ builder.pipeline.add(
44
+ lunr.trimmer,
45
+ lunr.stopWordFilter,
46
+ lunr.stemmer
47
+ )
48
+
49
+ builder.searchPipeline.add(
50
+ lunr.stemmer
51
+ )
52
+
53
+ config.call(builder, builder)
54
+ return builder.build()
55
+ }
56
+
57
+ lunr.version = "2.1.5"
58
+ /*!
59
+ * lunr.utils
60
+ * Copyright (C) 2017 Oliver Nightingale
61
+ */
62
+
63
+ /**
64
+ * A namespace containing utils for the rest of the lunr library
65
+ */
66
+ lunr.utils = {}
67
+
68
+ /**
69
+ * Print a warning message to the console.
70
+ *
71
+ * @param {String} message The message to be printed.
72
+ * @memberOf Utils
73
+ */
74
+ lunr.utils.warn = (function (global) {
75
+ /* eslint-disable no-console */
76
+ return function (message) {
77
+ if (global.console && console.warn) {
78
+ console.warn(message)
79
+ }
80
+ }
81
+ /* eslint-enable no-console */
82
+ })(this)
83
+
84
+ /**
85
+ * Convert an object to a string.
86
+ *
87
+ * In the case of `null` and `undefined` the function returns
88
+ * the empty string, in all other cases the result of calling
89
+ * `toString` on the passed object is returned.
90
+ *
91
+ * @param {Any} obj The object to convert to a string.
92
+ * @return {String} string representation of the passed object.
93
+ * @memberOf Utils
94
+ */
95
+ lunr.utils.asString = function (obj) {
96
+ if (obj === void 0 || obj === null) {
97
+ return ""
98
+ } else {
99
+ return obj.toString()
100
+ }
101
+ }
102
+ lunr.FieldRef = function (docRef, fieldName, stringValue) {
103
+ this.docRef = docRef
104
+ this.fieldName = fieldName
105
+ this._stringValue = stringValue
106
+ }
107
+
108
+ lunr.FieldRef.joiner = "/"
109
+
110
+ lunr.FieldRef.fromString = function (s) {
111
+ var n = s.indexOf(lunr.FieldRef.joiner)
112
+
113
+ if (n === -1) {
114
+ throw "malformed field ref string"
115
+ }
116
+
117
+ var fieldRef = s.slice(0, n),
118
+ docRef = s.slice(n + 1)
119
+
120
+ return new lunr.FieldRef (docRef, fieldRef, s)
121
+ }
122
+
123
+ lunr.FieldRef.prototype.toString = function () {
124
+ if (this._stringValue == undefined) {
125
+ this._stringValue = this.fieldName + lunr.FieldRef.joiner + this.docRef
126
+ }
127
+
128
+ return this._stringValue
129
+ }
130
+ /**
131
+ * A function to calculate the inverse document frequency for
132
+ * a posting. This is shared between the builder and the index
133
+ *
134
+ * @private
135
+ * @param {object} posting - The posting for a given term
136
+ * @param {number} documentCount - The total number of documents.
137
+ */
138
+ lunr.idf = function (posting, documentCount) {
139
+ var documentsWithTerm = 0
140
+
141
+ for (var fieldName in posting) {
142
+ if (fieldName == '_index') continue // Ignore the term index, its not a field
143
+ documentsWithTerm += Object.keys(posting[fieldName]).length
144
+ }
145
+
146
+ var x = (documentCount - documentsWithTerm + 0.5) / (documentsWithTerm + 0.5)
147
+
148
+ return Math.log(1 + Math.abs(x))
149
+ }
150
+
151
+ /**
152
+ * A token wraps a string representation of a token
153
+ * as it is passed through the text processing pipeline.
154
+ *
155
+ * @constructor
156
+ * @param {string} [str=''] - The string token being wrapped.
157
+ * @param {object} [metadata={}] - Metadata associated with this token.
158
+ */
159
+ lunr.Token = function (str, metadata) {
160
+ this.str = str || ""
161
+ this.metadata = metadata || {}
162
+ }
163
+
164
+ /**
165
+ * Returns the token string that is being wrapped by this object.
166
+ *
167
+ * @returns {string}
168
+ */
169
+ lunr.Token.prototype.toString = function () {
170
+ return this.str
171
+ }
172
+
173
+ /**
174
+ * A token update function is used when updating or optionally
175
+ * when cloning a token.
176
+ *
177
+ * @callback lunr.Token~updateFunction
178
+ * @param {string} str - The string representation of the token.
179
+ * @param {Object} metadata - All metadata associated with this token.
180
+ */
181
+
182
+ /**
183
+ * Applies the given function to the wrapped string token.
184
+ *
185
+ * @example
186
+ * token.update(function (str, metadata) {
187
+ * return str.toUpperCase()
188
+ * })
189
+ *
190
+ * @param {lunr.Token~updateFunction} fn - A function to apply to the token string.
191
+ * @returns {lunr.Token}
192
+ */
193
+ lunr.Token.prototype.update = function (fn) {
194
+ this.str = fn(this.str, this.metadata)
195
+ return this
196
+ }
197
+
198
+ /**
199
+ * Creates a clone of this token. Optionally a function can be
200
+ * applied to the cloned token.
201
+ *
202
+ * @param {lunr.Token~updateFunction} [fn] - An optional function to apply to the cloned token.
203
+ * @returns {lunr.Token}
204
+ */
205
+ lunr.Token.prototype.clone = function (fn) {
206
+ fn = fn || function (s) { return s }
207
+ return new lunr.Token (fn(this.str, this.metadata), this.metadata)
208
+ }
209
+ /*!
210
+ * lunr.tokenizer
211
+ * Copyright (C) 2017 Oliver Nightingale
212
+ */
213
+
214
+ /**
215
+ * A function for splitting a string into tokens ready to be inserted into
216
+ * the search index. Uses `lunr.tokenizer.separator` to split strings, change
217
+ * the value of this property to change how strings are split into tokens.
218
+ *
219
+ * This tokenizer will convert its parameter to a string by calling `toString` and
220
+ * then will split this string on the character in `lunr.tokenizer.separator`.
221
+ * Arrays will have their elements converted to strings and wrapped in a lunr.Token.
222
+ *
223
+ * @static
224
+ * @param {?(string|object|object[])} obj - The object to convert into tokens
225
+ * @returns {lunr.Token[]}
226
+ */
227
+ lunr.tokenizer = function (obj) {
228
+ if (obj == null || obj == undefined) {
229
+ return []
230
+ }
231
+
232
+ if (Array.isArray(obj)) {
233
+ return obj.map(function (t) {
234
+ return new lunr.Token(lunr.utils.asString(t).toLowerCase())
235
+ })
236
+ }
237
+
238
+ var str = obj.toString().trim().toLowerCase(),
239
+ len = str.length,
240
+ tokens = []
241
+
242
+ for (var sliceEnd = 0, sliceStart = 0; sliceEnd <= len; sliceEnd++) {
243
+ var char = str.charAt(sliceEnd),
244
+ sliceLength = sliceEnd - sliceStart
245
+
246
+ if ((char.match(lunr.tokenizer.separator) || sliceEnd == len)) {
247
+
248
+ if (sliceLength > 0) {
249
+ tokens.push(
250
+ new lunr.Token (str.slice(sliceStart, sliceEnd), {
251
+ position: [sliceStart, sliceLength],
252
+ index: tokens.length
253
+ })
254
+ )
255
+ }
256
+
257
+ sliceStart = sliceEnd + 1
258
+ }
259
+
260
+ }
261
+
262
+ return tokens
263
+ }
264
+
265
+ /**
266
+ * The separator used to split a string into tokens. Override this property to change the behaviour of
267
+ * `lunr.tokenizer` behaviour when tokenizing strings. By default this splits on whitespace and hyphens.
268
+ *
269
+ * @static
270
+ * @see lunr.tokenizer
271
+ */
272
+ lunr.tokenizer.separator = /[\s\-]+/
273
+ /*!
274
+ * lunr.Pipeline
275
+ * Copyright (C) 2017 Oliver Nightingale
276
+ */
277
+
278
+ /**
279
+ * lunr.Pipelines maintain an ordered list of functions to be applied to all
280
+ * tokens in documents entering the search index and queries being ran against
281
+ * the index.
282
+ *
283
+ * An instance of lunr.Index created with the lunr shortcut will contain a
284
+ * pipeline with a stop word filter and an English language stemmer. Extra
285
+ * functions can be added before or after either of these functions or these
286
+ * default functions can be removed.
287
+ *
288
+ * When run the pipeline will call each function in turn, passing a token, the
289
+ * index of that token in the original list of all tokens and finally a list of
290
+ * all the original tokens.
291
+ *
292
+ * The output of functions in the pipeline will be passed to the next function
293
+ * in the pipeline. To exclude a token from entering the index the function
294
+ * should return undefined, the rest of the pipeline will not be called with
295
+ * this token.
296
+ *
297
+ * For serialisation of pipelines to work, all functions used in an instance of
298
+ * a pipeline should be registered with lunr.Pipeline. Registered functions can
299
+ * then be loaded. If trying to load a serialised pipeline that uses functions
300
+ * that are not registered an error will be thrown.
301
+ *
302
+ * If not planning on serialising the pipeline then registering pipeline functions
303
+ * is not necessary.
304
+ *
305
+ * @constructor
306
+ */
307
+ lunr.Pipeline = function () {
308
+ this._stack = []
309
+ }
310
+
311
+ lunr.Pipeline.registeredFunctions = Object.create(null)
312
+
313
+ /**
314
+ * A pipeline function maps lunr.Token to lunr.Token. A lunr.Token contains the token
315
+ * string as well as all known metadata. A pipeline function can mutate the token string
316
+ * or mutate (or add) metadata for a given token.
317
+ *
318
+ * A pipeline function can indicate that the passed token should be discarded by returning
319
+ * null. This token will not be passed to any downstream pipeline functions and will not be
320
+ * added to the index.
321
+ *
322
+ * Multiple tokens can be returned by returning an array of tokens. Each token will be passed
323
+ * to any downstream pipeline functions and all will returned tokens will be added to the index.
324
+ *
325
+ * Any number of pipeline functions may be chained together using a lunr.Pipeline.
326
+ *
327
+ * @interface lunr.PipelineFunction
328
+ * @param {lunr.Token} token - A token from the document being processed.
329
+ * @param {number} i - The index of this token in the complete list of tokens for this document/field.
330
+ * @param {lunr.Token[]} tokens - All tokens for this document/field.
331
+ * @returns {(?lunr.Token|lunr.Token[])}
332
+ */
333
+
334
+ /**
335
+ * Register a function with the pipeline.
336
+ *
337
+ * Functions that are used in the pipeline should be registered if the pipeline
338
+ * needs to be serialised, or a serialised pipeline needs to be loaded.
339
+ *
340
+ * Registering a function does not add it to a pipeline, functions must still be
341
+ * added to instances of the pipeline for them to be used when running a pipeline.
342
+ *
343
+ * @param {lunr.PipelineFunction} fn - The function to check for.
344
+ * @param {String} label - The label to register this function with
345
+ */
346
+ lunr.Pipeline.registerFunction = function (fn, label) {
347
+ if (label in this.registeredFunctions) {
348
+ lunr.utils.warn('Overwriting existing registered function: ' + label)
349
+ }
350
+
351
+ fn.label = label
352
+ lunr.Pipeline.registeredFunctions[fn.label] = fn
353
+ }
354
+
355
+ /**
356
+ * Warns if the function is not registered as a Pipeline function.
357
+ *
358
+ * @param {lunr.PipelineFunction} fn - The function to check for.
359
+ * @private
360
+ */
361
+ lunr.Pipeline.warnIfFunctionNotRegistered = function (fn) {
362
+ var isRegistered = fn.label && (fn.label in this.registeredFunctions)
363
+
364
+ if (!isRegistered) {
365
+ lunr.utils.warn('Function is not registered with pipeline. This may cause problems when serialising the index.\n', fn)
366
+ }
367
+ }
368
+
369
+ /**
370
+ * Loads a previously serialised pipeline.
371
+ *
372
+ * All functions to be loaded must already be registered with lunr.Pipeline.
373
+ * If any function from the serialised data has not been registered then an
374
+ * error will be thrown.
375
+ *
376
+ * @param {Object} serialised - The serialised pipeline to load.
377
+ * @returns {lunr.Pipeline}
378
+ */
379
+ lunr.Pipeline.load = function (serialised) {
380
+ var pipeline = new lunr.Pipeline
381
+
382
+ serialised.forEach(function (fnName) {
383
+ var fn = lunr.Pipeline.registeredFunctions[fnName]
384
+
385
+ if (fn) {
386
+ pipeline.add(fn)
387
+ } else {
388
+ throw new Error('Cannot load unregistered function: ' + fnName)
389
+ }
390
+ })
391
+
392
+ return pipeline
393
+ }
394
+
395
+ /**
396
+ * Adds new functions to the end of the pipeline.
397
+ *
398
+ * Logs a warning if the function has not been registered.
399
+ *
400
+ * @param {lunr.PipelineFunction[]} functions - Any number of functions to add to the pipeline.
401
+ */
402
+ lunr.Pipeline.prototype.add = function () {
403
+ var fns = Array.prototype.slice.call(arguments)
404
+
405
+ fns.forEach(function (fn) {
406
+ lunr.Pipeline.warnIfFunctionNotRegistered(fn)
407
+ this._stack.push(fn)
408
+ }, this)
409
+ }
410
+
411
+ /**
412
+ * Adds a single function after a function that already exists in the
413
+ * pipeline.
414
+ *
415
+ * Logs a warning if the function has not been registered.
416
+ *
417
+ * @param {lunr.PipelineFunction} existingFn - A function that already exists in the pipeline.
418
+ * @param {lunr.PipelineFunction} newFn - The new function to add to the pipeline.
419
+ */
420
+ lunr.Pipeline.prototype.after = function (existingFn, newFn) {
421
+ lunr.Pipeline.warnIfFunctionNotRegistered(newFn)
422
+
423
+ var pos = this._stack.indexOf(existingFn)
424
+ if (pos == -1) {
425
+ throw new Error('Cannot find existingFn')
426
+ }
427
+
428
+ pos = pos + 1
429
+ this._stack.splice(pos, 0, newFn)
430
+ }
431
+
432
+ /**
433
+ * Adds a single function before a function that already exists in the
434
+ * pipeline.
435
+ *
436
+ * Logs a warning if the function has not been registered.
437
+ *
438
+ * @param {lunr.PipelineFunction} existingFn - A function that already exists in the pipeline.
439
+ * @param {lunr.PipelineFunction} newFn - The new function to add to the pipeline.
440
+ */
441
+ lunr.Pipeline.prototype.before = function (existingFn, newFn) {
442
+ lunr.Pipeline.warnIfFunctionNotRegistered(newFn)
443
+
444
+ var pos = this._stack.indexOf(existingFn)
445
+ if (pos == -1) {
446
+ throw new Error('Cannot find existingFn')
447
+ }
448
+
449
+ this._stack.splice(pos, 0, newFn)
450
+ }
451
+
452
+ /**
453
+ * Removes a function from the pipeline.
454
+ *
455
+ * @param {lunr.PipelineFunction} fn The function to remove from the pipeline.
456
+ */
457
+ lunr.Pipeline.prototype.remove = function (fn) {
458
+ var pos = this._stack.indexOf(fn)
459
+ if (pos == -1) {
460
+ return
461
+ }
462
+
463
+ this._stack.splice(pos, 1)
464
+ }
465
+
466
+ /**
467
+ * Runs the current list of functions that make up the pipeline against the
468
+ * passed tokens.
469
+ *
470
+ * @param {Array} tokens The tokens to run through the pipeline.
471
+ * @returns {Array}
472
+ */
473
+ lunr.Pipeline.prototype.run = function (tokens) {
474
+ var stackLength = this._stack.length
475
+
476
+ for (var i = 0; i < stackLength; i++) {
477
+ var fn = this._stack[i]
478
+
479
+ tokens = tokens.reduce(function (memo, token, j) {
480
+ var result = fn(token, j, tokens)
481
+
482
+ if (result === void 0 || result === '') return memo
483
+
484
+ return memo.concat(result)
485
+ }, [])
486
+ }
487
+
488
+ return tokens
489
+ }
490
+
491
+ /**
492
+ * Convenience method for passing a string through a pipeline and getting
493
+ * strings out. This method takes care of wrapping the passed string in a
494
+ * token and mapping the resulting tokens back to strings.
495
+ *
496
+ * @param {string} str - The string to pass through the pipeline.
497
+ * @returns {string[]}
498
+ */
499
+ lunr.Pipeline.prototype.runString = function (str) {
500
+ var token = new lunr.Token (str)
501
+
502
+ return this.run([token]).map(function (t) {
503
+ return t.toString()
504
+ })
505
+ }
506
+
507
+ /**
508
+ * Resets the pipeline by removing any existing processors.
509
+ *
510
+ */
511
+ lunr.Pipeline.prototype.reset = function () {
512
+ this._stack = []
513
+ }
514
+
515
+ /**
516
+ * Returns a representation of the pipeline ready for serialisation.
517
+ *
518
+ * Logs a warning if the function has not been registered.
519
+ *
520
+ * @returns {Array}
521
+ */
522
+ lunr.Pipeline.prototype.toJSON = function () {
523
+ return this._stack.map(function (fn) {
524
+ lunr.Pipeline.warnIfFunctionNotRegistered(fn)
525
+
526
+ return fn.label
527
+ })
528
+ }
529
+ /*!
530
+ * lunr.Vector
531
+ * Copyright (C) 2017 Oliver Nightingale
532
+ */
533
+
534
+ /**
535
+ * A vector is used to construct the vector space of documents and queries. These
536
+ * vectors support operations to determine the similarity between two documents or
537
+ * a document and a query.
538
+ *
539
+ * Normally no parameters are required for initializing a vector, but in the case of
540
+ * loading a previously dumped vector the raw elements can be provided to the constructor.
541
+ *
542
+ * For performance reasons vectors are implemented with a flat array, where an elements
543
+ * index is immediately followed by its value. E.g. [index, value, index, value]. This
544
+ * allows the underlying array to be as sparse as possible and still offer decent
545
+ * performance when being used for vector calculations.
546
+ *
547
+ * @constructor
548
+ * @param {Number[]} [elements] - The flat list of element index and element value pairs.
549
+ */
550
+ lunr.Vector = function (elements) {
551
+ this._magnitude = 0
552
+ this.elements = elements || []
553
+ }
554
+
555
+
556
+ /**
557
+ * Calculates the position within the vector to insert a given index.
558
+ *
559
+ * This is used internally by insert and upsert. If there are duplicate indexes then
560
+ * the position is returned as if the value for that index were to be updated, but it
561
+ * is the callers responsibility to check whether there is a duplicate at that index
562
+ *
563
+ * @param {Number} insertIdx - The index at which the element should be inserted.
564
+ * @returns {Number}
565
+ */
566
+ lunr.Vector.prototype.positionForIndex = function (index) {
567
+ // For an empty vector the tuple can be inserted at the beginning
568
+ if (this.elements.length == 0) {
569
+ return 0
570
+ }
571
+
572
+ var start = 0,
573
+ end = this.elements.length / 2,
574
+ sliceLength = end - start,
575
+ pivotPoint = Math.floor(sliceLength / 2),
576
+ pivotIndex = this.elements[pivotPoint * 2]
577
+
578
+ while (sliceLength > 1) {
579
+ if (pivotIndex < index) {
580
+ start = pivotPoint
581
+ }
582
+
583
+ if (pivotIndex > index) {
584
+ end = pivotPoint
585
+ }
586
+
587
+ if (pivotIndex == index) {
588
+ break
589
+ }
590
+
591
+ sliceLength = end - start
592
+ pivotPoint = start + Math.floor(sliceLength / 2)
593
+ pivotIndex = this.elements[pivotPoint * 2]
594
+ }
595
+
596
+ if (pivotIndex == index) {
597
+ return pivotPoint * 2
598
+ }
599
+
600
+ if (pivotIndex > index) {
601
+ return pivotPoint * 2
602
+ }
603
+
604
+ if (pivotIndex < index) {
605
+ return (pivotPoint + 1) * 2
606
+ }
607
+ }
608
+
609
+ /**
610
+ * Inserts an element at an index within the vector.
611
+ *
612
+ * Does not allow duplicates, will throw an error if there is already an entry
613
+ * for this index.
614
+ *
615
+ * @param {Number} insertIdx - The index at which the element should be inserted.
616
+ * @param {Number} val - The value to be inserted into the vector.
617
+ */
618
+ lunr.Vector.prototype.insert = function (insertIdx, val) {
619
+ this.upsert(insertIdx, val, function () {
620
+ throw "duplicate index"
621
+ })
622
+ }
623
+
624
+ /**
625
+ * Inserts or updates an existing index within the vector.
626
+ *
627
+ * @param {Number} insertIdx - The index at which the element should be inserted.
628
+ * @param {Number} val - The value to be inserted into the vector.
629
+ * @param {function} fn - A function that is called for updates, the existing value and the
630
+ * requested value are passed as arguments
631
+ */
632
+ lunr.Vector.prototype.upsert = function (insertIdx, val, fn) {
633
+ this._magnitude = 0
634
+ var position = this.positionForIndex(insertIdx)
635
+
636
+ if (this.elements[position] == insertIdx) {
637
+ this.elements[position + 1] = fn(this.elements[position + 1], val)
638
+ } else {
639
+ this.elements.splice(position, 0, insertIdx, val)
640
+ }
641
+ }
642
+
643
+ /**
644
+ * Calculates the magnitude of this vector.
645
+ *
646
+ * @returns {Number}
647
+ */
648
+ lunr.Vector.prototype.magnitude = function () {
649
+ if (this._magnitude) return this._magnitude
650
+
651
+ var sumOfSquares = 0,
652
+ elementsLength = this.elements.length
653
+
654
+ for (var i = 1; i < elementsLength; i += 2) {
655
+ var val = this.elements[i]
656
+ sumOfSquares += val * val
657
+ }
658
+
659
+ return this._magnitude = Math.sqrt(sumOfSquares)
660
+ }
661
+
662
+ /**
663
+ * Calculates the dot product of this vector and another vector.
664
+ *
665
+ * @param {lunr.Vector} otherVector - The vector to compute the dot product with.
666
+ * @returns {Number}
667
+ */
668
+ lunr.Vector.prototype.dot = function (otherVector) {
669
+ var dotProduct = 0,
670
+ a = this.elements, b = otherVector.elements,
671
+ aLen = a.length, bLen = b.length,
672
+ aVal = 0, bVal = 0,
673
+ i = 0, j = 0
674
+
675
+ while (i < aLen && j < bLen) {
676
+ aVal = a[i], bVal = b[j]
677
+ if (aVal < bVal) {
678
+ i += 2
679
+ } else if (aVal > bVal) {
680
+ j += 2
681
+ } else if (aVal == bVal) {
682
+ dotProduct += a[i + 1] * b[j + 1]
683
+ i += 2
684
+ j += 2
685
+ }
686
+ }
687
+
688
+ return dotProduct
689
+ }
690
+
691
+ /**
692
+ * Calculates the cosine similarity between this vector and another
693
+ * vector.
694
+ *
695
+ * @param {lunr.Vector} otherVector - The other vector to calculate the
696
+ * similarity with.
697
+ * @returns {Number}
698
+ */
699
+ lunr.Vector.prototype.similarity = function (otherVector) {
700
+ return this.dot(otherVector) / (this.magnitude() * otherVector.magnitude())
701
+ }
702
+
703
+ /**
704
+ * Converts the vector to an array of the elements within the vector.
705
+ *
706
+ * @returns {Number[]}
707
+ */
708
+ lunr.Vector.prototype.toArray = function () {
709
+ var output = new Array (this.elements.length / 2)
710
+
711
+ for (var i = 1, j = 0; i < this.elements.length; i += 2, j++) {
712
+ output[j] = this.elements[i]
713
+ }
714
+
715
+ return output
716
+ }
717
+
718
+ /**
719
+ * A JSON serializable representation of the vector.
720
+ *
721
+ * @returns {Number[]}
722
+ */
723
+ lunr.Vector.prototype.toJSON = function () {
724
+ return this.elements
725
+ }
726
+ /* eslint-disable */
727
+ /*!
728
+ * lunr.stemmer
729
+ * Copyright (C) 2017 Oliver Nightingale
730
+ * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt
731
+ */
732
+
733
+ /**
734
+ * lunr.stemmer is an english language stemmer, this is a JavaScript
735
+ * implementation of the PorterStemmer taken from http://tartarus.org/~martin
736
+ *
737
+ * @static
738
+ * @implements {lunr.PipelineFunction}
739
+ * @param {lunr.Token} token - The string to stem
740
+ * @returns {lunr.Token}
741
+ * @see {@link lunr.Pipeline}
742
+ */
743
+ lunr.stemmer = (function(){
744
+ var step2list = {
745
+ "ational" : "ate",
746
+ "tional" : "tion",
747
+ "enci" : "ence",
748
+ "anci" : "ance",
749
+ "izer" : "ize",
750
+ "bli" : "ble",
751
+ "alli" : "al",
752
+ "entli" : "ent",
753
+ "eli" : "e",
754
+ "ousli" : "ous",
755
+ "ization" : "ize",
756
+ "ation" : "ate",
757
+ "ator" : "ate",
758
+ "alism" : "al",
759
+ "iveness" : "ive",
760
+ "fulness" : "ful",
761
+ "ousness" : "ous",
762
+ "aliti" : "al",
763
+ "iviti" : "ive",
764
+ "biliti" : "ble",
765
+ "logi" : "log"
766
+ },
767
+
768
+ step3list = {
769
+ "icate" : "ic",
770
+ "ative" : "",
771
+ "alize" : "al",
772
+ "iciti" : "ic",
773
+ "ical" : "ic",
774
+ "ful" : "",
775
+ "ness" : ""
776
+ },
777
+
778
+ c = "[^aeiou]", // consonant
779
+ v = "[aeiouy]", // vowel
780
+ C = c + "[^aeiouy]*", // consonant sequence
781
+ V = v + "[aeiou]*", // vowel sequence
782
+
783
+ mgr0 = "^(" + C + ")?" + V + C, // [C]VC... is m>0
784
+ meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$", // [C]VC[V] is m=1
785
+ mgr1 = "^(" + C + ")?" + V + C + V + C, // [C]VCVC... is m>1
786
+ s_v = "^(" + C + ")?" + v; // vowel in stem
787
+
788
+ var re_mgr0 = new RegExp(mgr0);
789
+ var re_mgr1 = new RegExp(mgr1);
790
+ var re_meq1 = new RegExp(meq1);
791
+ var re_s_v = new RegExp(s_v);
792
+
793
+ var re_1a = /^(.+?)(ss|i)es$/;
794
+ var re2_1a = /^(.+?)([^s])s$/;
795
+ var re_1b = /^(.+?)eed$/;
796
+ var re2_1b = /^(.+?)(ed|ing)$/;
797
+ var re_1b_2 = /.$/;
798
+ var re2_1b_2 = /(at|bl|iz)$/;
799
+ var re3_1b_2 = new RegExp("([^aeiouylsz])\\1$");
800
+ var re4_1b_2 = new RegExp("^" + C + v + "[^aeiouwxy]$");
801
+
802
+ var re_1c = /^(.+?[^aeiou])y$/;
803
+ var re_2 = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;
804
+
805
+ var re_3 = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;
806
+
807
+ var re_4 = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;
808
+ var re2_4 = /^(.+?)(s|t)(ion)$/;
809
+
810
+ var re_5 = /^(.+?)e$/;
811
+ var re_5_1 = /ll$/;
812
+ var re3_5 = new RegExp("^" + C + v + "[^aeiouwxy]$");
813
+
814
+ var porterStemmer = function porterStemmer(w) {
815
+ var stem,
816
+ suffix,
817
+ firstch,
818
+ re,
819
+ re2,
820
+ re3,
821
+ re4;
822
+
823
+ if (w.length < 3) { return w; }
824
+
825
+ firstch = w.substr(0,1);
826
+ if (firstch == "y") {
827
+ w = firstch.toUpperCase() + w.substr(1);
828
+ }
829
+
830
+ // Step 1a
831
+ re = re_1a
832
+ re2 = re2_1a;
833
+
834
+ if (re.test(w)) { w = w.replace(re,"$1$2"); }
835
+ else if (re2.test(w)) { w = w.replace(re2,"$1$2"); }
836
+
837
+ // Step 1b
838
+ re = re_1b;
839
+ re2 = re2_1b;
840
+ if (re.test(w)) {
841
+ var fp = re.exec(w);
842
+ re = re_mgr0;
843
+ if (re.test(fp[1])) {
844
+ re = re_1b_2;
845
+ w = w.replace(re,"");
846
+ }
847
+ } else if (re2.test(w)) {
848
+ var fp = re2.exec(w);
849
+ stem = fp[1];
850
+ re2 = re_s_v;
851
+ if (re2.test(stem)) {
852
+ w = stem;
853
+ re2 = re2_1b_2;
854
+ re3 = re3_1b_2;
855
+ re4 = re4_1b_2;
856
+ if (re2.test(w)) { w = w + "e"; }
857
+ else if (re3.test(w)) { re = re_1b_2; w = w.replace(re,""); }
858
+ else if (re4.test(w)) { w = w + "e"; }
859
+ }
860
+ }
861
+
862
+ // Step 1c - replace suffix y or Y by i if preceded by a non-vowel which is not the first letter of the word (so cry -> cri, by -> by, say -> say)
863
+ re = re_1c;
864
+ if (re.test(w)) {
865
+ var fp = re.exec(w);
866
+ stem = fp[1];
867
+ w = stem + "i";
868
+ }
869
+
870
+ // Step 2
871
+ re = re_2;
872
+ if (re.test(w)) {
873
+ var fp = re.exec(w);
874
+ stem = fp[1];
875
+ suffix = fp[2];
876
+ re = re_mgr0;
877
+ if (re.test(stem)) {
878
+ w = stem + step2list[suffix];
879
+ }
880
+ }
881
+
882
+ // Step 3
883
+ re = re_3;
884
+ if (re.test(w)) {
885
+ var fp = re.exec(w);
886
+ stem = fp[1];
887
+ suffix = fp[2];
888
+ re = re_mgr0;
889
+ if (re.test(stem)) {
890
+ w = stem + step3list[suffix];
891
+ }
892
+ }
893
+
894
+ // Step 4
895
+ re = re_4;
896
+ re2 = re2_4;
897
+ if (re.test(w)) {
898
+ var fp = re.exec(w);
899
+ stem = fp[1];
900
+ re = re_mgr1;
901
+ if (re.test(stem)) {
902
+ w = stem;
903
+ }
904
+ } else if (re2.test(w)) {
905
+ var fp = re2.exec(w);
906
+ stem = fp[1] + fp[2];
907
+ re2 = re_mgr1;
908
+ if (re2.test(stem)) {
909
+ w = stem;
910
+ }
911
+ }
912
+
913
+ // Step 5
914
+ re = re_5;
915
+ if (re.test(w)) {
916
+ var fp = re.exec(w);
917
+ stem = fp[1];
918
+ re = re_mgr1;
919
+ re2 = re_meq1;
920
+ re3 = re3_5;
921
+ if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) {
922
+ w = stem;
923
+ }
924
+ }
925
+
926
+ re = re_5_1;
927
+ re2 = re_mgr1;
928
+ if (re.test(w) && re2.test(w)) {
929
+ re = re_1b_2;
930
+ w = w.replace(re,"");
931
+ }
932
+
933
+ // and turn initial Y back to y
934
+
935
+ if (firstch == "y") {
936
+ w = firstch.toLowerCase() + w.substr(1);
937
+ }
938
+
939
+ return w;
940
+ };
941
+
942
+ return function (token) {
943
+ return token.update(porterStemmer);
944
+ }
945
+ })();
946
+
947
+ lunr.Pipeline.registerFunction(lunr.stemmer, 'stemmer')
948
+ /*!
949
+ * lunr.stopWordFilter
950
+ * Copyright (C) 2017 Oliver Nightingale
951
+ */
952
+
953
+ /**
954
+ * lunr.generateStopWordFilter builds a stopWordFilter function from the provided
955
+ * list of stop words.
956
+ *
957
+ * The built in lunr.stopWordFilter is built using this generator and can be used
958
+ * to generate custom stopWordFilters for applications or non English languages.
959
+ *
960
+ * @param {Array} token The token to pass through the filter
961
+ * @returns {lunr.PipelineFunction}
962
+ * @see lunr.Pipeline
963
+ * @see lunr.stopWordFilter
964
+ */
965
+ lunr.generateStopWordFilter = function (stopWords) {
966
+ var words = stopWords.reduce(function (memo, stopWord) {
967
+ memo[stopWord] = stopWord
968
+ return memo
969
+ }, {})
970
+
971
+ return function (token) {
972
+ if (token && words[token.toString()] !== token.toString()) return token
973
+ }
974
+ }
975
+
976
+ /**
977
+ * lunr.stopWordFilter is an English language stop word list filter, any words
978
+ * contained in the list will not be passed through the filter.
979
+ *
980
+ * This is intended to be used in the Pipeline. If the token does not pass the
981
+ * filter then undefined will be returned.
982
+ *
983
+ * @implements {lunr.PipelineFunction}
984
+ * @params {lunr.Token} token - A token to check for being a stop word.
985
+ * @returns {lunr.Token}
986
+ * @see {@link lunr.Pipeline}
987
+ */
988
+ lunr.stopWordFilter = lunr.generateStopWordFilter([
989
+ 'a',
990
+ 'able',
991
+ 'about',
992
+ 'across',
993
+ 'after',
994
+ 'all',
995
+ 'almost',
996
+ 'also',
997
+ 'am',
998
+ 'among',
999
+ 'an',
1000
+ 'and',
1001
+ 'any',
1002
+ 'are',
1003
+ 'as',
1004
+ 'at',
1005
+ 'be',
1006
+ 'because',
1007
+ 'been',
1008
+ 'but',
1009
+ 'by',
1010
+ 'can',
1011
+ 'cannot',
1012
+ 'could',
1013
+ 'dear',
1014
+ 'did',
1015
+ 'do',
1016
+ 'does',
1017
+ 'either',
1018
+ 'else',
1019
+ 'ever',
1020
+ 'every',
1021
+ 'for',
1022
+ 'from',
1023
+ 'get',
1024
+ 'got',
1025
+ 'had',
1026
+ 'has',
1027
+ 'have',
1028
+ 'he',
1029
+ 'her',
1030
+ 'hers',
1031
+ 'him',
1032
+ 'his',
1033
+ 'how',
1034
+ 'however',
1035
+ 'i',
1036
+ 'if',
1037
+ 'in',
1038
+ 'into',
1039
+ 'is',
1040
+ 'it',
1041
+ 'its',
1042
+ 'just',
1043
+ 'least',
1044
+ 'let',
1045
+ 'like',
1046
+ 'likely',
1047
+ 'may',
1048
+ 'me',
1049
+ 'might',
1050
+ 'most',
1051
+ 'must',
1052
+ 'my',
1053
+ 'neither',
1054
+ 'no',
1055
+ 'nor',
1056
+ 'not',
1057
+ 'of',
1058
+ 'off',
1059
+ 'often',
1060
+ 'on',
1061
+ 'only',
1062
+ 'or',
1063
+ 'other',
1064
+ 'our',
1065
+ 'own',
1066
+ 'rather',
1067
+ 'said',
1068
+ 'say',
1069
+ 'says',
1070
+ 'she',
1071
+ 'should',
1072
+ 'since',
1073
+ 'so',
1074
+ 'some',
1075
+ 'than',
1076
+ 'that',
1077
+ 'the',
1078
+ 'their',
1079
+ 'them',
1080
+ 'then',
1081
+ 'there',
1082
+ 'these',
1083
+ 'they',
1084
+ 'this',
1085
+ 'tis',
1086
+ 'to',
1087
+ 'too',
1088
+ 'twas',
1089
+ 'us',
1090
+ 'wants',
1091
+ 'was',
1092
+ 'we',
1093
+ 'were',
1094
+ 'what',
1095
+ 'when',
1096
+ 'where',
1097
+ 'which',
1098
+ 'while',
1099
+ 'who',
1100
+ 'whom',
1101
+ 'why',
1102
+ 'will',
1103
+ 'with',
1104
+ 'would',
1105
+ 'yet',
1106
+ 'you',
1107
+ 'your'
1108
+ ])
1109
+
1110
+ lunr.Pipeline.registerFunction(lunr.stopWordFilter, 'stopWordFilter')
1111
+ /*!
1112
+ * lunr.trimmer
1113
+ * Copyright (C) 2017 Oliver Nightingale
1114
+ */
1115
+
1116
+ /**
1117
+ * lunr.trimmer is a pipeline function for trimming non word
1118
+ * characters from the beginning and end of tokens before they
1119
+ * enter the index.
1120
+ *
1121
+ * This implementation may not work correctly for non latin
1122
+ * characters and should either be removed or adapted for use
1123
+ * with languages with non-latin characters.
1124
+ *
1125
+ * @static
1126
+ * @implements {lunr.PipelineFunction}
1127
+ * @param {lunr.Token} token The token to pass through the filter
1128
+ * @returns {lunr.Token}
1129
+ * @see lunr.Pipeline
1130
+ */
1131
+ lunr.trimmer = function (token) {
1132
+ return token.update(function (s) {
1133
+ return s.replace(/^\W+/, '').replace(/\W+$/, '')
1134
+ })
1135
+ }
1136
+
1137
+ lunr.Pipeline.registerFunction(lunr.trimmer, 'trimmer')
1138
+ /*!
1139
+ * lunr.TokenSet
1140
+ * Copyright (C) 2017 Oliver Nightingale
1141
+ */
1142
+
1143
+ /**
1144
+ * A token set is used to store the unique list of all tokens
1145
+ * within an index. Token sets are also used to represent an
1146
+ * incoming query to the index, this query token set and index
1147
+ * token set are then intersected to find which tokens to look
1148
+ * up in the inverted index.
1149
+ *
1150
+ * A token set can hold multiple tokens, as in the case of the
1151
+ * index token set, or it can hold a single token as in the
1152
+ * case of a simple query token set.
1153
+ *
1154
+ * Additionally token sets are used to perform wildcard matching.
1155
+ * Leading, contained and trailing wildcards are supported, and
1156
+ * from this edit distance matching can also be provided.
1157
+ *
1158
+ * Token sets are implemented as a minimal finite state automata,
1159
+ * where both common prefixes and suffixes are shared between tokens.
1160
+ * This helps to reduce the space used for storing the token set.
1161
+ *
1162
+ * @constructor
1163
+ */
1164
+ lunr.TokenSet = function () {
1165
+ this.final = false
1166
+ this.edges = {}
1167
+ this.id = lunr.TokenSet._nextId
1168
+ lunr.TokenSet._nextId += 1
1169
+ }
1170
+
1171
+ /**
1172
+ * Keeps track of the next, auto increment, identifier to assign
1173
+ * to a new tokenSet.
1174
+ *
1175
+ * TokenSets require a unique identifier to be correctly minimised.
1176
+ *
1177
+ * @private
1178
+ */
1179
+ lunr.TokenSet._nextId = 1
1180
+
1181
+ /**
1182
+ * Creates a TokenSet instance from the given sorted array of words.
1183
+ *
1184
+ * @param {String[]} arr - A sorted array of strings to create the set from.
1185
+ * @returns {lunr.TokenSet}
1186
+ * @throws Will throw an error if the input array is not sorted.
1187
+ */
1188
+ lunr.TokenSet.fromArray = function (arr) {
1189
+ var builder = new lunr.TokenSet.Builder
1190
+
1191
+ for (var i = 0, len = arr.length; i < len; i++) {
1192
+ builder.insert(arr[i])
1193
+ }
1194
+
1195
+ builder.finish()
1196
+ return builder.root
1197
+ }
1198
+
1199
+ /**
1200
+ * Creates a token set from a query clause.
1201
+ *
1202
+ * @private
1203
+ * @param {Object} clause - A single clause from lunr.Query.
1204
+ * @param {string} clause.term - The query clause term.
1205
+ * @param {number} [clause.editDistance] - The optional edit distance for the term.
1206
+ * @returns {lunr.TokenSet}
1207
+ */
1208
+ lunr.TokenSet.fromClause = function (clause) {
1209
+ if ('editDistance' in clause) {
1210
+ return lunr.TokenSet.fromFuzzyString(clause.term, clause.editDistance)
1211
+ } else {
1212
+ return lunr.TokenSet.fromString(clause.term)
1213
+ }
1214
+ }
1215
+
1216
+ /**
1217
+ * Creates a token set representing a single string with a specified
1218
+ * edit distance.
1219
+ *
1220
+ * Insertions, deletions, substitutions and transpositions are each
1221
+ * treated as an edit distance of 1.
1222
+ *
1223
+ * Increasing the allowed edit distance will have a dramatic impact
1224
+ * on the performance of both creating and intersecting these TokenSets.
1225
+ * It is advised to keep the edit distance less than 3.
1226
+ *
1227
+ * @param {string} str - The string to create the token set from.
1228
+ * @param {number} editDistance - The allowed edit distance to match.
1229
+ * @returns {lunr.Vector}
1230
+ */
1231
+ lunr.TokenSet.fromFuzzyString = function (str, editDistance) {
1232
+ var root = new lunr.TokenSet
1233
+
1234
+ var stack = [{
1235
+ node: root,
1236
+ editsRemaining: editDistance,
1237
+ str: str
1238
+ }]
1239
+
1240
+ while (stack.length) {
1241
+ var frame = stack.pop()
1242
+
1243
+ // no edit
1244
+ if (frame.str.length > 0) {
1245
+ var char = frame.str.charAt(0),
1246
+ noEditNode
1247
+
1248
+ if (char in frame.node.edges) {
1249
+ noEditNode = frame.node.edges[char]
1250
+ } else {
1251
+ noEditNode = new lunr.TokenSet
1252
+ frame.node.edges[char] = noEditNode
1253
+ }
1254
+
1255
+ if (frame.str.length == 1) {
1256
+ noEditNode.final = true
1257
+ } else {
1258
+ stack.push({
1259
+ node: noEditNode,
1260
+ editsRemaining: frame.editsRemaining,
1261
+ str: frame.str.slice(1)
1262
+ })
1263
+ }
1264
+ }
1265
+
1266
+ // deletion
1267
+ // can only do a deletion if we have enough edits remaining
1268
+ // and if there are characters left to delete in the string
1269
+ if (frame.editsRemaining > 0 && frame.str.length > 1) {
1270
+ var char = frame.str.charAt(1),
1271
+ deletionNode
1272
+
1273
+ if (char in frame.node.edges) {
1274
+ deletionNode = frame.node.edges[char]
1275
+ } else {
1276
+ deletionNode = new lunr.TokenSet
1277
+ frame.node.edges[char] = deletionNode
1278
+ }
1279
+
1280
+ if (frame.str.length <= 2) {
1281
+ deletionNode.final = true
1282
+ } else {
1283
+ stack.push({
1284
+ node: deletionNode,
1285
+ editsRemaining: frame.editsRemaining - 1,
1286
+ str: frame.str.slice(2)
1287
+ })
1288
+ }
1289
+ }
1290
+
1291
+ // deletion
1292
+ // just removing the last character from the str
1293
+ if (frame.editsRemaining > 0 && frame.str.length == 1) {
1294
+ frame.node.final = true
1295
+ }
1296
+
1297
+ // substitution
1298
+ // can only do a substitution if we have enough edits remaining
1299
+ // and if there are characters left to substitute
1300
+ if (frame.editsRemaining > 0 && frame.str.length >= 1) {
1301
+ if ("*" in frame.node.edges) {
1302
+ var substitutionNode = frame.node.edges["*"]
1303
+ } else {
1304
+ var substitutionNode = new lunr.TokenSet
1305
+ frame.node.edges["*"] = substitutionNode
1306
+ }
1307
+
1308
+ if (frame.str.length == 1) {
1309
+ substitutionNode.final = true
1310
+ } else {
1311
+ stack.push({
1312
+ node: substitutionNode,
1313
+ editsRemaining: frame.editsRemaining - 1,
1314
+ str: frame.str.slice(1)
1315
+ })
1316
+ }
1317
+ }
1318
+
1319
+ // insertion
1320
+ // can only do insertion if there are edits remaining
1321
+ if (frame.editsRemaining > 0) {
1322
+ if ("*" in frame.node.edges) {
1323
+ var insertionNode = frame.node.edges["*"]
1324
+ } else {
1325
+ var insertionNode = new lunr.TokenSet
1326
+ frame.node.edges["*"] = insertionNode
1327
+ }
1328
+
1329
+ if (frame.str.length == 0) {
1330
+ insertionNode.final = true
1331
+ } else {
1332
+ stack.push({
1333
+ node: insertionNode,
1334
+ editsRemaining: frame.editsRemaining - 1,
1335
+ str: frame.str
1336
+ })
1337
+ }
1338
+ }
1339
+
1340
+ // transposition
1341
+ // can only do a transposition if there are edits remaining
1342
+ // and there are enough characters to transpose
1343
+ if (frame.editsRemaining > 0 && frame.str.length > 1) {
1344
+ var charA = frame.str.charAt(0),
1345
+ charB = frame.str.charAt(1),
1346
+ transposeNode
1347
+
1348
+ if (charB in frame.node.edges) {
1349
+ transposeNode = frame.node.edges[charB]
1350
+ } else {
1351
+ transposeNode = new lunr.TokenSet
1352
+ frame.node.edges[charB] = transposeNode
1353
+ }
1354
+
1355
+ if (frame.str.length == 1) {
1356
+ transposeNode.final = true
1357
+ } else {
1358
+ stack.push({
1359
+ node: transposeNode,
1360
+ editsRemaining: frame.editsRemaining - 1,
1361
+ str: charA + frame.str.slice(2)
1362
+ })
1363
+ }
1364
+ }
1365
+ }
1366
+
1367
+ return root
1368
+ }
1369
+
1370
+ /**
1371
+ * Creates a TokenSet from a string.
1372
+ *
1373
+ * The string may contain one or more wildcard characters (*)
1374
+ * that will allow wildcard matching when intersecting with
1375
+ * another TokenSet.
1376
+ *
1377
+ * @param {string} str - The string to create a TokenSet from.
1378
+ * @returns {lunr.TokenSet}
1379
+ */
1380
+ lunr.TokenSet.fromString = function (str) {
1381
+ var node = new lunr.TokenSet,
1382
+ root = node,
1383
+ wildcardFound = false
1384
+
1385
+ /*
1386
+ * Iterates through all characters within the passed string
1387
+ * appending a node for each character.
1388
+ *
1389
+ * As soon as a wildcard character is found then a self
1390
+ * referencing edge is introduced to continually match
1391
+ * any number of any characters.
1392
+ */
1393
+ for (var i = 0, len = str.length; i < len; i++) {
1394
+ var char = str[i],
1395
+ final = (i == len - 1)
1396
+
1397
+ if (char == "*") {
1398
+ wildcardFound = true
1399
+ node.edges[char] = node
1400
+ node.final = final
1401
+
1402
+ } else {
1403
+ var next = new lunr.TokenSet
1404
+ next.final = final
1405
+
1406
+ node.edges[char] = next
1407
+ node = next
1408
+
1409
+ // TODO: is this needed anymore?
1410
+ if (wildcardFound) {
1411
+ node.edges["*"] = root
1412
+ }
1413
+ }
1414
+ }
1415
+
1416
+ return root
1417
+ }
1418
+
1419
+ /**
1420
+ * Converts this TokenSet into an array of strings
1421
+ * contained within the TokenSet.
1422
+ *
1423
+ * @returns {string[]}
1424
+ */
1425
+ lunr.TokenSet.prototype.toArray = function () {
1426
+ var words = []
1427
+
1428
+ var stack = [{
1429
+ prefix: "",
1430
+ node: this
1431
+ }]
1432
+
1433
+ while (stack.length) {
1434
+ var frame = stack.pop(),
1435
+ edges = Object.keys(frame.node.edges),
1436
+ len = edges.length
1437
+
1438
+ if (frame.node.final) {
1439
+ words.push(frame.prefix)
1440
+ }
1441
+
1442
+ for (var i = 0; i < len; i++) {
1443
+ var edge = edges[i]
1444
+
1445
+ stack.push({
1446
+ prefix: frame.prefix.concat(edge),
1447
+ node: frame.node.edges[edge]
1448
+ })
1449
+ }
1450
+ }
1451
+
1452
+ return words
1453
+ }
1454
+
1455
+ /**
1456
+ * Generates a string representation of a TokenSet.
1457
+ *
1458
+ * This is intended to allow TokenSets to be used as keys
1459
+ * in objects, largely to aid the construction and minimisation
1460
+ * of a TokenSet. As such it is not designed to be a human
1461
+ * friendly representation of the TokenSet.
1462
+ *
1463
+ * @returns {string}
1464
+ */
1465
+ lunr.TokenSet.prototype.toString = function () {
1466
+ // NOTE: Using Object.keys here as this.edges is very likely
1467
+ // to enter 'hash-mode' with many keys being added
1468
+ //
1469
+ // avoiding a for-in loop here as it leads to the function
1470
+ // being de-optimised (at least in V8). From some simple
1471
+ // benchmarks the performance is comparable, but allowing
1472
+ // V8 to optimize may mean easy performance wins in the future.
1473
+
1474
+ if (this._str) {
1475
+ return this._str
1476
+ }
1477
+
1478
+ var str = this.final ? '1' : '0',
1479
+ labels = Object.keys(this.edges).sort(),
1480
+ len = labels.length
1481
+
1482
+ for (var i = 0; i < len; i++) {
1483
+ var label = labels[i],
1484
+ node = this.edges[label]
1485
+
1486
+ str = str + label + node.id
1487
+ }
1488
+
1489
+ return str
1490
+ }
1491
+
1492
+ /**
1493
+ * Returns a new TokenSet that is the intersection of
1494
+ * this TokenSet and the passed TokenSet.
1495
+ *
1496
+ * This intersection will take into account any wildcards
1497
+ * contained within the TokenSet.
1498
+ *
1499
+ * @param {lunr.TokenSet} b - An other TokenSet to intersect with.
1500
+ * @returns {lunr.TokenSet}
1501
+ */
1502
+ lunr.TokenSet.prototype.intersect = function (b) {
1503
+ var output = new lunr.TokenSet,
1504
+ frame = undefined
1505
+
1506
+ var stack = [{
1507
+ qNode: b,
1508
+ output: output,
1509
+ node: this
1510
+ }]
1511
+
1512
+ while (stack.length) {
1513
+ frame = stack.pop()
1514
+
1515
+ // NOTE: As with the #toString method, we are using
1516
+ // Object.keys and a for loop instead of a for-in loop
1517
+ // as both of these objects enter 'hash' mode, causing
1518
+ // the function to be de-optimised in V8
1519
+ var qEdges = Object.keys(frame.qNode.edges),
1520
+ qLen = qEdges.length,
1521
+ nEdges = Object.keys(frame.node.edges),
1522
+ nLen = nEdges.length
1523
+
1524
+ for (var q = 0; q < qLen; q++) {
1525
+ var qEdge = qEdges[q]
1526
+
1527
+ for (var n = 0; n < nLen; n++) {
1528
+ var nEdge = nEdges[n]
1529
+
1530
+ if (nEdge == qEdge || qEdge == '*') {
1531
+ var node = frame.node.edges[nEdge],
1532
+ qNode = frame.qNode.edges[qEdge],
1533
+ final = node.final && qNode.final,
1534
+ next = undefined
1535
+
1536
+ if (nEdge in frame.output.edges) {
1537
+ // an edge already exists for this character
1538
+ // no need to create a new node, just set the finality
1539
+ // bit unless this node is already final
1540
+ next = frame.output.edges[nEdge]
1541
+ next.final = next.final || final
1542
+
1543
+ } else {
1544
+ // no edge exists yet, must create one
1545
+ // set the finality bit and insert it
1546
+ // into the output
1547
+ next = new lunr.TokenSet
1548
+ next.final = final
1549
+ frame.output.edges[nEdge] = next
1550
+ }
1551
+
1552
+ stack.push({
1553
+ qNode: qNode,
1554
+ output: next,
1555
+ node: node
1556
+ })
1557
+ }
1558
+ }
1559
+ }
1560
+ }
1561
+
1562
+ return output
1563
+ }
1564
+ lunr.TokenSet.Builder = function () {
1565
+ this.previousWord = ""
1566
+ this.root = new lunr.TokenSet
1567
+ this.uncheckedNodes = []
1568
+ this.minimizedNodes = {}
1569
+ }
1570
+
1571
+ lunr.TokenSet.Builder.prototype.insert = function (word) {
1572
+ var node,
1573
+ commonPrefix = 0
1574
+
1575
+ if (word < this.previousWord) {
1576
+ throw new Error ("Out of order word insertion")
1577
+ }
1578
+
1579
+ for (var i = 0; i < word.length && i < this.previousWord.length; i++) {
1580
+ if (word[i] != this.previousWord[i]) break
1581
+ commonPrefix++
1582
+ }
1583
+
1584
+ this.minimize(commonPrefix)
1585
+
1586
+ if (this.uncheckedNodes.length == 0) {
1587
+ node = this.root
1588
+ } else {
1589
+ node = this.uncheckedNodes[this.uncheckedNodes.length - 1].child
1590
+ }
1591
+
1592
+ for (var i = commonPrefix; i < word.length; i++) {
1593
+ var nextNode = new lunr.TokenSet,
1594
+ char = word[i]
1595
+
1596
+ node.edges[char] = nextNode
1597
+
1598
+ this.uncheckedNodes.push({
1599
+ parent: node,
1600
+ char: char,
1601
+ child: nextNode
1602
+ })
1603
+
1604
+ node = nextNode
1605
+ }
1606
+
1607
+ node.final = true
1608
+ this.previousWord = word
1609
+ }
1610
+
1611
+ lunr.TokenSet.Builder.prototype.finish = function () {
1612
+ this.minimize(0)
1613
+ }
1614
+
1615
+ lunr.TokenSet.Builder.prototype.minimize = function (downTo) {
1616
+ for (var i = this.uncheckedNodes.length - 1; i >= downTo; i--) {
1617
+ var node = this.uncheckedNodes[i],
1618
+ childKey = node.child.toString()
1619
+
1620
+ if (childKey in this.minimizedNodes) {
1621
+ node.parent.edges[node.char] = this.minimizedNodes[childKey]
1622
+ } else {
1623
+ // Cache the key for this node since
1624
+ // we know it can't change anymore
1625
+ node.child._str = childKey
1626
+
1627
+ this.minimizedNodes[childKey] = node.child
1628
+ }
1629
+
1630
+ this.uncheckedNodes.pop()
1631
+ }
1632
+ }
1633
+ /*!
1634
+ * lunr.Index
1635
+ * Copyright (C) 2017 Oliver Nightingale
1636
+ */
1637
+
1638
+ /**
1639
+ * An index contains the built index of all documents and provides a query interface
1640
+ * to the index.
1641
+ *
1642
+ * Usually instances of lunr.Index will not be created using this constructor, instead
1643
+ * lunr.Builder should be used to construct new indexes, or lunr.Index.load should be
1644
+ * used to load previously built and serialized indexes.
1645
+ *
1646
+ * @constructor
1647
+ * @param {Object} attrs - The attributes of the built search index.
1648
+ * @param {Object} attrs.invertedIndex - An index of term/field to document reference.
1649
+ * @param {Object<string, lunr.Vector>} attrs.documentVectors - Document vectors keyed by document reference.
1650
+ * @param {lunr.TokenSet} attrs.tokenSet - An set of all corpus tokens.
1651
+ * @param {string[]} attrs.fields - The names of indexed document fields.
1652
+ * @param {lunr.Pipeline} attrs.pipeline - The pipeline to use for search terms.
1653
+ */
1654
+ lunr.Index = function (attrs) {
1655
+ this.invertedIndex = attrs.invertedIndex
1656
+ this.fieldVectors = attrs.fieldVectors
1657
+ this.tokenSet = attrs.tokenSet
1658
+ this.fields = attrs.fields
1659
+ this.pipeline = attrs.pipeline
1660
+ }
1661
+
1662
+ /**
1663
+ * A result contains details of a document matching a search query.
1664
+ * @typedef {Object} lunr.Index~Result
1665
+ * @property {string} ref - The reference of the document this result represents.
1666
+ * @property {number} score - A number between 0 and 1 representing how similar this document is to the query.
1667
+ * @property {lunr.MatchData} matchData - Contains metadata about this match including which term(s) caused the match.
1668
+ */
1669
+
1670
+ /**
1671
+ * Although lunr provides the ability to create queries using lunr.Query, it also provides a simple
1672
+ * query language which itself is parsed into an instance of lunr.Query.
1673
+ *
1674
+ * For programmatically building queries it is advised to directly use lunr.Query, the query language
1675
+ * is best used for human entered text rather than program generated text.
1676
+ *
1677
+ * At its simplest queries can just be a single term, e.g. `hello`, multiple terms are also supported
1678
+ * and will be combined with OR, e.g `hello world` will match documents that contain either 'hello'
1679
+ * or 'world', though those that contain both will rank higher in the results.
1680
+ *
1681
+ * Wildcards can be included in terms to match one or more unspecified characters, these wildcards can
1682
+ * be inserted anywhere within the term, and more than one wildcard can exist in a single term. Adding
1683
+ * wildcards will increase the number of documents that will be found but can also have a negative
1684
+ * impact on query performance, especially with wildcards at the beginning of a term.
1685
+ *
1686
+ * Terms can be restricted to specific fields, e.g. `title:hello`, only documents with the term
1687
+ * hello in the title field will match this query. Using a field not present in the index will lead
1688
+ * to an error being thrown.
1689
+ *
1690
+ * Modifiers can also be added to terms, lunr supports edit distance and boost modifiers on terms. A term
1691
+ * boost will make documents matching that term score higher, e.g. `foo^5`. Edit distance is also supported
1692
+ * to provide fuzzy matching, e.g. 'hello~2' will match documents with hello with an edit distance of 2.
1693
+ * Avoid large values for edit distance to improve query performance.
1694
+ *
1695
+ * To escape special characters the backslash character '\' can be used, this allows searches to include
1696
+ * characters that would normally be considered modifiers, e.g. `foo\~2` will search for a term "foo~2" instead
1697
+ * of attempting to apply a boost of 2 to the search term "foo".
1698
+ *
1699
+ * @typedef {string} lunr.Index~QueryString
1700
+ * @example <caption>Simple single term query</caption>
1701
+ * hello
1702
+ * @example <caption>Multiple term query</caption>
1703
+ * hello world
1704
+ * @example <caption>term scoped to a field</caption>
1705
+ * title:hello
1706
+ * @example <caption>term with a boost of 10</caption>
1707
+ * hello^10
1708
+ * @example <caption>term with an edit distance of 2</caption>
1709
+ * hello~2
1710
+ */
1711
+
1712
+ /**
1713
+ * Performs a search against the index using lunr query syntax.
1714
+ *
1715
+ * Results will be returned sorted by their score, the most relevant results
1716
+ * will be returned first.
1717
+ *
1718
+ * For more programmatic querying use lunr.Index#query.
1719
+ *
1720
+ * @param {lunr.Index~QueryString} queryString - A string containing a lunr query.
1721
+ * @throws {lunr.QueryParseError} If the passed query string cannot be parsed.
1722
+ * @returns {lunr.Index~Result[]}
1723
+ */
1724
+ lunr.Index.prototype.search = function (queryString) {
1725
+ return this.query(function (query) {
1726
+ var parser = new lunr.QueryParser(queryString, query)
1727
+ parser.parse()
1728
+ })
1729
+ }
1730
+
1731
+ /**
1732
+ * A query builder callback provides a query object to be used to express
1733
+ * the query to perform on the index.
1734
+ *
1735
+ * @callback lunr.Index~queryBuilder
1736
+ * @param {lunr.Query} query - The query object to build up.
1737
+ * @this lunr.Query
1738
+ */
1739
+
1740
+ /**
1741
+ * Performs a query against the index using the yielded lunr.Query object.
1742
+ *
1743
+ * If performing programmatic queries against the index, this method is preferred
1744
+ * over lunr.Index#search so as to avoid the additional query parsing overhead.
1745
+ *
1746
+ * A query object is yielded to the supplied function which should be used to
1747
+ * express the query to be run against the index.
1748
+ *
1749
+ * Note that although this function takes a callback parameter it is _not_ an
1750
+ * asynchronous operation, the callback is just yielded a query object to be
1751
+ * customized.
1752
+ *
1753
+ * @param {lunr.Index~queryBuilder} fn - A function that is used to build the query.
1754
+ * @returns {lunr.Index~Result[]}
1755
+ */
1756
+ lunr.Index.prototype.query = function (fn) {
1757
+ // for each query clause
1758
+ // * process terms
1759
+ // * expand terms from token set
1760
+ // * find matching documents and metadata
1761
+ // * get document vectors
1762
+ // * score documents
1763
+
1764
+ var query = new lunr.Query(this.fields),
1765
+ matchingFields = Object.create(null),
1766
+ queryVectors = Object.create(null),
1767
+ termFieldCache = Object.create(null)
1768
+
1769
+ fn.call(query, query)
1770
+
1771
+ for (var i = 0; i < query.clauses.length; i++) {
1772
+ /*
1773
+ * Unless the pipeline has been disabled for this term, which is
1774
+ * the case for terms with wildcards, we need to pass the clause
1775
+ * term through the search pipeline. A pipeline returns an array
1776
+ * of processed terms. Pipeline functions may expand the passed
1777
+ * term, which means we may end up performing multiple index lookups
1778
+ * for a single query term.
1779
+ */
1780
+ var clause = query.clauses[i],
1781
+ terms = null
1782
+
1783
+ if (clause.usePipeline) {
1784
+ terms = this.pipeline.runString(clause.term)
1785
+ } else {
1786
+ terms = [clause.term]
1787
+ }
1788
+
1789
+ for (var m = 0; m < terms.length; m++) {
1790
+ var term = terms[m]
1791
+
1792
+ /*
1793
+ * Each term returned from the pipeline needs to use the same query
1794
+ * clause object, e.g. the same boost and or edit distance. The
1795
+ * simplest way to do this is to re-use the clause object but mutate
1796
+ * its term property.
1797
+ */
1798
+ clause.term = term
1799
+
1800
+ /*
1801
+ * From the term in the clause we create a token set which will then
1802
+ * be used to intersect the indexes token set to get a list of terms
1803
+ * to lookup in the inverted index
1804
+ */
1805
+ var termTokenSet = lunr.TokenSet.fromClause(clause),
1806
+ expandedTerms = this.tokenSet.intersect(termTokenSet).toArray()
1807
+
1808
+ for (var j = 0; j < expandedTerms.length; j++) {
1809
+ /*
1810
+ * For each term get the posting and termIndex, this is required for
1811
+ * building the query vector.
1812
+ */
1813
+ var expandedTerm = expandedTerms[j],
1814
+ posting = this.invertedIndex[expandedTerm],
1815
+ termIndex = posting._index
1816
+
1817
+ for (var k = 0; k < clause.fields.length; k++) {
1818
+ /*
1819
+ * For each field that this query term is scoped by (by default
1820
+ * all fields are in scope) we need to get all the document refs
1821
+ * that have this term in that field.
1822
+ *
1823
+ * The posting is the entry in the invertedIndex for the matching
1824
+ * term from above.
1825
+ */
1826
+ var field = clause.fields[k],
1827
+ fieldPosting = posting[field],
1828
+ matchingDocumentRefs = Object.keys(fieldPosting),
1829
+ termField = expandedTerm + "/" + field
1830
+
1831
+ /*
1832
+ * To support field level boosts a query vector is created per
1833
+ * field. This vector is populated using the termIndex found for
1834
+ * the term and a unit value with the appropriate boost applied.
1835
+ *
1836
+ * If the query vector for this field does not exist yet it needs
1837
+ * to be created.
1838
+ */
1839
+ if (queryVectors[field] === undefined) {
1840
+ queryVectors[field] = new lunr.Vector
1841
+ }
1842
+
1843
+ /*
1844
+ * Using upsert because there could already be an entry in the vector
1845
+ * for the term we are working with. In that case we just add the scores
1846
+ * together.
1847
+ */
1848
+ queryVectors[field].upsert(termIndex, 1 * clause.boost, function (a, b) { return a + b })
1849
+
1850
+ /**
1851
+ * If we've already seen this term, field combo then we've already collected
1852
+ * the matching documents and metadata, no need to go through all that again
1853
+ */
1854
+ if (termFieldCache[termField]) {
1855
+ continue
1856
+ }
1857
+
1858
+ for (var l = 0; l < matchingDocumentRefs.length; l++) {
1859
+ /*
1860
+ * All metadata for this term/field/document triple
1861
+ * are then extracted and collected into an instance
1862
+ * of lunr.MatchData ready to be returned in the query
1863
+ * results
1864
+ */
1865
+ var matchingDocumentRef = matchingDocumentRefs[l],
1866
+ matchingFieldRef = new lunr.FieldRef (matchingDocumentRef, field),
1867
+ metadata = fieldPosting[matchingDocumentRef],
1868
+ fieldMatch
1869
+
1870
+ if ((fieldMatch = matchingFields[matchingFieldRef]) === undefined) {
1871
+ matchingFields[matchingFieldRef] = new lunr.MatchData (expandedTerm, field, metadata)
1872
+ } else {
1873
+ fieldMatch.add(expandedTerm, field, metadata)
1874
+ }
1875
+
1876
+ }
1877
+
1878
+ termFieldCache[termField] = true
1879
+ }
1880
+ }
1881
+ }
1882
+ }
1883
+
1884
+ var matchingFieldRefs = Object.keys(matchingFields),
1885
+ results = [],
1886
+ matches = Object.create(null)
1887
+
1888
+ for (var i = 0; i < matchingFieldRefs.length; i++) {
1889
+ /*
1890
+ * Currently we have document fields that match the query, but we
1891
+ * need to return documents. The matchData and scores are combined
1892
+ * from multiple fields belonging to the same document.
1893
+ *
1894
+ * Scores are calculated by field, using the query vectors created
1895
+ * above, and combined into a final document score using addition.
1896
+ */
1897
+ var fieldRef = lunr.FieldRef.fromString(matchingFieldRefs[i]),
1898
+ docRef = fieldRef.docRef,
1899
+ fieldVector = this.fieldVectors[fieldRef],
1900
+ score = queryVectors[fieldRef.fieldName].similarity(fieldVector),
1901
+ docMatch
1902
+
1903
+ if ((docMatch = matches[docRef]) !== undefined) {
1904
+ docMatch.score += score
1905
+ docMatch.matchData.combine(matchingFields[fieldRef])
1906
+ } else {
1907
+ var match = {
1908
+ ref: docRef,
1909
+ score: score,
1910
+ matchData: matchingFields[fieldRef]
1911
+ }
1912
+ matches[docRef] = match
1913
+ results.push(match)
1914
+ }
1915
+ }
1916
+
1917
+ /*
1918
+ * Sort the results objects by score, highest first.
1919
+ */
1920
+ return results.sort(function (a, b) {
1921
+ return b.score - a.score
1922
+ })
1923
+ }
1924
+
1925
+ /**
1926
+ * Prepares the index for JSON serialization.
1927
+ *
1928
+ * The schema for this JSON blob will be described in a
1929
+ * separate JSON schema file.
1930
+ *
1931
+ * @returns {Object}
1932
+ */
1933
+ lunr.Index.prototype.toJSON = function () {
1934
+ var invertedIndex = Object.keys(this.invertedIndex)
1935
+ .sort()
1936
+ .map(function (term) {
1937
+ return [term, this.invertedIndex[term]]
1938
+ }, this)
1939
+
1940
+ var fieldVectors = Object.keys(this.fieldVectors)
1941
+ .map(function (ref) {
1942
+ return [ref, this.fieldVectors[ref].toJSON()]
1943
+ }, this)
1944
+
1945
+ return {
1946
+ version: lunr.version,
1947
+ fields: this.fields,
1948
+ fieldVectors: fieldVectors,
1949
+ invertedIndex: invertedIndex,
1950
+ pipeline: this.pipeline.toJSON()
1951
+ }
1952
+ }
1953
+
1954
+ /**
1955
+ * Loads a previously serialized lunr.Index
1956
+ *
1957
+ * @param {Object} serializedIndex - A previously serialized lunr.Index
1958
+ * @returns {lunr.Index}
1959
+ */
1960
+ lunr.Index.load = function (serializedIndex) {
1961
+ var attrs = {},
1962
+ fieldVectors = {},
1963
+ serializedVectors = serializedIndex.fieldVectors,
1964
+ invertedIndex = {},
1965
+ serializedInvertedIndex = serializedIndex.invertedIndex,
1966
+ tokenSetBuilder = new lunr.TokenSet.Builder,
1967
+ pipeline = lunr.Pipeline.load(serializedIndex.pipeline)
1968
+
1969
+ if (serializedIndex.version != lunr.version) {
1970
+ lunr.utils.warn("Version mismatch when loading serialised index. Current version of lunr '" + lunr.version + "' does not match serialized index '" + serializedIndex.version + "'")
1971
+ }
1972
+
1973
+ for (var i = 0; i < serializedVectors.length; i++) {
1974
+ var tuple = serializedVectors[i],
1975
+ ref = tuple[0],
1976
+ elements = tuple[1]
1977
+
1978
+ fieldVectors[ref] = new lunr.Vector(elements)
1979
+ }
1980
+
1981
+ for (var i = 0; i < serializedInvertedIndex.length; i++) {
1982
+ var tuple = serializedInvertedIndex[i],
1983
+ term = tuple[0],
1984
+ posting = tuple[1]
1985
+
1986
+ tokenSetBuilder.insert(term)
1987
+ invertedIndex[term] = posting
1988
+ }
1989
+
1990
+ tokenSetBuilder.finish()
1991
+
1992
+ attrs.fields = serializedIndex.fields
1993
+
1994
+ attrs.fieldVectors = fieldVectors
1995
+ attrs.invertedIndex = invertedIndex
1996
+ attrs.tokenSet = tokenSetBuilder.root
1997
+ attrs.pipeline = pipeline
1998
+
1999
+ return new lunr.Index(attrs)
2000
+ }
2001
+ /*!
2002
+ * lunr.Builder
2003
+ * Copyright (C) 2017 Oliver Nightingale
2004
+ */
2005
+
2006
+ /**
2007
+ * lunr.Builder performs indexing on a set of documents and
2008
+ * returns instances of lunr.Index ready for querying.
2009
+ *
2010
+ * All configuration of the index is done via the builder, the
2011
+ * fields to index, the document reference, the text processing
2012
+ * pipeline and document scoring parameters are all set on the
2013
+ * builder before indexing.
2014
+ *
2015
+ * @constructor
2016
+ * @property {string} _ref - Internal reference to the document reference field.
2017
+ * @property {string[]} _fields - Internal reference to the document fields to index.
2018
+ * @property {object} invertedIndex - The inverted index maps terms to document fields.
2019
+ * @property {object} documentTermFrequencies - Keeps track of document term frequencies.
2020
+ * @property {object} documentLengths - Keeps track of the length of documents added to the index.
2021
+ * @property {lunr.tokenizer} tokenizer - Function for splitting strings into tokens for indexing.
2022
+ * @property {lunr.Pipeline} pipeline - The pipeline performs text processing on tokens before indexing.
2023
+ * @property {lunr.Pipeline} searchPipeline - A pipeline for processing search terms before querying the index.
2024
+ * @property {number} documentCount - Keeps track of the total number of documents indexed.
2025
+ * @property {number} _b - A parameter to control field length normalization, setting this to 0 disabled normalization, 1 fully normalizes field lengths, the default value is 0.75.
2026
+ * @property {number} _k1 - A parameter to control how quickly an increase in term frequency results in term frequency saturation, the default value is 1.2.
2027
+ * @property {number} termIndex - A counter incremented for each unique term, used to identify a terms position in the vector space.
2028
+ * @property {array} metadataWhitelist - A list of metadata keys that have been whitelisted for entry in the index.
2029
+ */
2030
+ lunr.Builder = function () {
2031
+ this._ref = "id"
2032
+ this._fields = []
2033
+ this.invertedIndex = Object.create(null)
2034
+ this.fieldTermFrequencies = {}
2035
+ this.fieldLengths = {}
2036
+ this.tokenizer = lunr.tokenizer
2037
+ this.pipeline = new lunr.Pipeline
2038
+ this.searchPipeline = new lunr.Pipeline
2039
+ this.documentCount = 0
2040
+ this._b = 0.75
2041
+ this._k1 = 1.2
2042
+ this.termIndex = 0
2043
+ this.metadataWhitelist = []
2044
+ }
2045
+
2046
+ /**
2047
+ * Sets the document field used as the document reference. Every document must have this field.
2048
+ * The type of this field in the document should be a string, if it is not a string it will be
2049
+ * coerced into a string by calling toString.
2050
+ *
2051
+ * The default ref is 'id'.
2052
+ *
2053
+ * The ref should _not_ be changed during indexing, it should be set before any documents are
2054
+ * added to the index. Changing it during indexing can lead to inconsistent results.
2055
+ *
2056
+ * @param {string} ref - The name of the reference field in the document.
2057
+ */
2058
+ lunr.Builder.prototype.ref = function (ref) {
2059
+ this._ref = ref
2060
+ }
2061
+
2062
+ /**
2063
+ * Adds a field to the list of document fields that will be indexed. Every document being
2064
+ * indexed should have this field. Null values for this field in indexed documents will
2065
+ * not cause errors but will limit the chance of that document being retrieved by searches.
2066
+ *
2067
+ * All fields should be added before adding documents to the index. Adding fields after
2068
+ * a document has been indexed will have no effect on already indexed documents.
2069
+ *
2070
+ * @param {string} field - The name of a field to index in all documents.
2071
+ */
2072
+ lunr.Builder.prototype.field = function (field) {
2073
+ this._fields.push(field)
2074
+ }
2075
+
2076
+ /**
2077
+ * A parameter to tune the amount of field length normalisation that is applied when
2078
+ * calculating relevance scores. A value of 0 will completely disable any normalisation
2079
+ * and a value of 1 will fully normalise field lengths. The default is 0.75. Values of b
2080
+ * will be clamped to the range 0 - 1.
2081
+ *
2082
+ * @param {number} number - The value to set for this tuning parameter.
2083
+ */
2084
+ lunr.Builder.prototype.b = function (number) {
2085
+ if (number < 0) {
2086
+ this._b = 0
2087
+ } else if (number > 1) {
2088
+ this._b = 1
2089
+ } else {
2090
+ this._b = number
2091
+ }
2092
+ }
2093
+
2094
+ /**
2095
+ * A parameter that controls the speed at which a rise in term frequency results in term
2096
+ * frequency saturation. The default value is 1.2. Setting this to a higher value will give
2097
+ * slower saturation levels, a lower value will result in quicker saturation.
2098
+ *
2099
+ * @param {number} number - The value to set for this tuning parameter.
2100
+ */
2101
+ lunr.Builder.prototype.k1 = function (number) {
2102
+ this._k1 = number
2103
+ }
2104
+
2105
+ /**
2106
+ * Adds a document to the index.
2107
+ *
2108
+ * Before adding fields to the index the index should have been fully setup, with the document
2109
+ * ref and all fields to index already having been specified.
2110
+ *
2111
+ * The document must have a field name as specified by the ref (by default this is 'id') and
2112
+ * it should have all fields defined for indexing, though null or undefined values will not
2113
+ * cause errors.
2114
+ *
2115
+ * @param {object} doc - The document to add to the index.
2116
+ */
2117
+ lunr.Builder.prototype.add = function (doc) {
2118
+ var docRef = doc[this._ref]
2119
+
2120
+ this.documentCount += 1
2121
+
2122
+ for (var i = 0; i < this._fields.length; i++) {
2123
+ var fieldName = this._fields[i],
2124
+ field = doc[fieldName],
2125
+ tokens = this.tokenizer(field),
2126
+ terms = this.pipeline.run(tokens),
2127
+ fieldRef = new lunr.FieldRef (docRef, fieldName),
2128
+ fieldTerms = Object.create(null)
2129
+
2130
+ this.fieldTermFrequencies[fieldRef] = fieldTerms
2131
+ this.fieldLengths[fieldRef] = 0
2132
+
2133
+ // store the length of this field for this document
2134
+ this.fieldLengths[fieldRef] += terms.length
2135
+
2136
+ // calculate term frequencies for this field
2137
+ for (var j = 0; j < terms.length; j++) {
2138
+ var term = terms[j]
2139
+
2140
+ if (fieldTerms[term] == undefined) {
2141
+ fieldTerms[term] = 0
2142
+ }
2143
+
2144
+ fieldTerms[term] += 1
2145
+
2146
+ // add to inverted index
2147
+ // create an initial posting if one doesn't exist
2148
+ if (this.invertedIndex[term] == undefined) {
2149
+ var posting = Object.create(null)
2150
+ posting["_index"] = this.termIndex
2151
+ this.termIndex += 1
2152
+
2153
+ for (var k = 0; k < this._fields.length; k++) {
2154
+ posting[this._fields[k]] = Object.create(null)
2155
+ }
2156
+
2157
+ this.invertedIndex[term] = posting
2158
+ }
2159
+
2160
+ // add an entry for this term/fieldName/docRef to the invertedIndex
2161
+ if (this.invertedIndex[term][fieldName][docRef] == undefined) {
2162
+ this.invertedIndex[term][fieldName][docRef] = Object.create(null)
2163
+ }
2164
+
2165
+ // store all whitelisted metadata about this token in the
2166
+ // inverted index
2167
+ for (var l = 0; l < this.metadataWhitelist.length; l++) {
2168
+ var metadataKey = this.metadataWhitelist[l],
2169
+ metadata = term.metadata[metadataKey]
2170
+
2171
+ if (this.invertedIndex[term][fieldName][docRef][metadataKey] == undefined) {
2172
+ this.invertedIndex[term][fieldName][docRef][metadataKey] = []
2173
+ }
2174
+
2175
+ this.invertedIndex[term][fieldName][docRef][metadataKey].push(metadata)
2176
+ }
2177
+ }
2178
+
2179
+ }
2180
+ }
2181
+
2182
+ /**
2183
+ * Calculates the average document length for this index
2184
+ *
2185
+ * @private
2186
+ */
2187
+ lunr.Builder.prototype.calculateAverageFieldLengths = function () {
2188
+
2189
+ var fieldRefs = Object.keys(this.fieldLengths),
2190
+ numberOfFields = fieldRefs.length,
2191
+ accumulator = {},
2192
+ documentsWithField = {}
2193
+
2194
+ for (var i = 0; i < numberOfFields; i++) {
2195
+ var fieldRef = lunr.FieldRef.fromString(fieldRefs[i]),
2196
+ field = fieldRef.fieldName
2197
+
2198
+ documentsWithField[field] || (documentsWithField[field] = 0)
2199
+ documentsWithField[field] += 1
2200
+
2201
+ accumulator[field] || (accumulator[field] = 0)
2202
+ accumulator[field] += this.fieldLengths[fieldRef]
2203
+ }
2204
+
2205
+ for (var i = 0; i < this._fields.length; i++) {
2206
+ var field = this._fields[i]
2207
+ accumulator[field] = accumulator[field] / documentsWithField[field]
2208
+ }
2209
+
2210
+ this.averageFieldLength = accumulator
2211
+ }
2212
+
2213
+ /**
2214
+ * Builds a vector space model of every document using lunr.Vector
2215
+ *
2216
+ * @private
2217
+ */
2218
+ lunr.Builder.prototype.createFieldVectors = function () {
2219
+ var fieldVectors = {},
2220
+ fieldRefs = Object.keys(this.fieldTermFrequencies),
2221
+ fieldRefsLength = fieldRefs.length,
2222
+ termIdfCache = Object.create(null)
2223
+
2224
+ for (var i = 0; i < fieldRefsLength; i++) {
2225
+ var fieldRef = lunr.FieldRef.fromString(fieldRefs[i]),
2226
+ field = fieldRef.fieldName,
2227
+ fieldLength = this.fieldLengths[fieldRef],
2228
+ fieldVector = new lunr.Vector,
2229
+ termFrequencies = this.fieldTermFrequencies[fieldRef],
2230
+ terms = Object.keys(termFrequencies),
2231
+ termsLength = terms.length
2232
+
2233
+ for (var j = 0; j < termsLength; j++) {
2234
+ var term = terms[j],
2235
+ tf = termFrequencies[term],
2236
+ termIndex = this.invertedIndex[term]._index,
2237
+ idf, score, scoreWithPrecision
2238
+
2239
+ if (termIdfCache[term] === undefined) {
2240
+ idf = lunr.idf(this.invertedIndex[term], this.documentCount)
2241
+ termIdfCache[term] = idf
2242
+ } else {
2243
+ idf = termIdfCache[term]
2244
+ }
2245
+
2246
+ score = idf * ((this._k1 + 1) * tf) / (this._k1 * (1 - this._b + this._b * (fieldLength / this.averageFieldLength[field])) + tf)
2247
+ scoreWithPrecision = Math.round(score * 1000) / 1000
2248
+ // Converts 1.23456789 to 1.234.
2249
+ // Reducing the precision so that the vectors take up less
2250
+ // space when serialised. Doing it now so that they behave
2251
+ // the same before and after serialisation. Also, this is
2252
+ // the fastest approach to reducing a number's precision in
2253
+ // JavaScript.
2254
+
2255
+ fieldVector.insert(termIndex, scoreWithPrecision)
2256
+ }
2257
+
2258
+ fieldVectors[fieldRef] = fieldVector
2259
+ }
2260
+
2261
+ this.fieldVectors = fieldVectors
2262
+ }
2263
+
2264
+ /**
2265
+ * Creates a token set of all tokens in the index using lunr.TokenSet
2266
+ *
2267
+ * @private
2268
+ */
2269
+ lunr.Builder.prototype.createTokenSet = function () {
2270
+ this.tokenSet = lunr.TokenSet.fromArray(
2271
+ Object.keys(this.invertedIndex).sort()
2272
+ )
2273
+ }
2274
+
2275
+ /**
2276
+ * Builds the index, creating an instance of lunr.Index.
2277
+ *
2278
+ * This completes the indexing process and should only be called
2279
+ * once all documents have been added to the index.
2280
+ *
2281
+ * @returns {lunr.Index}
2282
+ */
2283
+ lunr.Builder.prototype.build = function () {
2284
+ this.calculateAverageFieldLengths()
2285
+ this.createFieldVectors()
2286
+ this.createTokenSet()
2287
+
2288
+ return new lunr.Index({
2289
+ invertedIndex: this.invertedIndex,
2290
+ fieldVectors: this.fieldVectors,
2291
+ tokenSet: this.tokenSet,
2292
+ fields: this._fields,
2293
+ pipeline: this.searchPipeline
2294
+ })
2295
+ }
2296
+
2297
+ /**
2298
+ * Applies a plugin to the index builder.
2299
+ *
2300
+ * A plugin is a function that is called with the index builder as its context.
2301
+ * Plugins can be used to customise or extend the behaviour of the index
2302
+ * in some way. A plugin is just a function, that encapsulated the custom
2303
+ * behaviour that should be applied when building the index.
2304
+ *
2305
+ * The plugin function will be called with the index builder as its argument, additional
2306
+ * arguments can also be passed when calling use. The function will be called
2307
+ * with the index builder as its context.
2308
+ *
2309
+ * @param {Function} plugin The plugin to apply.
2310
+ */
2311
+ lunr.Builder.prototype.use = function (fn) {
2312
+ var args = Array.prototype.slice.call(arguments, 1)
2313
+ args.unshift(this)
2314
+ fn.apply(this, args)
2315
+ }
2316
+ /**
2317
+ * Contains and collects metadata about a matching document.
2318
+ * A single instance of lunr.MatchData is returned as part of every
2319
+ * lunr.Index~Result.
2320
+ *
2321
+ * @constructor
2322
+ * @param {string} term - The term this match data is associated with
2323
+ * @param {string} field - The field in which the term was found
2324
+ * @param {object} metadata - The metadata recorded about this term in this field
2325
+ * @property {object} metadata - A cloned collection of metadata associated with this document.
2326
+ * @see {@link lunr.Index~Result}
2327
+ */
2328
+ lunr.MatchData = function (term, field, metadata) {
2329
+ var clonedMetadata = Object.create(null),
2330
+ metadataKeys = Object.keys(metadata)
2331
+
2332
+ // Cloning the metadata to prevent the original
2333
+ // being mutated during match data combination.
2334
+ // Metadata is kept in an array within the inverted
2335
+ // index so cloning the data can be done with
2336
+ // Array#slice
2337
+ for (var i = 0; i < metadataKeys.length; i++) {
2338
+ var key = metadataKeys[i]
2339
+ clonedMetadata[key] = metadata[key].slice()
2340
+ }
2341
+
2342
+ this.metadata = Object.create(null)
2343
+ this.metadata[term] = Object.create(null)
2344
+ this.metadata[term][field] = clonedMetadata
2345
+ }
2346
+
2347
+ /**
2348
+ * An instance of lunr.MatchData will be created for every term that matches a
2349
+ * document. However only one instance is required in a lunr.Index~Result. This
2350
+ * method combines metadata from another instance of lunr.MatchData with this
2351
+ * objects metadata.
2352
+ *
2353
+ * @param {lunr.MatchData} otherMatchData - Another instance of match data to merge with this one.
2354
+ * @see {@link lunr.Index~Result}
2355
+ */
2356
+ lunr.MatchData.prototype.combine = function (otherMatchData) {
2357
+ var terms = Object.keys(otherMatchData.metadata)
2358
+
2359
+ for (var i = 0; i < terms.length; i++) {
2360
+ var term = terms[i],
2361
+ fields = Object.keys(otherMatchData.metadata[term])
2362
+
2363
+ if (this.metadata[term] == undefined) {
2364
+ this.metadata[term] = Object.create(null)
2365
+ }
2366
+
2367
+ for (var j = 0; j < fields.length; j++) {
2368
+ var field = fields[j],
2369
+ keys = Object.keys(otherMatchData.metadata[term][field])
2370
+
2371
+ if (this.metadata[term][field] == undefined) {
2372
+ this.metadata[term][field] = Object.create(null)
2373
+ }
2374
+
2375
+ for (var k = 0; k < keys.length; k++) {
2376
+ var key = keys[k]
2377
+
2378
+ if (this.metadata[term][field][key] == undefined) {
2379
+ this.metadata[term][field][key] = otherMatchData.metadata[term][field][key]
2380
+ } else {
2381
+ this.metadata[term][field][key] = this.metadata[term][field][key].concat(otherMatchData.metadata[term][field][key])
2382
+ }
2383
+
2384
+ }
2385
+ }
2386
+ }
2387
+ }
2388
+
2389
+ /**
2390
+ * Add metadata for a term/field pair to this instance of match data.
2391
+ *
2392
+ * @param {string} term - The term this match data is associated with
2393
+ * @param {string} field - The field in which the term was found
2394
+ * @param {object} metadata - The metadata recorded about this term in this field
2395
+ */
2396
+ lunr.MatchData.prototype.add = function (term, field, metadata) {
2397
+ if (!(term in this.metadata)) {
2398
+ this.metadata[term] = Object.create(null)
2399
+ this.metadata[term][field] = metadata
2400
+ return
2401
+ }
2402
+
2403
+ if (!(field in this.metadata[term])) {
2404
+ this.metadata[term][field] = metadata
2405
+ return
2406
+ }
2407
+
2408
+ var metadataKeys = Object.keys(metadata)
2409
+
2410
+ for (var i = 0; i < metadataKeys.length; i++) {
2411
+ var key = metadataKeys[i]
2412
+
2413
+ if (key in this.metadata[term][field]) {
2414
+ this.metadata[term][field][key] = this.metadata[term][field][key].concat(metadata[key])
2415
+ } else {
2416
+ this.metadata[term][field][key] = metadata[key]
2417
+ }
2418
+ }
2419
+ }
2420
+ /**
2421
+ * A lunr.Query provides a programmatic way of defining queries to be performed
2422
+ * against a {@link lunr.Index}.
2423
+ *
2424
+ * Prefer constructing a lunr.Query using the {@link lunr.Index#query} method
2425
+ * so the query object is pre-initialized with the right index fields.
2426
+ *
2427
+ * @constructor
2428
+ * @property {lunr.Query~Clause[]} clauses - An array of query clauses.
2429
+ * @property {string[]} allFields - An array of all available fields in a lunr.Index.
2430
+ */
2431
+ lunr.Query = function (allFields) {
2432
+ this.clauses = []
2433
+ this.allFields = allFields
2434
+ }
2435
+
2436
+ /**
2437
+ * Constants for indicating what kind of automatic wildcard insertion will be used when constructing a query clause.
2438
+ *
2439
+ * This allows wildcards to be added to the beginning and end of a term without having to manually do any string
2440
+ * concatenation.
2441
+ *
2442
+ * The wildcard constants can be bitwise combined to select both leading and trailing wildcards.
2443
+ *
2444
+ * @constant
2445
+ * @default
2446
+ * @property {number} wildcard.NONE - The term will have no wildcards inserted, this is the default behaviour
2447
+ * @property {number} wildcard.LEADING - Prepend the term with a wildcard, unless a leading wildcard already exists
2448
+ * @property {number} wildcard.TRAILING - Append a wildcard to the term, unless a trailing wildcard already exists
2449
+ * @see lunr.Query~Clause
2450
+ * @see lunr.Query#clause
2451
+ * @see lunr.Query#term
2452
+ * @example <caption>query term with trailing wildcard</caption>
2453
+ * query.term('foo', { wildcard: lunr.Query.wildcard.TRAILING })
2454
+ * @example <caption>query term with leading and trailing wildcard</caption>
2455
+ * query.term('foo', {
2456
+ * wildcard: lunr.Query.wildcard.LEADING | lunr.Query.wildcard.TRAILING
2457
+ * })
2458
+ */
2459
+ lunr.Query.wildcard = new String ("*")
2460
+ lunr.Query.wildcard.NONE = 0
2461
+ lunr.Query.wildcard.LEADING = 1
2462
+ lunr.Query.wildcard.TRAILING = 2
2463
+
2464
+ /**
2465
+ * A single clause in a {@link lunr.Query} contains a term and details on how to
2466
+ * match that term against a {@link lunr.Index}.
2467
+ *
2468
+ * @typedef {Object} lunr.Query~Clause
2469
+ * @property {string[]} fields - The fields in an index this clause should be matched against.
2470
+ * @property {number} [boost=1] - Any boost that should be applied when matching this clause.
2471
+ * @property {number} [editDistance] - Whether the term should have fuzzy matching applied, and how fuzzy the match should be.
2472
+ * @property {boolean} [usePipeline] - Whether the term should be passed through the search pipeline.
2473
+ * @property {number} [wildcard=0] - Whether the term should have wildcards appended or prepended.
2474
+ */
2475
+
2476
+ /**
2477
+ * Adds a {@link lunr.Query~Clause} to this query.
2478
+ *
2479
+ * Unless the clause contains the fields to be matched all fields will be matched. In addition
2480
+ * a default boost of 1 is applied to the clause.
2481
+ *
2482
+ * @param {lunr.Query~Clause} clause - The clause to add to this query.
2483
+ * @see lunr.Query~Clause
2484
+ * @returns {lunr.Query}
2485
+ */
2486
+ lunr.Query.prototype.clause = function (clause) {
2487
+ if (!('fields' in clause)) {
2488
+ clause.fields = this.allFields
2489
+ }
2490
+
2491
+ if (!('boost' in clause)) {
2492
+ clause.boost = 1
2493
+ }
2494
+
2495
+ if (!('usePipeline' in clause)) {
2496
+ clause.usePipeline = true
2497
+ }
2498
+
2499
+ if (!('wildcard' in clause)) {
2500
+ clause.wildcard = lunr.Query.wildcard.NONE
2501
+ }
2502
+
2503
+ if ((clause.wildcard & lunr.Query.wildcard.LEADING) && (clause.term.charAt(0) != lunr.Query.wildcard)) {
2504
+ clause.term = "*" + clause.term
2505
+ }
2506
+
2507
+ if ((clause.wildcard & lunr.Query.wildcard.TRAILING) && (clause.term.slice(-1) != lunr.Query.wildcard)) {
2508
+ clause.term = "" + clause.term + "*"
2509
+ }
2510
+
2511
+ this.clauses.push(clause)
2512
+
2513
+ return this
2514
+ }
2515
+
2516
+ /**
2517
+ * Adds a term to the current query, under the covers this will create a {@link lunr.Query~Clause}
2518
+ * to the list of clauses that make up this query.
2519
+ *
2520
+ * @param {string} term - The term to add to the query.
2521
+ * @param {Object} [options] - Any additional properties to add to the query clause.
2522
+ * @returns {lunr.Query}
2523
+ * @see lunr.Query#clause
2524
+ * @see lunr.Query~Clause
2525
+ * @example <caption>adding a single term to a query</caption>
2526
+ * query.term("foo")
2527
+ * @example <caption>adding a single term to a query and specifying search fields, term boost and automatic trailing wildcard</caption>
2528
+ * query.term("foo", {
2529
+ * fields: ["title"],
2530
+ * boost: 10,
2531
+ * wildcard: lunr.Query.wildcard.TRAILING
2532
+ * })
2533
+ */
2534
+ lunr.Query.prototype.term = function (term, options) {
2535
+ var clause = options || {}
2536
+ clause.term = term
2537
+
2538
+ this.clause(clause)
2539
+
2540
+ return this
2541
+ }
2542
+ lunr.QueryParseError = function (message, start, end) {
2543
+ this.name = "QueryParseError"
2544
+ this.message = message
2545
+ this.start = start
2546
+ this.end = end
2547
+ }
2548
+
2549
+ lunr.QueryParseError.prototype = new Error
2550
+ lunr.QueryLexer = function (str) {
2551
+ this.lexemes = []
2552
+ this.str = str
2553
+ this.length = str.length
2554
+ this.pos = 0
2555
+ this.start = 0
2556
+ this.escapeCharPositions = []
2557
+ }
2558
+
2559
+ lunr.QueryLexer.prototype.run = function () {
2560
+ var state = lunr.QueryLexer.lexText
2561
+
2562
+ while (state) {
2563
+ state = state(this)
2564
+ }
2565
+ }
2566
+
2567
+ lunr.QueryLexer.prototype.sliceString = function () {
2568
+ var subSlices = [],
2569
+ sliceStart = this.start,
2570
+ sliceEnd = this.pos
2571
+
2572
+ for (var i = 0; i < this.escapeCharPositions.length; i++) {
2573
+ sliceEnd = this.escapeCharPositions[i]
2574
+ subSlices.push(this.str.slice(sliceStart, sliceEnd))
2575
+ sliceStart = sliceEnd + 1
2576
+ }
2577
+
2578
+ subSlices.push(this.str.slice(sliceStart, this.pos))
2579
+ this.escapeCharPositions.length = 0
2580
+
2581
+ return subSlices.join('')
2582
+ }
2583
+
2584
+ lunr.QueryLexer.prototype.emit = function (type) {
2585
+ this.lexemes.push({
2586
+ type: type,
2587
+ str: this.sliceString(),
2588
+ start: this.start,
2589
+ end: this.pos
2590
+ })
2591
+
2592
+ this.start = this.pos
2593
+ }
2594
+
2595
+ lunr.QueryLexer.prototype.escapeCharacter = function () {
2596
+ this.escapeCharPositions.push(this.pos - 1)
2597
+ this.pos += 1
2598
+ }
2599
+
2600
+ lunr.QueryLexer.prototype.next = function () {
2601
+ if (this.pos >= this.length) {
2602
+ return lunr.QueryLexer.EOS
2603
+ }
2604
+
2605
+ var char = this.str.charAt(this.pos)
2606
+ this.pos += 1
2607
+ return char
2608
+ }
2609
+
2610
+ lunr.QueryLexer.prototype.width = function () {
2611
+ return this.pos - this.start
2612
+ }
2613
+
2614
+ lunr.QueryLexer.prototype.ignore = function () {
2615
+ if (this.start == this.pos) {
2616
+ this.pos += 1
2617
+ }
2618
+
2619
+ this.start = this.pos
2620
+ }
2621
+
2622
+ lunr.QueryLexer.prototype.backup = function () {
2623
+ this.pos -= 1
2624
+ }
2625
+
2626
+ lunr.QueryLexer.prototype.acceptDigitRun = function () {
2627
+ var char, charCode
2628
+
2629
+ do {
2630
+ char = this.next()
2631
+ charCode = char.charCodeAt(0)
2632
+ } while (charCode > 47 && charCode < 58)
2633
+
2634
+ if (char != lunr.QueryLexer.EOS) {
2635
+ this.backup()
2636
+ }
2637
+ }
2638
+
2639
+ lunr.QueryLexer.prototype.more = function () {
2640
+ return this.pos < this.length
2641
+ }
2642
+
2643
+ lunr.QueryLexer.EOS = 'EOS'
2644
+ lunr.QueryLexer.FIELD = 'FIELD'
2645
+ lunr.QueryLexer.TERM = 'TERM'
2646
+ lunr.QueryLexer.EDIT_DISTANCE = 'EDIT_DISTANCE'
2647
+ lunr.QueryLexer.BOOST = 'BOOST'
2648
+
2649
+ lunr.QueryLexer.lexField = function (lexer) {
2650
+ lexer.backup()
2651
+ lexer.emit(lunr.QueryLexer.FIELD)
2652
+ lexer.ignore()
2653
+ return lunr.QueryLexer.lexText
2654
+ }
2655
+
2656
+ lunr.QueryLexer.lexTerm = function (lexer) {
2657
+ if (lexer.width() > 1) {
2658
+ lexer.backup()
2659
+ lexer.emit(lunr.QueryLexer.TERM)
2660
+ }
2661
+
2662
+ lexer.ignore()
2663
+
2664
+ if (lexer.more()) {
2665
+ return lunr.QueryLexer.lexText
2666
+ }
2667
+ }
2668
+
2669
+ lunr.QueryLexer.lexEditDistance = function (lexer) {
2670
+ lexer.ignore()
2671
+ lexer.acceptDigitRun()
2672
+ lexer.emit(lunr.QueryLexer.EDIT_DISTANCE)
2673
+ return lunr.QueryLexer.lexText
2674
+ }
2675
+
2676
+ lunr.QueryLexer.lexBoost = function (lexer) {
2677
+ lexer.ignore()
2678
+ lexer.acceptDigitRun()
2679
+ lexer.emit(lunr.QueryLexer.BOOST)
2680
+ return lunr.QueryLexer.lexText
2681
+ }
2682
+
2683
+ lunr.QueryLexer.lexEOS = function (lexer) {
2684
+ if (lexer.width() > 0) {
2685
+ lexer.emit(lunr.QueryLexer.TERM)
2686
+ }
2687
+ }
2688
+
2689
+ // This matches the separator used when tokenising fields
2690
+ // within a document. These should match otherwise it is
2691
+ // not possible to search for some tokens within a document.
2692
+ //
2693
+ // It is possible for the user to change the separator on the
2694
+ // tokenizer so it _might_ clash with any other of the special
2695
+ // characters already used within the search string, e.g. :.
2696
+ //
2697
+ // This means that it is possible to change the separator in
2698
+ // such a way that makes some words unsearchable using a search
2699
+ // string.
2700
+ lunr.QueryLexer.termSeparator = lunr.tokenizer.separator
2701
+
2702
+ lunr.QueryLexer.lexText = function (lexer) {
2703
+ while (true) {
2704
+ var char = lexer.next()
2705
+
2706
+ if (char == lunr.QueryLexer.EOS) {
2707
+ return lunr.QueryLexer.lexEOS
2708
+ }
2709
+
2710
+ // Escape character is '\'
2711
+ if (char.charCodeAt(0) == 92) {
2712
+ lexer.escapeCharacter()
2713
+ continue
2714
+ }
2715
+
2716
+ if (char == ":") {
2717
+ return lunr.QueryLexer.lexField
2718
+ }
2719
+
2720
+ if (char == "~") {
2721
+ lexer.backup()
2722
+ if (lexer.width() > 0) {
2723
+ lexer.emit(lunr.QueryLexer.TERM)
2724
+ }
2725
+ return lunr.QueryLexer.lexEditDistance
2726
+ }
2727
+
2728
+ if (char == "^") {
2729
+ lexer.backup()
2730
+ if (lexer.width() > 0) {
2731
+ lexer.emit(lunr.QueryLexer.TERM)
2732
+ }
2733
+ return lunr.QueryLexer.lexBoost
2734
+ }
2735
+
2736
+ if (char.match(lunr.QueryLexer.termSeparator)) {
2737
+ return lunr.QueryLexer.lexTerm
2738
+ }
2739
+ }
2740
+ }
2741
+
2742
+ lunr.QueryParser = function (str, query) {
2743
+ this.lexer = new lunr.QueryLexer (str)
2744
+ this.query = query
2745
+ this.currentClause = {}
2746
+ this.lexemeIdx = 0
2747
+ }
2748
+
2749
+ lunr.QueryParser.prototype.parse = function () {
2750
+ this.lexer.run()
2751
+ this.lexemes = this.lexer.lexemes
2752
+
2753
+ var state = lunr.QueryParser.parseFieldOrTerm
2754
+
2755
+ while (state) {
2756
+ state = state(this)
2757
+ }
2758
+
2759
+ return this.query
2760
+ }
2761
+
2762
+ lunr.QueryParser.prototype.peekLexeme = function () {
2763
+ return this.lexemes[this.lexemeIdx]
2764
+ }
2765
+
2766
+ lunr.QueryParser.prototype.consumeLexeme = function () {
2767
+ var lexeme = this.peekLexeme()
2768
+ this.lexemeIdx += 1
2769
+ return lexeme
2770
+ }
2771
+
2772
+ lunr.QueryParser.prototype.nextClause = function () {
2773
+ var completedClause = this.currentClause
2774
+ this.query.clause(completedClause)
2775
+ this.currentClause = {}
2776
+ }
2777
+
2778
+ lunr.QueryParser.parseFieldOrTerm = function (parser) {
2779
+ var lexeme = parser.peekLexeme()
2780
+
2781
+ if (lexeme == undefined) {
2782
+ return
2783
+ }
2784
+
2785
+ switch (lexeme.type) {
2786
+ case lunr.QueryLexer.FIELD:
2787
+ return lunr.QueryParser.parseField
2788
+ case lunr.QueryLexer.TERM:
2789
+ return lunr.QueryParser.parseTerm
2790
+ default:
2791
+ var errorMessage = "expected either a field or a term, found " + lexeme.type
2792
+
2793
+ if (lexeme.str.length >= 1) {
2794
+ errorMessage += " with value '" + lexeme.str + "'"
2795
+ }
2796
+
2797
+ throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)
2798
+ }
2799
+ }
2800
+
2801
+ lunr.QueryParser.parseField = function (parser) {
2802
+ var lexeme = parser.consumeLexeme()
2803
+
2804
+ if (lexeme == undefined) {
2805
+ return
2806
+ }
2807
+
2808
+ if (parser.query.allFields.indexOf(lexeme.str) == -1) {
2809
+ var possibleFields = parser.query.allFields.map(function (f) { return "'" + f + "'" }).join(', '),
2810
+ errorMessage = "unrecognised field '" + lexeme.str + "', possible fields: " + possibleFields
2811
+
2812
+ throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)
2813
+ }
2814
+
2815
+ parser.currentClause.fields = [lexeme.str]
2816
+
2817
+ var nextLexeme = parser.peekLexeme()
2818
+
2819
+ if (nextLexeme == undefined) {
2820
+ var errorMessage = "expecting term, found nothing"
2821
+ throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)
2822
+ }
2823
+
2824
+ switch (nextLexeme.type) {
2825
+ case lunr.QueryLexer.TERM:
2826
+ return lunr.QueryParser.parseTerm
2827
+ default:
2828
+ var errorMessage = "expecting term, found '" + nextLexeme.type + "'"
2829
+ throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)
2830
+ }
2831
+ }
2832
+
2833
+ lunr.QueryParser.parseTerm = function (parser) {
2834
+ var lexeme = parser.consumeLexeme()
2835
+
2836
+ if (lexeme == undefined) {
2837
+ return
2838
+ }
2839
+
2840
+ parser.currentClause.term = lexeme.str.toLowerCase()
2841
+
2842
+ if (lexeme.str.indexOf("*") != -1) {
2843
+ parser.currentClause.usePipeline = false
2844
+ }
2845
+
2846
+ var nextLexeme = parser.peekLexeme()
2847
+
2848
+ if (nextLexeme == undefined) {
2849
+ parser.nextClause()
2850
+ return
2851
+ }
2852
+
2853
+ switch (nextLexeme.type) {
2854
+ case lunr.QueryLexer.TERM:
2855
+ parser.nextClause()
2856
+ return lunr.QueryParser.parseTerm
2857
+ case lunr.QueryLexer.FIELD:
2858
+ parser.nextClause()
2859
+ return lunr.QueryParser.parseField
2860
+ case lunr.QueryLexer.EDIT_DISTANCE:
2861
+ return lunr.QueryParser.parseEditDistance
2862
+ case lunr.QueryLexer.BOOST:
2863
+ return lunr.QueryParser.parseBoost
2864
+ default:
2865
+ var errorMessage = "Unexpected lexeme type '" + nextLexeme.type + "'"
2866
+ throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)
2867
+ }
2868
+ }
2869
+
2870
+ lunr.QueryParser.parseEditDistance = function (parser) {
2871
+ var lexeme = parser.consumeLexeme()
2872
+
2873
+ if (lexeme == undefined) {
2874
+ return
2875
+ }
2876
+
2877
+ var editDistance = parseInt(lexeme.str, 10)
2878
+
2879
+ if (isNaN(editDistance)) {
2880
+ var errorMessage = "edit distance must be numeric"
2881
+ throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)
2882
+ }
2883
+
2884
+ parser.currentClause.editDistance = editDistance
2885
+
2886
+ var nextLexeme = parser.peekLexeme()
2887
+
2888
+ if (nextLexeme == undefined) {
2889
+ parser.nextClause()
2890
+ return
2891
+ }
2892
+
2893
+ switch (nextLexeme.type) {
2894
+ case lunr.QueryLexer.TERM:
2895
+ parser.nextClause()
2896
+ return lunr.QueryParser.parseTerm
2897
+ case lunr.QueryLexer.FIELD:
2898
+ parser.nextClause()
2899
+ return lunr.QueryParser.parseField
2900
+ case lunr.QueryLexer.EDIT_DISTANCE:
2901
+ return lunr.QueryParser.parseEditDistance
2902
+ case lunr.QueryLexer.BOOST:
2903
+ return lunr.QueryParser.parseBoost
2904
+ default:
2905
+ var errorMessage = "Unexpected lexeme type '" + nextLexeme.type + "'"
2906
+ throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)
2907
+ }
2908
+ }
2909
+
2910
+ lunr.QueryParser.parseBoost = function (parser) {
2911
+ var lexeme = parser.consumeLexeme()
2912
+
2913
+ if (lexeme == undefined) {
2914
+ return
2915
+ }
2916
+
2917
+ var boost = parseInt(lexeme.str, 10)
2918
+
2919
+ if (isNaN(boost)) {
2920
+ var errorMessage = "boost must be numeric"
2921
+ throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)
2922
+ }
2923
+
2924
+ parser.currentClause.boost = boost
2925
+
2926
+ var nextLexeme = parser.peekLexeme()
2927
+
2928
+ if (nextLexeme == undefined) {
2929
+ parser.nextClause()
2930
+ return
2931
+ }
2932
+
2933
+ switch (nextLexeme.type) {
2934
+ case lunr.QueryLexer.TERM:
2935
+ parser.nextClause()
2936
+ return lunr.QueryParser.parseTerm
2937
+ case lunr.QueryLexer.FIELD:
2938
+ parser.nextClause()
2939
+ return lunr.QueryParser.parseField
2940
+ case lunr.QueryLexer.EDIT_DISTANCE:
2941
+ return lunr.QueryParser.parseEditDistance
2942
+ case lunr.QueryLexer.BOOST:
2943
+ return lunr.QueryParser.parseBoost
2944
+ default:
2945
+ var errorMessage = "Unexpected lexeme type '" + nextLexeme.type + "'"
2946
+ throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)
2947
+ }
2948
+ }
2949
+
2950
+ /**
2951
+ * export the module via AMD, CommonJS or as a browser global
2952
+ * Export code from https://github.com/umdjs/umd/blob/master/returnExports.js
2953
+ */
2954
+ ;(function (root, factory) {
2955
+ if (typeof define === 'function' && define.amd) {
2956
+ // AMD. Register as an anonymous module.
2957
+ define(factory)
2958
+ } else if (typeof exports === 'object') {
2959
+ /**
2960
+ * Node. Does not work with strict CommonJS, but
2961
+ * only CommonJS-like enviroments that support module.exports,
2962
+ * like Node.
2963
+ */
2964
+ module.exports = factory()
2965
+ } else {
2966
+ // Browser globals (root is window)
2967
+ root.lunr = factory()
2968
+ }
2969
+ }(this, function () {
2970
+ /**
2971
+ * Just return a value to define the module export.
2972
+ * This example returns an object, but the module
2973
+ * can return a function as the exported value.
2974
+ */
2975
+ return lunr
2976
+ }))
2977
+ })();