minimal-mistakes-jekyll 4.13.0 → 4.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +34 -0
- data/LICENSE +21 -0
- data/README.md +33 -17
- data/_data/ui-text.yml +45 -0
- data/_includes/author-profile-custom-links.html +1 -1
- data/_includes/author-profile.html +25 -25
- data/_includes/breadcrumbs.html +3 -3
- data/_includes/comments-providers/scripts.html +2 -0
- data/_includes/comments-providers/utterances.html +20 -0
- data/_includes/comments.html +5 -2
- data/_includes/footer.html +1 -1
- data/_includes/head.html +1 -3
- data/_includes/page__hero.html +6 -1
- data/_includes/scripts.html +4 -4
- data/_includes/search/algolia-search-scripts.html +2 -2
- data/_includes/video +4 -2
- data/_layouts/default.html +7 -7
- data/_layouts/single.html +3 -0
- data/_sass/minimal-mistakes.scss +2 -2
- data/_sass/minimal-mistakes/_archive.scss +70 -0
- data/_sass/minimal-mistakes/_base.scss +1 -1
- data/_sass/minimal-mistakes/_mixins.scss +2 -2
- data/_sass/minimal-mistakes/_navigation.scss +6 -12
- data/_sass/minimal-mistakes/_search.scss +5 -4
- data/_sass/minimal-mistakes/_utilities.scss +10 -2
- data/_sass/minimal-mistakes/_variables.scss +7 -6
- data/_sass/minimal-mistakes/skins/_dark.scss +4 -18
- data/_sass/minimal-mistakes/skins/_neon.scss +4 -0
- data/_sass/minimal-mistakes/skins/_plum.scss +4 -0
- data/assets/js/lunr/lunr-store.js +4 -2
- data/assets/js/lunr/lunr.js +591 -84
- data/assets/js/lunr/lunr.min.js +3 -3
- data/assets/js/main.min.js +1 -1
- metadata +18 -4
- data/LICENSE.txt +0 -63
- data/_includes/base_path +0 -5
@@ -11,16 +11,17 @@
|
|
11
11
|
.search__toggle {
|
12
12
|
margin-left: 1rem;
|
13
13
|
margin-right: 1rem;
|
14
|
+
height: $nav-toggle-height;
|
14
15
|
border: 0;
|
15
16
|
outline: none;
|
16
|
-
color: $
|
17
|
+
color: $primary-color;
|
17
18
|
background-color: transparent;
|
18
19
|
cursor: pointer;
|
19
20
|
-webkit-transition: 0.2s;
|
20
21
|
transition: 0.2s;
|
21
22
|
|
22
23
|
&:hover {
|
23
|
-
color: $
|
24
|
+
color: mix(#000, $primary-color, 25%);
|
24
25
|
}
|
25
26
|
}
|
26
27
|
|
@@ -114,12 +115,12 @@
|
|
114
115
|
}
|
115
116
|
|
116
117
|
.archive__item-title .ais-Highlight {
|
117
|
-
color: $
|
118
|
+
color: $primary-color;
|
118
119
|
font-style: normal;
|
119
120
|
text-decoration: underline;
|
120
121
|
}
|
121
122
|
.archive__item-excerpt .ais-Highlight {
|
122
|
-
color: $
|
123
|
+
color: $primary-color;
|
123
124
|
font-style: normal;
|
124
125
|
font-weight: bold;
|
125
126
|
}
|
@@ -315,7 +315,7 @@ body:hover .visually-hidden button {
|
|
315
315
|
position: relative;
|
316
316
|
width: $navicon-width;
|
317
317
|
height: $navicon-height;
|
318
|
-
background:
|
318
|
+
background: $primary-color;
|
319
319
|
margin: auto;
|
320
320
|
-webkit-transition: 0.3s;
|
321
321
|
transition: 0.3s;
|
@@ -327,7 +327,7 @@ body:hover .visually-hidden button {
|
|
327
327
|
left: 0;
|
328
328
|
width: $navicon-width;
|
329
329
|
height: $navicon-height;
|
330
|
-
background:
|
330
|
+
background: $primary-color;
|
331
331
|
-webkit-transition: 0.3s;
|
332
332
|
transition: 0.3s;
|
333
333
|
}
|
@@ -366,6 +366,14 @@ body:hover .visually-hidden button {
|
|
366
366
|
}
|
367
367
|
}
|
368
368
|
|
369
|
+
.greedy-nav__toggle:hover {
|
370
|
+
.navicon,
|
371
|
+
.navicon:before,
|
372
|
+
.navicon:after {
|
373
|
+
background: mix(#000, $primary-color, 25%);
|
374
|
+
}
|
375
|
+
}
|
376
|
+
|
369
377
|
/*
|
370
378
|
Sticky, fixed to top content
|
371
379
|
========================================================================== */
|
@@ -62,11 +62,12 @@ $border-color: $lighter-gray !default;
|
|
62
62
|
$form-background-color: $lighter-gray !default;
|
63
63
|
$footer-background-color: $lighter-gray !default;
|
64
64
|
|
65
|
-
$primary-color: #
|
66
|
-
$success-color: #
|
67
|
-
$warning-color: #
|
65
|
+
$primary-color: #6f777d !default;
|
66
|
+
$success-color: #3fa63f !default;
|
67
|
+
$warning-color: #d67f05 !default;
|
68
68
|
$danger-color: #ee5f5b !default;
|
69
|
-
$info-color: #
|
69
|
+
$info-color: #3b9cba !default;
|
70
|
+
$focus-color: $primary-color !default;
|
70
71
|
|
71
72
|
/* YIQ color contrast */
|
72
73
|
$yiq-contrasted-dark-default: $dark-gray !default;
|
@@ -101,9 +102,9 @@ $youtube-color: #bb0000 !default;
|
|
101
102
|
$xing-color: #006567 !default;
|
102
103
|
|
103
104
|
/* links */
|
104
|
-
$link-color: $info-color !default;
|
105
|
+
$link-color: mix(#000, $info-color, 15%) !default;
|
105
106
|
$link-color-hover: mix(#000, $link-color, 25%) !default;
|
106
|
-
$link-color-visited: mix(#fff, $link-color,
|
107
|
+
$link-color-visited: mix(#fff, $link-color, 15%) !default;
|
107
108
|
$masthead-link-color: $primary-color !default;
|
108
109
|
$masthead-link-color-hover: mix(#000, $primary-color, 25%) !default;
|
109
110
|
$navicon-link-color-hover: mix(#fff, $primary-color, 75%) !default;
|
@@ -18,25 +18,11 @@ $masthead-link-color: $text-color !default;
|
|
18
18
|
$masthead-link-color-hover: mix(#000, $text-color, 20%) !default;
|
19
19
|
$navicon-link-color-hover: mix(#000, $background-color, 30%) !default;
|
20
20
|
|
21
|
-
/* dark syntax highlighting (base16) */
|
22
|
-
$base00: #ffffff !default;
|
23
|
-
$base01: #e0e0e0 !default;
|
24
|
-
$base02: #d0d0d0 !default;
|
25
|
-
$base03: #b0b0b0 !default;
|
26
|
-
$base04: #000000 !default;
|
27
|
-
$base05: #101010 !default;
|
28
|
-
$base06: #151515 !default;
|
29
|
-
$base07: #202020 !default;
|
30
|
-
$base08: #ff0086 !default;
|
31
|
-
$base09: #fd8900 !default;
|
32
|
-
$base0a: #aba800 !default;
|
33
|
-
$base0b: #00c918 !default;
|
34
|
-
$base0c: #1faaaa !default;
|
35
|
-
$base0d: #3777e6 !default;
|
36
|
-
$base0e: #ad00a1 !default;
|
37
|
-
$base0f: #cc6633 !default;
|
38
|
-
|
39
21
|
.author__urls.social-icons .svg-inline--fa,
|
40
22
|
.page__footer-follow .social-icons .svg-inline--fa {
|
41
23
|
color: inherit;
|
42
24
|
}
|
25
|
+
|
26
|
+
.ais-search-box .ais-search-box--input {
|
27
|
+
background-color: $form-background-color;
|
28
|
+
}
|
@@ -18,7 +18,8 @@ var store = [
|
|
18
18
|
"title": {{ doc.title | jsonify }},
|
19
19
|
"excerpt":
|
20
20
|
{%- if site.search_full_content == true -%}
|
21
|
-
{{ doc.content |
|
21
|
+
{{ doc.content | newline_to_br |
|
22
|
+
replace:"<br />", " " |
|
22
23
|
replace:"</p>", " " |
|
23
24
|
replace:"</h1>", " " |
|
24
25
|
replace:"</h2>", " " |
|
@@ -28,7 +29,8 @@ var store = [
|
|
28
29
|
replace:"</h6>", " "|
|
29
30
|
strip_html | strip_newlines | jsonify }},
|
30
31
|
{%- else -%}
|
31
|
-
{{ doc.content |
|
32
|
+
{{ doc.content | newline_to_br |
|
33
|
+
replace:"<br />", " " |
|
32
34
|
replace:"</p>", " " |
|
33
35
|
replace:"</h1>", " " |
|
34
36
|
replace:"</h2>", " " |
|
data/assets/js/lunr/lunr.js
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
/**
|
2
|
-
* lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.
|
3
|
-
* Copyright (C)
|
2
|
+
* lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.3
|
3
|
+
* Copyright (C) 2018 Oliver Nightingale
|
4
4
|
* @license MIT
|
5
5
|
*/
|
6
6
|
|
@@ -54,14 +54,15 @@ var lunr = function (config) {
|
|
54
54
|
return builder.build()
|
55
55
|
}
|
56
56
|
|
57
|
-
lunr.version = "2.
|
57
|
+
lunr.version = "2.3.3"
|
58
58
|
/*!
|
59
59
|
* lunr.utils
|
60
|
-
* Copyright (C)
|
60
|
+
* Copyright (C) 2018 Oliver Nightingale
|
61
61
|
*/
|
62
62
|
|
63
63
|
/**
|
64
64
|
* A namespace containing utils for the rest of the lunr library
|
65
|
+
* @namespace lunr.utils
|
65
66
|
*/
|
66
67
|
lunr.utils = {}
|
67
68
|
|
@@ -69,7 +70,8 @@ lunr.utils = {}
|
|
69
70
|
* Print a warning message to the console.
|
70
71
|
*
|
71
72
|
* @param {String} message The message to be printed.
|
72
|
-
* @memberOf
|
73
|
+
* @memberOf lunr.utils
|
74
|
+
* @function
|
73
75
|
*/
|
74
76
|
lunr.utils.warn = (function (global) {
|
75
77
|
/* eslint-disable no-console */
|
@@ -90,7 +92,7 @@ lunr.utils.warn = (function (global) {
|
|
90
92
|
*
|
91
93
|
* @param {Any} obj The object to convert to a string.
|
92
94
|
* @return {String} string representation of the passed object.
|
93
|
-
* @memberOf
|
95
|
+
* @memberOf lunr.utils
|
94
96
|
*/
|
95
97
|
lunr.utils.asString = function (obj) {
|
96
98
|
if (obj === void 0 || obj === null) {
|
@@ -99,6 +101,52 @@ lunr.utils.asString = function (obj) {
|
|
99
101
|
return obj.toString()
|
100
102
|
}
|
101
103
|
}
|
104
|
+
|
105
|
+
/**
|
106
|
+
* Clones an object.
|
107
|
+
*
|
108
|
+
* Will create a copy of an existing object such that any mutations
|
109
|
+
* on the copy cannot affect the original.
|
110
|
+
*
|
111
|
+
* Only shallow objects are supported, passing a nested object to this
|
112
|
+
* function will cause a TypeError.
|
113
|
+
*
|
114
|
+
* Objects with primitives, and arrays of primitives are supported.
|
115
|
+
*
|
116
|
+
* @param {Object} obj The object to clone.
|
117
|
+
* @return {Object} a clone of the passed object.
|
118
|
+
* @throws {TypeError} when a nested object is passed.
|
119
|
+
* @memberOf Utils
|
120
|
+
*/
|
121
|
+
lunr.utils.clone = function (obj) {
|
122
|
+
if (obj === null || obj === undefined) {
|
123
|
+
return obj
|
124
|
+
}
|
125
|
+
|
126
|
+
var clone = Object.create(null),
|
127
|
+
keys = Object.keys(obj)
|
128
|
+
|
129
|
+
for (var i = 0; i < keys.length; i++) {
|
130
|
+
var key = keys[i],
|
131
|
+
val = obj[key]
|
132
|
+
|
133
|
+
if (Array.isArray(val)) {
|
134
|
+
clone[key] = val.slice()
|
135
|
+
continue
|
136
|
+
}
|
137
|
+
|
138
|
+
if (typeof val === 'string' ||
|
139
|
+
typeof val === 'number' ||
|
140
|
+
typeof val === 'boolean') {
|
141
|
+
clone[key] = val
|
142
|
+
continue
|
143
|
+
}
|
144
|
+
|
145
|
+
throw new TypeError("clone is not deep and does not support nested objects")
|
146
|
+
}
|
147
|
+
|
148
|
+
return clone
|
149
|
+
}
|
102
150
|
lunr.FieldRef = function (docRef, fieldName, stringValue) {
|
103
151
|
this.docRef = docRef
|
104
152
|
this.fieldName = fieldName
|
@@ -127,6 +175,139 @@ lunr.FieldRef.prototype.toString = function () {
|
|
127
175
|
|
128
176
|
return this._stringValue
|
129
177
|
}
|
178
|
+
/*!
|
179
|
+
* lunr.Set
|
180
|
+
* Copyright (C) 2018 Oliver Nightingale
|
181
|
+
*/
|
182
|
+
|
183
|
+
/**
|
184
|
+
* A lunr set.
|
185
|
+
*
|
186
|
+
* @constructor
|
187
|
+
*/
|
188
|
+
lunr.Set = function (elements) {
|
189
|
+
this.elements = Object.create(null)
|
190
|
+
|
191
|
+
if (elements) {
|
192
|
+
this.length = elements.length
|
193
|
+
|
194
|
+
for (var i = 0; i < this.length; i++) {
|
195
|
+
this.elements[elements[i]] = true
|
196
|
+
}
|
197
|
+
} else {
|
198
|
+
this.length = 0
|
199
|
+
}
|
200
|
+
}
|
201
|
+
|
202
|
+
/**
|
203
|
+
* A complete set that contains all elements.
|
204
|
+
*
|
205
|
+
* @static
|
206
|
+
* @readonly
|
207
|
+
* @type {lunr.Set}
|
208
|
+
*/
|
209
|
+
lunr.Set.complete = {
|
210
|
+
intersect: function (other) {
|
211
|
+
return other
|
212
|
+
},
|
213
|
+
|
214
|
+
union: function (other) {
|
215
|
+
return other
|
216
|
+
},
|
217
|
+
|
218
|
+
contains: function () {
|
219
|
+
return true
|
220
|
+
}
|
221
|
+
}
|
222
|
+
|
223
|
+
/**
|
224
|
+
* An empty set that contains no elements.
|
225
|
+
*
|
226
|
+
* @static
|
227
|
+
* @readonly
|
228
|
+
* @type {lunr.Set}
|
229
|
+
*/
|
230
|
+
lunr.Set.empty = {
|
231
|
+
intersect: function () {
|
232
|
+
return this
|
233
|
+
},
|
234
|
+
|
235
|
+
union: function (other) {
|
236
|
+
return other
|
237
|
+
},
|
238
|
+
|
239
|
+
contains: function () {
|
240
|
+
return false
|
241
|
+
}
|
242
|
+
}
|
243
|
+
|
244
|
+
/**
|
245
|
+
* Returns true if this set contains the specified object.
|
246
|
+
*
|
247
|
+
* @param {object} object - Object whose presence in this set is to be tested.
|
248
|
+
* @returns {boolean} - True if this set contains the specified object.
|
249
|
+
*/
|
250
|
+
lunr.Set.prototype.contains = function (object) {
|
251
|
+
return !!this.elements[object]
|
252
|
+
}
|
253
|
+
|
254
|
+
/**
|
255
|
+
* Returns a new set containing only the elements that are present in both
|
256
|
+
* this set and the specified set.
|
257
|
+
*
|
258
|
+
* @param {lunr.Set} other - set to intersect with this set.
|
259
|
+
* @returns {lunr.Set} a new set that is the intersection of this and the specified set.
|
260
|
+
*/
|
261
|
+
|
262
|
+
lunr.Set.prototype.intersect = function (other) {
|
263
|
+
var a, b, elements, intersection = []
|
264
|
+
|
265
|
+
if (other === lunr.Set.complete) {
|
266
|
+
return this
|
267
|
+
}
|
268
|
+
|
269
|
+
if (other === lunr.Set.empty) {
|
270
|
+
return other
|
271
|
+
}
|
272
|
+
|
273
|
+
if (this.length < other.length) {
|
274
|
+
a = this
|
275
|
+
b = other
|
276
|
+
} else {
|
277
|
+
a = other
|
278
|
+
b = this
|
279
|
+
}
|
280
|
+
|
281
|
+
elements = Object.keys(a.elements)
|
282
|
+
|
283
|
+
for (var i = 0; i < elements.length; i++) {
|
284
|
+
var element = elements[i]
|
285
|
+
if (element in b.elements) {
|
286
|
+
intersection.push(element)
|
287
|
+
}
|
288
|
+
}
|
289
|
+
|
290
|
+
return new lunr.Set (intersection)
|
291
|
+
}
|
292
|
+
|
293
|
+
/**
|
294
|
+
* Returns a new set combining the elements of this and the specified set.
|
295
|
+
*
|
296
|
+
* @param {lunr.Set} other - set to union with this set.
|
297
|
+
* @return {lunr.Set} a new set that is the union of this and the specified set.
|
298
|
+
*/
|
299
|
+
|
300
|
+
lunr.Set.prototype.union = function (other) {
|
301
|
+
if (other === lunr.Set.complete) {
|
302
|
+
return lunr.Set.complete
|
303
|
+
}
|
304
|
+
|
305
|
+
if (other === lunr.Set.empty) {
|
306
|
+
return this
|
307
|
+
}
|
308
|
+
|
309
|
+
return new lunr.Set(Object.keys(this.elements).concat(Object.keys(other.elements)))
|
310
|
+
}
|
130
311
|
/**
|
131
312
|
* A function to calculate the inverse document frequency for
|
132
313
|
* a posting. This is shared between the builder and the index
|
@@ -208,7 +389,7 @@ lunr.Token.prototype.clone = function (fn) {
|
|
208
389
|
}
|
209
390
|
/*!
|
210
391
|
* lunr.tokenizer
|
211
|
-
* Copyright (C)
|
392
|
+
* Copyright (C) 2018 Oliver Nightingale
|
212
393
|
*/
|
213
394
|
|
214
395
|
/**
|
@@ -220,18 +401,26 @@ lunr.Token.prototype.clone = function (fn) {
|
|
220
401
|
* then will split this string on the character in `lunr.tokenizer.separator`.
|
221
402
|
* Arrays will have their elements converted to strings and wrapped in a lunr.Token.
|
222
403
|
*
|
404
|
+
* Optional metadata can be passed to the tokenizer, this metadata will be cloned and
|
405
|
+
* added as metadata to every token that is created from the object to be tokenized.
|
406
|
+
*
|
223
407
|
* @static
|
224
408
|
* @param {?(string|object|object[])} obj - The object to convert into tokens
|
409
|
+
* @param {?object} metadata - Optional metadata to associate with every token
|
225
410
|
* @returns {lunr.Token[]}
|
411
|
+
* @see {@link lunr.Pipeline}
|
226
412
|
*/
|
227
|
-
lunr.tokenizer = function (obj) {
|
413
|
+
lunr.tokenizer = function (obj, metadata) {
|
228
414
|
if (obj == null || obj == undefined) {
|
229
415
|
return []
|
230
416
|
}
|
231
417
|
|
232
418
|
if (Array.isArray(obj)) {
|
233
419
|
return obj.map(function (t) {
|
234
|
-
return new lunr.Token(
|
420
|
+
return new lunr.Token(
|
421
|
+
lunr.utils.asString(t).toLowerCase(),
|
422
|
+
lunr.utils.clone(metadata)
|
423
|
+
)
|
235
424
|
})
|
236
425
|
}
|
237
426
|
|
@@ -246,11 +435,15 @@ lunr.tokenizer = function (obj) {
|
|
246
435
|
if ((char.match(lunr.tokenizer.separator) || sliceEnd == len)) {
|
247
436
|
|
248
437
|
if (sliceLength > 0) {
|
438
|
+
var tokenMetadata = lunr.utils.clone(metadata) || {}
|
439
|
+
tokenMetadata["position"] = [sliceStart, sliceLength]
|
440
|
+
tokenMetadata["index"] = tokens.length
|
441
|
+
|
249
442
|
tokens.push(
|
250
|
-
new lunr.Token (
|
251
|
-
|
252
|
-
|
253
|
-
|
443
|
+
new lunr.Token (
|
444
|
+
str.slice(sliceStart, sliceEnd),
|
445
|
+
tokenMetadata
|
446
|
+
)
|
254
447
|
)
|
255
448
|
}
|
256
449
|
|
@@ -272,7 +465,7 @@ lunr.tokenizer = function (obj) {
|
|
272
465
|
lunr.tokenizer.separator = /[\s\-]+/
|
273
466
|
/*!
|
274
467
|
* lunr.Pipeline
|
275
|
-
* Copyright (C)
|
468
|
+
* Copyright (C) 2018 Oliver Nightingale
|
276
469
|
*/
|
277
470
|
|
278
471
|
/**
|
@@ -475,14 +668,23 @@ lunr.Pipeline.prototype.run = function (tokens) {
|
|
475
668
|
|
476
669
|
for (var i = 0; i < stackLength; i++) {
|
477
670
|
var fn = this._stack[i]
|
671
|
+
var memo = []
|
478
672
|
|
479
|
-
|
480
|
-
var result = fn(
|
673
|
+
for (var j = 0; j < tokens.length; j++) {
|
674
|
+
var result = fn(tokens[j], j, tokens)
|
481
675
|
|
482
|
-
if (result === void 0 || result === '')
|
676
|
+
if (result === void 0 || result === '') continue
|
677
|
+
|
678
|
+
if (result instanceof Array) {
|
679
|
+
for (var k = 0; k < result.length; k++) {
|
680
|
+
memo.push(result[k])
|
681
|
+
}
|
682
|
+
} else {
|
683
|
+
memo.push(result)
|
684
|
+
}
|
685
|
+
}
|
483
686
|
|
484
|
-
|
485
|
-
}, [])
|
687
|
+
tokens = memo
|
486
688
|
}
|
487
689
|
|
488
690
|
return tokens
|
@@ -494,10 +696,12 @@ lunr.Pipeline.prototype.run = function (tokens) {
|
|
494
696
|
* token and mapping the resulting tokens back to strings.
|
495
697
|
*
|
496
698
|
* @param {string} str - The string to pass through the pipeline.
|
699
|
+
* @param {?object} metadata - Optional metadata to associate with the token
|
700
|
+
* passed to the pipeline.
|
497
701
|
* @returns {string[]}
|
498
702
|
*/
|
499
|
-
lunr.Pipeline.prototype.runString = function (str) {
|
500
|
-
var token = new lunr.Token (str)
|
703
|
+
lunr.Pipeline.prototype.runString = function (str, metadata) {
|
704
|
+
var token = new lunr.Token (str, metadata)
|
501
705
|
|
502
706
|
return this.run([token]).map(function (t) {
|
503
707
|
return t.toString()
|
@@ -528,7 +732,7 @@ lunr.Pipeline.prototype.toJSON = function () {
|
|
528
732
|
}
|
529
733
|
/*!
|
530
734
|
* lunr.Vector
|
531
|
-
* Copyright (C)
|
735
|
+
* Copyright (C) 2018 Oliver Nightingale
|
532
736
|
*/
|
533
737
|
|
534
738
|
/**
|
@@ -689,15 +893,14 @@ lunr.Vector.prototype.dot = function (otherVector) {
|
|
689
893
|
}
|
690
894
|
|
691
895
|
/**
|
692
|
-
* Calculates the
|
693
|
-
* vector.
|
896
|
+
* Calculates the similarity between this vector and another vector.
|
694
897
|
*
|
695
898
|
* @param {lunr.Vector} otherVector - The other vector to calculate the
|
696
899
|
* similarity with.
|
697
900
|
* @returns {Number}
|
698
901
|
*/
|
699
902
|
lunr.Vector.prototype.similarity = function (otherVector) {
|
700
|
-
return this.dot(otherVector) /
|
903
|
+
return this.dot(otherVector) / this.magnitude() || 0
|
701
904
|
}
|
702
905
|
|
703
906
|
/**
|
@@ -726,7 +929,7 @@ lunr.Vector.prototype.toJSON = function () {
|
|
726
929
|
/* eslint-disable */
|
727
930
|
/*!
|
728
931
|
* lunr.stemmer
|
729
|
-
* Copyright (C)
|
932
|
+
* Copyright (C) 2018 Oliver Nightingale
|
730
933
|
* Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt
|
731
934
|
*/
|
732
935
|
|
@@ -739,6 +942,7 @@ lunr.Vector.prototype.toJSON = function () {
|
|
739
942
|
* @param {lunr.Token} token - The string to stem
|
740
943
|
* @returns {lunr.Token}
|
741
944
|
* @see {@link lunr.Pipeline}
|
945
|
+
* @function
|
742
946
|
*/
|
743
947
|
lunr.stemmer = (function(){
|
744
948
|
var step2list = {
|
@@ -947,7 +1151,7 @@ lunr.stemmer = (function(){
|
|
947
1151
|
lunr.Pipeline.registerFunction(lunr.stemmer, 'stemmer')
|
948
1152
|
/*!
|
949
1153
|
* lunr.stopWordFilter
|
950
|
-
* Copyright (C)
|
1154
|
+
* Copyright (C) 2018 Oliver Nightingale
|
951
1155
|
*/
|
952
1156
|
|
953
1157
|
/**
|
@@ -957,6 +1161,7 @@ lunr.Pipeline.registerFunction(lunr.stemmer, 'stemmer')
|
|
957
1161
|
* The built in lunr.stopWordFilter is built using this generator and can be used
|
958
1162
|
* to generate custom stopWordFilters for applications or non English languages.
|
959
1163
|
*
|
1164
|
+
* @function
|
960
1165
|
* @param {Array} token The token to pass through the filter
|
961
1166
|
* @returns {lunr.PipelineFunction}
|
962
1167
|
* @see lunr.Pipeline
|
@@ -980,6 +1185,7 @@ lunr.generateStopWordFilter = function (stopWords) {
|
|
980
1185
|
* This is intended to be used in the Pipeline. If the token does not pass the
|
981
1186
|
* filter then undefined will be returned.
|
982
1187
|
*
|
1188
|
+
* @function
|
983
1189
|
* @implements {lunr.PipelineFunction}
|
984
1190
|
* @params {lunr.Token} token - A token to check for being a stop word.
|
985
1191
|
* @returns {lunr.Token}
|
@@ -1110,7 +1316,7 @@ lunr.stopWordFilter = lunr.generateStopWordFilter([
|
|
1110
1316
|
lunr.Pipeline.registerFunction(lunr.stopWordFilter, 'stopWordFilter')
|
1111
1317
|
/*!
|
1112
1318
|
* lunr.trimmer
|
1113
|
-
* Copyright (C)
|
1319
|
+
* Copyright (C) 2018 Oliver Nightingale
|
1114
1320
|
*/
|
1115
1321
|
|
1116
1322
|
/**
|
@@ -1137,7 +1343,7 @@ lunr.trimmer = function (token) {
|
|
1137
1343
|
lunr.Pipeline.registerFunction(lunr.trimmer, 'trimmer')
|
1138
1344
|
/*!
|
1139
1345
|
* lunr.TokenSet
|
1140
|
-
* Copyright (C)
|
1346
|
+
* Copyright (C) 2018 Oliver Nightingale
|
1141
1347
|
*/
|
1142
1348
|
|
1143
1349
|
/**
|
@@ -1379,14 +1585,13 @@ lunr.TokenSet.fromFuzzyString = function (str, editDistance) {
|
|
1379
1585
|
*/
|
1380
1586
|
lunr.TokenSet.fromString = function (str) {
|
1381
1587
|
var node = new lunr.TokenSet,
|
1382
|
-
root = node
|
1383
|
-
wildcardFound = false
|
1588
|
+
root = node
|
1384
1589
|
|
1385
1590
|
/*
|
1386
1591
|
* Iterates through all characters within the passed string
|
1387
1592
|
* appending a node for each character.
|
1388
1593
|
*
|
1389
|
-
*
|
1594
|
+
* When a wildcard character is found then a self
|
1390
1595
|
* referencing edge is introduced to continually match
|
1391
1596
|
* any number of any characters.
|
1392
1597
|
*/
|
@@ -1395,7 +1600,6 @@ lunr.TokenSet.fromString = function (str) {
|
|
1395
1600
|
final = (i == len - 1)
|
1396
1601
|
|
1397
1602
|
if (char == "*") {
|
1398
|
-
wildcardFound = true
|
1399
1603
|
node.edges[char] = node
|
1400
1604
|
node.final = final
|
1401
1605
|
|
@@ -1405,11 +1609,6 @@ lunr.TokenSet.fromString = function (str) {
|
|
1405
1609
|
|
1406
1610
|
node.edges[char] = next
|
1407
1611
|
node = next
|
1408
|
-
|
1409
|
-
// TODO: is this needed anymore?
|
1410
|
-
if (wildcardFound) {
|
1411
|
-
node.edges["*"] = root
|
1412
|
-
}
|
1413
1612
|
}
|
1414
1613
|
}
|
1415
1614
|
|
@@ -1436,6 +1635,11 @@ lunr.TokenSet.prototype.toArray = function () {
|
|
1436
1635
|
len = edges.length
|
1437
1636
|
|
1438
1637
|
if (frame.node.final) {
|
1638
|
+
/* In Safari, at this point the prefix is sometimes corrupted, see:
|
1639
|
+
* https://github.com/olivernn/lunr.js/issues/279 Calling any
|
1640
|
+
* String.prototype method forces Safari to "cast" this string to what
|
1641
|
+
* it's supposed to be, fixing the bug. */
|
1642
|
+
frame.prefix.charAt(0)
|
1439
1643
|
words.push(frame.prefix)
|
1440
1644
|
}
|
1441
1645
|
|
@@ -1632,7 +1836,7 @@ lunr.TokenSet.Builder.prototype.minimize = function (downTo) {
|
|
1632
1836
|
}
|
1633
1837
|
/*!
|
1634
1838
|
* lunr.Index
|
1635
|
-
* Copyright (C)
|
1839
|
+
* Copyright (C) 2018 Oliver Nightingale
|
1636
1840
|
*/
|
1637
1841
|
|
1638
1842
|
/**
|
@@ -1646,7 +1850,7 @@ lunr.TokenSet.Builder.prototype.minimize = function (downTo) {
|
|
1646
1850
|
* @constructor
|
1647
1851
|
* @param {Object} attrs - The attributes of the built search index.
|
1648
1852
|
* @param {Object} attrs.invertedIndex - An index of term/field to document reference.
|
1649
|
-
* @param {Object<string, lunr.Vector>} attrs.
|
1853
|
+
* @param {Object<string, lunr.Vector>} attrs.fieldVectors - Field vectors
|
1650
1854
|
* @param {lunr.TokenSet} attrs.tokenSet - An set of all corpus tokens.
|
1651
1855
|
* @param {string[]} attrs.fields - The names of indexed document fields.
|
1652
1856
|
* @param {lunr.Pipeline} attrs.pipeline - The pipeline to use for search terms.
|
@@ -1692,6 +1896,12 @@ lunr.Index = function (attrs) {
|
|
1692
1896
|
* to provide fuzzy matching, e.g. 'hello~2' will match documents with hello with an edit distance of 2.
|
1693
1897
|
* Avoid large values for edit distance to improve query performance.
|
1694
1898
|
*
|
1899
|
+
* Each term also supports a presence modifier. By default a term's presence in document is optional, however
|
1900
|
+
* this can be changed to either required or prohibited. For a term's presence to be required in a document the
|
1901
|
+
* term should be prefixed with a '+', e.g. `+foo bar` is a search for documents that must contain 'foo' and
|
1902
|
+
* optionally contain 'bar'. Conversely a leading '-' sets the terms presence to prohibited, i.e. it must not
|
1903
|
+
* appear in a document, e.g. `-foo bar` is a search for documents that do not contain 'foo' but may contain 'bar'.
|
1904
|
+
*
|
1695
1905
|
* To escape special characters the backslash character '\' can be used, this allows searches to include
|
1696
1906
|
* characters that would normally be considered modifiers, e.g. `foo\~2` will search for a term "foo~2" instead
|
1697
1907
|
* of attempting to apply a boost of 2 to the search term "foo".
|
@@ -1707,13 +1917,16 @@ lunr.Index = function (attrs) {
|
|
1707
1917
|
* hello^10
|
1708
1918
|
* @example <caption>term with an edit distance of 2</caption>
|
1709
1919
|
* hello~2
|
1920
|
+
* @example <caption>terms with presence modifiers</caption>
|
1921
|
+
* -foo +bar baz
|
1710
1922
|
*/
|
1711
1923
|
|
1712
1924
|
/**
|
1713
1925
|
* Performs a search against the index using lunr query syntax.
|
1714
1926
|
*
|
1715
1927
|
* Results will be returned sorted by their score, the most relevant results
|
1716
|
-
* will be returned first.
|
1928
|
+
* will be returned first. For details on how the score is calculated, please see
|
1929
|
+
* the {@link https://lunrjs.com/guides/searching.html#scoring|guide}.
|
1717
1930
|
*
|
1718
1931
|
* For more programmatic querying use lunr.Index#query.
|
1719
1932
|
*
|
@@ -1764,7 +1977,18 @@ lunr.Index.prototype.query = function (fn) {
|
|
1764
1977
|
var query = new lunr.Query(this.fields),
|
1765
1978
|
matchingFields = Object.create(null),
|
1766
1979
|
queryVectors = Object.create(null),
|
1767
|
-
termFieldCache = Object.create(null)
|
1980
|
+
termFieldCache = Object.create(null),
|
1981
|
+
requiredMatches = Object.create(null),
|
1982
|
+
prohibitedMatches = Object.create(null)
|
1983
|
+
|
1984
|
+
/*
|
1985
|
+
* To support field level boosts a query vector is created per
|
1986
|
+
* field. An empty vector is eagerly created to support negated
|
1987
|
+
* queries.
|
1988
|
+
*/
|
1989
|
+
for (var i = 0; i < this.fields.length; i++) {
|
1990
|
+
queryVectors[this.fields[i]] = new lunr.Vector
|
1991
|
+
}
|
1768
1992
|
|
1769
1993
|
fn.call(query, query)
|
1770
1994
|
|
@@ -1778,10 +2002,13 @@ lunr.Index.prototype.query = function (fn) {
|
|
1778
2002
|
* for a single query term.
|
1779
2003
|
*/
|
1780
2004
|
var clause = query.clauses[i],
|
1781
|
-
terms = null
|
2005
|
+
terms = null,
|
2006
|
+
clauseMatches = lunr.Set.complete
|
1782
2007
|
|
1783
2008
|
if (clause.usePipeline) {
|
1784
|
-
terms = this.pipeline.runString(clause.term
|
2009
|
+
terms = this.pipeline.runString(clause.term, {
|
2010
|
+
fields: clause.fields
|
2011
|
+
})
|
1785
2012
|
} else {
|
1786
2013
|
terms = [clause.term]
|
1787
2014
|
}
|
@@ -1805,6 +2032,21 @@ lunr.Index.prototype.query = function (fn) {
|
|
1805
2032
|
var termTokenSet = lunr.TokenSet.fromClause(clause),
|
1806
2033
|
expandedTerms = this.tokenSet.intersect(termTokenSet).toArray()
|
1807
2034
|
|
2035
|
+
/*
|
2036
|
+
* If a term marked as required does not exist in the tokenSet it is
|
2037
|
+
* impossible for the search to return any matches. We set all the field
|
2038
|
+
* scoped required matches set to empty and stop examining any further
|
2039
|
+
* clauses.
|
2040
|
+
*/
|
2041
|
+
if (expandedTerms.length === 0 && clause.presence === lunr.Query.presence.REQUIRED) {
|
2042
|
+
for (var k = 0; k < clause.fields.length; k++) {
|
2043
|
+
var field = clause.fields[k]
|
2044
|
+
requiredMatches[field] = lunr.Set.empty
|
2045
|
+
}
|
2046
|
+
|
2047
|
+
break
|
2048
|
+
}
|
2049
|
+
|
1808
2050
|
for (var j = 0; j < expandedTerms.length; j++) {
|
1809
2051
|
/*
|
1810
2052
|
* For each term get the posting and termIndex, this is required for
|
@@ -1826,26 +2068,50 @@ lunr.Index.prototype.query = function (fn) {
|
|
1826
2068
|
var field = clause.fields[k],
|
1827
2069
|
fieldPosting = posting[field],
|
1828
2070
|
matchingDocumentRefs = Object.keys(fieldPosting),
|
1829
|
-
termField = expandedTerm + "/" + field
|
2071
|
+
termField = expandedTerm + "/" + field,
|
2072
|
+
matchingDocumentsSet = new lunr.Set(matchingDocumentRefs)
|
1830
2073
|
|
1831
2074
|
/*
|
1832
|
-
*
|
1833
|
-
*
|
1834
|
-
* the term and a unit value with the appropriate boost applied.
|
2075
|
+
* if the presence of this term is required ensure that the matching
|
2076
|
+
* documents are added to the set of required matches for this clause.
|
1835
2077
|
*
|
1836
|
-
* If the query vector for this field does not exist yet it needs
|
1837
|
-
* to be created.
|
1838
2078
|
*/
|
1839
|
-
if (
|
1840
|
-
|
2079
|
+
if (clause.presence == lunr.Query.presence.REQUIRED) {
|
2080
|
+
clauseMatches = clauseMatches.union(matchingDocumentsSet)
|
2081
|
+
|
2082
|
+
if (requiredMatches[field] === undefined) {
|
2083
|
+
requiredMatches[field] = lunr.Set.complete
|
2084
|
+
}
|
2085
|
+
}
|
2086
|
+
|
2087
|
+
/*
|
2088
|
+
* if the presence of this term is prohibited ensure that the matching
|
2089
|
+
* documents are added to the set of prohibited matches for this field,
|
2090
|
+
* creating that set if it does not yet exist.
|
2091
|
+
*/
|
2092
|
+
if (clause.presence == lunr.Query.presence.PROHIBITED) {
|
2093
|
+
if (prohibitedMatches[field] === undefined) {
|
2094
|
+
prohibitedMatches[field] = lunr.Set.empty
|
2095
|
+
}
|
2096
|
+
|
2097
|
+
prohibitedMatches[field] = prohibitedMatches[field].union(matchingDocumentsSet)
|
2098
|
+
|
2099
|
+
/*
|
2100
|
+
* Prohibited matches should not be part of the query vector used for
|
2101
|
+
* similarity scoring and no metadata should be extracted so we continue
|
2102
|
+
* to the next field
|
2103
|
+
*/
|
2104
|
+
continue
|
1841
2105
|
}
|
1842
2106
|
|
1843
2107
|
/*
|
2108
|
+
* The query field vector is populated using the termIndex found for
|
2109
|
+
* the term and a unit value with the appropriate boost applied.
|
1844
2110
|
* Using upsert because there could already be an entry in the vector
|
1845
2111
|
* for the term we are working with. In that case we just add the scores
|
1846
2112
|
* together.
|
1847
2113
|
*/
|
1848
|
-
queryVectors[field].upsert(termIndex,
|
2114
|
+
queryVectors[field].upsert(termIndex, clause.boost, function (a, b) { return a + b })
|
1849
2115
|
|
1850
2116
|
/**
|
1851
2117
|
* If we've already seen this term, field combo then we've already collected
|
@@ -1879,12 +2145,65 @@ lunr.Index.prototype.query = function (fn) {
|
|
1879
2145
|
}
|
1880
2146
|
}
|
1881
2147
|
}
|
2148
|
+
|
2149
|
+
/**
|
2150
|
+
* If the presence was required we need to update the requiredMatches field sets.
|
2151
|
+
* We do this after all fields for the term have collected their matches because
|
2152
|
+
* the clause terms presence is required in _any_ of the fields not _all_ of the
|
2153
|
+
* fields.
|
2154
|
+
*/
|
2155
|
+
if (clause.presence === lunr.Query.presence.REQUIRED) {
|
2156
|
+
for (var k = 0; k < clause.fields.length; k++) {
|
2157
|
+
var field = clause.fields[k]
|
2158
|
+
requiredMatches[field] = requiredMatches[field].intersect(clauseMatches)
|
2159
|
+
}
|
2160
|
+
}
|
2161
|
+
}
|
2162
|
+
|
2163
|
+
/**
|
2164
|
+
* Need to combine the field scoped required and prohibited
|
2165
|
+
* matching documents into a global set of required and prohibited
|
2166
|
+
* matches
|
2167
|
+
*/
|
2168
|
+
var allRequiredMatches = lunr.Set.complete,
|
2169
|
+
allProhibitedMatches = lunr.Set.empty
|
2170
|
+
|
2171
|
+
for (var i = 0; i < this.fields.length; i++) {
|
2172
|
+
var field = this.fields[i]
|
2173
|
+
|
2174
|
+
if (requiredMatches[field]) {
|
2175
|
+
allRequiredMatches = allRequiredMatches.intersect(requiredMatches[field])
|
2176
|
+
}
|
2177
|
+
|
2178
|
+
if (prohibitedMatches[field]) {
|
2179
|
+
allProhibitedMatches = allProhibitedMatches.union(prohibitedMatches[field])
|
2180
|
+
}
|
1882
2181
|
}
|
1883
2182
|
|
1884
2183
|
var matchingFieldRefs = Object.keys(matchingFields),
|
1885
2184
|
results = [],
|
1886
2185
|
matches = Object.create(null)
|
1887
2186
|
|
2187
|
+
/*
|
2188
|
+
* If the query is negated (contains only prohibited terms)
|
2189
|
+
* we need to get _all_ fieldRefs currently existing in the
|
2190
|
+
* index. This is only done when we know that the query is
|
2191
|
+
* entirely prohibited terms to avoid any cost of getting all
|
2192
|
+
* fieldRefs unnecessarily.
|
2193
|
+
*
|
2194
|
+
* Additionally, blank MatchData must be created to correctly
|
2195
|
+
* populate the results.
|
2196
|
+
*/
|
2197
|
+
if (query.isNegated()) {
|
2198
|
+
matchingFieldRefs = Object.keys(this.fieldVectors)
|
2199
|
+
|
2200
|
+
for (var i = 0; i < matchingFieldRefs.length; i++) {
|
2201
|
+
var matchingFieldRef = matchingFieldRefs[i]
|
2202
|
+
var fieldRef = lunr.FieldRef.fromString(matchingFieldRef)
|
2203
|
+
matchingFields[matchingFieldRef] = new lunr.MatchData
|
2204
|
+
}
|
2205
|
+
}
|
2206
|
+
|
1888
2207
|
for (var i = 0; i < matchingFieldRefs.length; i++) {
|
1889
2208
|
/*
|
1890
2209
|
* Currently we have document fields that match the query, but we
|
@@ -1895,8 +2214,17 @@ lunr.Index.prototype.query = function (fn) {
|
|
1895
2214
|
* above, and combined into a final document score using addition.
|
1896
2215
|
*/
|
1897
2216
|
var fieldRef = lunr.FieldRef.fromString(matchingFieldRefs[i]),
|
1898
|
-
docRef = fieldRef.docRef
|
1899
|
-
|
2217
|
+
docRef = fieldRef.docRef
|
2218
|
+
|
2219
|
+
if (!allRequiredMatches.contains(docRef)) {
|
2220
|
+
continue
|
2221
|
+
}
|
2222
|
+
|
2223
|
+
if (allProhibitedMatches.contains(docRef)) {
|
2224
|
+
continue
|
2225
|
+
}
|
2226
|
+
|
2227
|
+
var fieldVector = this.fieldVectors[fieldRef],
|
1900
2228
|
score = queryVectors[fieldRef.fieldName].similarity(fieldVector),
|
1901
2229
|
docMatch
|
1902
2230
|
|
@@ -2000,7 +2328,7 @@ lunr.Index.load = function (serializedIndex) {
|
|
2000
2328
|
}
|
2001
2329
|
/*!
|
2002
2330
|
* lunr.Builder
|
2003
|
-
* Copyright (C)
|
2331
|
+
* Copyright (C) 2018 Oliver Nightingale
|
2004
2332
|
*/
|
2005
2333
|
|
2006
2334
|
/**
|
@@ -2029,7 +2357,8 @@ lunr.Index.load = function (serializedIndex) {
|
|
2029
2357
|
*/
|
2030
2358
|
lunr.Builder = function () {
|
2031
2359
|
this._ref = "id"
|
2032
|
-
this._fields =
|
2360
|
+
this._fields = Object.create(null)
|
2361
|
+
this._documents = Object.create(null)
|
2033
2362
|
this.invertedIndex = Object.create(null)
|
2034
2363
|
this.fieldTermFrequencies = {}
|
2035
2364
|
this.fieldLengths = {}
|
@@ -2059,6 +2388,20 @@ lunr.Builder.prototype.ref = function (ref) {
|
|
2059
2388
|
this._ref = ref
|
2060
2389
|
}
|
2061
2390
|
|
2391
|
+
/**
|
2392
|
+
* A function that is used to extract a field from a document.
|
2393
|
+
*
|
2394
|
+
* Lunr expects a field to be at the top level of a document, if however the field
|
2395
|
+
* is deeply nested within a document an extractor function can be used to extract
|
2396
|
+
* the right field for indexing.
|
2397
|
+
*
|
2398
|
+
* @callback fieldExtractor
|
2399
|
+
* @param {object} doc - The document being added to the index.
|
2400
|
+
* @returns {?(string|object|object[])} obj - The object that will be indexed for this field.
|
2401
|
+
* @example <caption>Extracting a nested field</caption>
|
2402
|
+
* function (doc) { return doc.nested.field }
|
2403
|
+
*/
|
2404
|
+
|
2062
2405
|
/**
|
2063
2406
|
* Adds a field to the list of document fields that will be indexed. Every document being
|
2064
2407
|
* indexed should have this field. Null values for this field in indexed documents will
|
@@ -2067,10 +2410,22 @@ lunr.Builder.prototype.ref = function (ref) {
|
|
2067
2410
|
* All fields should be added before adding documents to the index. Adding fields after
|
2068
2411
|
* a document has been indexed will have no effect on already indexed documents.
|
2069
2412
|
*
|
2070
|
-
*
|
2413
|
+
* Fields can be boosted at build time. This allows terms within that field to have more
|
2414
|
+
* importance when ranking search results. Use a field boost to specify that matches within
|
2415
|
+
* one field are more important than other fields.
|
2416
|
+
*
|
2417
|
+
* @param {string} fieldName - The name of a field to index in all documents.
|
2418
|
+
* @param {object} attributes - Optional attributes associated with this field.
|
2419
|
+
* @param {number} [attributes.boost=1] - Boost applied to all terms within this field.
|
2420
|
+
* @param {fieldExtractor} [attributes.extractor] - Function to extract a field from a document.
|
2421
|
+
* @throws {RangeError} fieldName cannot contain unsupported characters '/'
|
2071
2422
|
*/
|
2072
|
-
lunr.Builder.prototype.field = function (
|
2073
|
-
|
2423
|
+
lunr.Builder.prototype.field = function (fieldName, attributes) {
|
2424
|
+
if (/\//.test(fieldName)) {
|
2425
|
+
throw new RangeError ("Field '" + fieldName + "' contains illegal character '/'")
|
2426
|
+
}
|
2427
|
+
|
2428
|
+
this._fields[fieldName] = attributes || {}
|
2074
2429
|
}
|
2075
2430
|
|
2076
2431
|
/**
|
@@ -2112,17 +2467,27 @@ lunr.Builder.prototype.k1 = function (number) {
|
|
2112
2467
|
* it should have all fields defined for indexing, though null or undefined values will not
|
2113
2468
|
* cause errors.
|
2114
2469
|
*
|
2470
|
+
* Entire documents can be boosted at build time. Applying a boost to a document indicates that
|
2471
|
+
* this document should rank higher in search results than other documents.
|
2472
|
+
*
|
2115
2473
|
* @param {object} doc - The document to add to the index.
|
2474
|
+
* @param {object} attributes - Optional attributes associated with this document.
|
2475
|
+
* @param {number} [attributes.boost=1] - Boost applied to all terms within this document.
|
2116
2476
|
*/
|
2117
|
-
lunr.Builder.prototype.add = function (doc) {
|
2118
|
-
var docRef = doc[this._ref]
|
2477
|
+
lunr.Builder.prototype.add = function (doc, attributes) {
|
2478
|
+
var docRef = doc[this._ref],
|
2479
|
+
fields = Object.keys(this._fields)
|
2119
2480
|
|
2481
|
+
this._documents[docRef] = attributes || {}
|
2120
2482
|
this.documentCount += 1
|
2121
2483
|
|
2122
|
-
for (var i = 0; i <
|
2123
|
-
var fieldName =
|
2124
|
-
|
2125
|
-
|
2484
|
+
for (var i = 0; i < fields.length; i++) {
|
2485
|
+
var fieldName = fields[i],
|
2486
|
+
extractor = this._fields[fieldName].extractor,
|
2487
|
+
field = extractor ? extractor(doc) : doc[fieldName],
|
2488
|
+
tokens = this.tokenizer(field, {
|
2489
|
+
fields: [fieldName]
|
2490
|
+
}),
|
2126
2491
|
terms = this.pipeline.run(tokens),
|
2127
2492
|
fieldRef = new lunr.FieldRef (docRef, fieldName),
|
2128
2493
|
fieldTerms = Object.create(null)
|
@@ -2150,8 +2515,8 @@ lunr.Builder.prototype.add = function (doc) {
|
|
2150
2515
|
posting["_index"] = this.termIndex
|
2151
2516
|
this.termIndex += 1
|
2152
2517
|
|
2153
|
-
for (var k = 0; k <
|
2154
|
-
posting[
|
2518
|
+
for (var k = 0; k < fields.length; k++) {
|
2519
|
+
posting[fields[k]] = Object.create(null)
|
2155
2520
|
}
|
2156
2521
|
|
2157
2522
|
this.invertedIndex[term] = posting
|
@@ -2202,9 +2567,11 @@ lunr.Builder.prototype.calculateAverageFieldLengths = function () {
|
|
2202
2567
|
accumulator[field] += this.fieldLengths[fieldRef]
|
2203
2568
|
}
|
2204
2569
|
|
2205
|
-
|
2206
|
-
|
2207
|
-
|
2570
|
+
var fields = Object.keys(this._fields)
|
2571
|
+
|
2572
|
+
for (var i = 0; i < fields.length; i++) {
|
2573
|
+
var fieldName = fields[i]
|
2574
|
+
accumulator[fieldName] = accumulator[fieldName] / documentsWithField[fieldName]
|
2208
2575
|
}
|
2209
2576
|
|
2210
2577
|
this.averageFieldLength = accumulator
|
@@ -2223,13 +2590,17 @@ lunr.Builder.prototype.createFieldVectors = function () {
|
|
2223
2590
|
|
2224
2591
|
for (var i = 0; i < fieldRefsLength; i++) {
|
2225
2592
|
var fieldRef = lunr.FieldRef.fromString(fieldRefs[i]),
|
2226
|
-
|
2593
|
+
fieldName = fieldRef.fieldName,
|
2227
2594
|
fieldLength = this.fieldLengths[fieldRef],
|
2228
2595
|
fieldVector = new lunr.Vector,
|
2229
2596
|
termFrequencies = this.fieldTermFrequencies[fieldRef],
|
2230
2597
|
terms = Object.keys(termFrequencies),
|
2231
2598
|
termsLength = terms.length
|
2232
2599
|
|
2600
|
+
|
2601
|
+
var fieldBoost = this._fields[fieldName].boost || 1,
|
2602
|
+
docBoost = this._documents[fieldRef.docRef].boost || 1
|
2603
|
+
|
2233
2604
|
for (var j = 0; j < termsLength; j++) {
|
2234
2605
|
var term = terms[j],
|
2235
2606
|
tf = termFrequencies[term],
|
@@ -2243,7 +2614,9 @@ lunr.Builder.prototype.createFieldVectors = function () {
|
|
2243
2614
|
idf = termIdfCache[term]
|
2244
2615
|
}
|
2245
2616
|
|
2246
|
-
score = idf * ((this._k1 + 1) * tf) / (this._k1 * (1 - this._b + this._b * (fieldLength / this.averageFieldLength[
|
2617
|
+
score = idf * ((this._k1 + 1) * tf) / (this._k1 * (1 - this._b + this._b * (fieldLength / this.averageFieldLength[fieldName])) + tf)
|
2618
|
+
score *= fieldBoost
|
2619
|
+
score *= docBoost
|
2247
2620
|
scoreWithPrecision = Math.round(score * 1000) / 1000
|
2248
2621
|
// Converts 1.23456789 to 1.234.
|
2249
2622
|
// Reducing the precision so that the vectors take up less
|
@@ -2289,7 +2662,7 @@ lunr.Builder.prototype.build = function () {
|
|
2289
2662
|
invertedIndex: this.invertedIndex,
|
2290
2663
|
fieldVectors: this.fieldVectors,
|
2291
2664
|
tokenSet: this.tokenSet,
|
2292
|
-
fields: this._fields,
|
2665
|
+
fields: Object.keys(this._fields),
|
2293
2666
|
pipeline: this.searchPipeline
|
2294
2667
|
})
|
2295
2668
|
}
|
@@ -2327,7 +2700,7 @@ lunr.Builder.prototype.use = function (fn) {
|
|
2327
2700
|
*/
|
2328
2701
|
lunr.MatchData = function (term, field, metadata) {
|
2329
2702
|
var clonedMetadata = Object.create(null),
|
2330
|
-
metadataKeys = Object.keys(metadata)
|
2703
|
+
metadataKeys = Object.keys(metadata || {})
|
2331
2704
|
|
2332
2705
|
// Cloning the metadata to prevent the original
|
2333
2706
|
// being mutated during match data combination.
|
@@ -2340,8 +2713,11 @@ lunr.MatchData = function (term, field, metadata) {
|
|
2340
2713
|
}
|
2341
2714
|
|
2342
2715
|
this.metadata = Object.create(null)
|
2343
|
-
|
2344
|
-
|
2716
|
+
|
2717
|
+
if (term !== undefined) {
|
2718
|
+
this.metadata[term] = Object.create(null)
|
2719
|
+
this.metadata[term][field] = clonedMetadata
|
2720
|
+
}
|
2345
2721
|
}
|
2346
2722
|
|
2347
2723
|
/**
|
@@ -2456,11 +2832,42 @@ lunr.Query = function (allFields) {
|
|
2456
2832
|
* wildcard: lunr.Query.wildcard.LEADING | lunr.Query.wildcard.TRAILING
|
2457
2833
|
* })
|
2458
2834
|
*/
|
2835
|
+
|
2459
2836
|
lunr.Query.wildcard = new String ("*")
|
2460
2837
|
lunr.Query.wildcard.NONE = 0
|
2461
2838
|
lunr.Query.wildcard.LEADING = 1
|
2462
2839
|
lunr.Query.wildcard.TRAILING = 2
|
2463
2840
|
|
2841
|
+
/**
|
2842
|
+
* Constants for indicating what kind of presence a term must have in matching documents.
|
2843
|
+
*
|
2844
|
+
* @constant
|
2845
|
+
* @enum {number}
|
2846
|
+
* @see lunr.Query~Clause
|
2847
|
+
* @see lunr.Query#clause
|
2848
|
+
* @see lunr.Query#term
|
2849
|
+
* @example <caption>query term with required presence</caption>
|
2850
|
+
* query.term('foo', { presence: lunr.Query.presence.REQUIRED })
|
2851
|
+
*/
|
2852
|
+
lunr.Query.presence = {
|
2853
|
+
/**
|
2854
|
+
* Term's presence in a document is optional, this is the default value.
|
2855
|
+
*/
|
2856
|
+
OPTIONAL: 1,
|
2857
|
+
|
2858
|
+
/**
|
2859
|
+
* Term's presence in a document is required, documents that do not contain
|
2860
|
+
* this term will not be returned.
|
2861
|
+
*/
|
2862
|
+
REQUIRED: 2,
|
2863
|
+
|
2864
|
+
/**
|
2865
|
+
* Term's presence in a document is prohibited, documents that do contain
|
2866
|
+
* this term will not be returned.
|
2867
|
+
*/
|
2868
|
+
PROHIBITED: 3
|
2869
|
+
}
|
2870
|
+
|
2464
2871
|
/**
|
2465
2872
|
* A single clause in a {@link lunr.Query} contains a term and details on how to
|
2466
2873
|
* match that term against a {@link lunr.Index}.
|
@@ -2470,7 +2877,8 @@ lunr.Query.wildcard.TRAILING = 2
|
|
2470
2877
|
* @property {number} [boost=1] - Any boost that should be applied when matching this clause.
|
2471
2878
|
* @property {number} [editDistance] - Whether the term should have fuzzy matching applied, and how fuzzy the match should be.
|
2472
2879
|
* @property {boolean} [usePipeline] - Whether the term should be passed through the search pipeline.
|
2473
|
-
* @property {number} [wildcard=
|
2880
|
+
* @property {number} [wildcard=lunr.Query.wildcard.NONE] - Whether the term should have wildcards appended or prepended.
|
2881
|
+
* @property {number} [presence=lunr.Query.presence.OPTIONAL] - The terms presence in any matching documents.
|
2474
2882
|
*/
|
2475
2883
|
|
2476
2884
|
/**
|
@@ -2508,17 +2916,44 @@ lunr.Query.prototype.clause = function (clause) {
|
|
2508
2916
|
clause.term = "" + clause.term + "*"
|
2509
2917
|
}
|
2510
2918
|
|
2919
|
+
if (!('presence' in clause)) {
|
2920
|
+
clause.presence = lunr.Query.presence.OPTIONAL
|
2921
|
+
}
|
2922
|
+
|
2511
2923
|
this.clauses.push(clause)
|
2512
2924
|
|
2513
2925
|
return this
|
2514
2926
|
}
|
2515
2927
|
|
2928
|
+
/**
|
2929
|
+
* A negated query is one in which every clause has a presence of
|
2930
|
+
* prohibited. These queries require some special processing to return
|
2931
|
+
* the expected results.
|
2932
|
+
*
|
2933
|
+
* @returns boolean
|
2934
|
+
*/
|
2935
|
+
lunr.Query.prototype.isNegated = function () {
|
2936
|
+
for (var i = 0; i < this.clauses.length; i++) {
|
2937
|
+
if (this.clauses[i].presence != lunr.Query.presence.PROHIBITED) {
|
2938
|
+
return false
|
2939
|
+
}
|
2940
|
+
}
|
2941
|
+
|
2942
|
+
return true
|
2943
|
+
}
|
2944
|
+
|
2516
2945
|
/**
|
2517
2946
|
* Adds a term to the current query, under the covers this will create a {@link lunr.Query~Clause}
|
2518
2947
|
* to the list of clauses that make up this query.
|
2519
2948
|
*
|
2520
|
-
*
|
2521
|
-
*
|
2949
|
+
* The term is used as is, i.e. no tokenization will be performed by this method. Instead conversion
|
2950
|
+
* to a token or token-like string should be done before calling this method.
|
2951
|
+
*
|
2952
|
+
* The term will be converted to a string by calling `toString`. Multiple terms can be passed as an
|
2953
|
+
* array, each term in the array will share the same options.
|
2954
|
+
*
|
2955
|
+
* @param {object|object[]} term - The term(s) to add to the query.
|
2956
|
+
* @param {object} [options] - Any additional properties to add to the query clause.
|
2522
2957
|
* @returns {lunr.Query}
|
2523
2958
|
* @see lunr.Query#clause
|
2524
2959
|
* @see lunr.Query~Clause
|
@@ -2530,10 +2965,17 @@ lunr.Query.prototype.clause = function (clause) {
|
|
2530
2965
|
* boost: 10,
|
2531
2966
|
* wildcard: lunr.Query.wildcard.TRAILING
|
2532
2967
|
* })
|
2968
|
+
* @example <caption>using lunr.tokenizer to convert a string to tokens before using them as terms</caption>
|
2969
|
+
* query.term(lunr.tokenizer("foo bar"))
|
2533
2970
|
*/
|
2534
2971
|
lunr.Query.prototype.term = function (term, options) {
|
2972
|
+
if (Array.isArray(term)) {
|
2973
|
+
term.forEach(function (t) { this.term(t, lunr.utils.clone(options)) }, this)
|
2974
|
+
return this
|
2975
|
+
}
|
2976
|
+
|
2535
2977
|
var clause = options || {}
|
2536
|
-
clause.term = term
|
2978
|
+
clause.term = term.toString()
|
2537
2979
|
|
2538
2980
|
this.clause(clause)
|
2539
2981
|
|
@@ -2645,6 +3087,7 @@ lunr.QueryLexer.FIELD = 'FIELD'
|
|
2645
3087
|
lunr.QueryLexer.TERM = 'TERM'
|
2646
3088
|
lunr.QueryLexer.EDIT_DISTANCE = 'EDIT_DISTANCE'
|
2647
3089
|
lunr.QueryLexer.BOOST = 'BOOST'
|
3090
|
+
lunr.QueryLexer.PRESENCE = 'PRESENCE'
|
2648
3091
|
|
2649
3092
|
lunr.QueryLexer.lexField = function (lexer) {
|
2650
3093
|
lexer.backup()
|
@@ -2733,6 +3176,22 @@ lunr.QueryLexer.lexText = function (lexer) {
|
|
2733
3176
|
return lunr.QueryLexer.lexBoost
|
2734
3177
|
}
|
2735
3178
|
|
3179
|
+
// "+" indicates term presence is required
|
3180
|
+
// checking for length to ensure that only
|
3181
|
+
// leading "+" are considered
|
3182
|
+
if (char == "+" && lexer.width() === 1) {
|
3183
|
+
lexer.emit(lunr.QueryLexer.PRESENCE)
|
3184
|
+
return lunr.QueryLexer.lexText
|
3185
|
+
}
|
3186
|
+
|
3187
|
+
// "-" indicates term presence is prohibited
|
3188
|
+
// checking for length to ensure that only
|
3189
|
+
// leading "-" are considered
|
3190
|
+
if (char == "-" && lexer.width() === 1) {
|
3191
|
+
lexer.emit(lunr.QueryLexer.PRESENCE)
|
3192
|
+
return lunr.QueryLexer.lexText
|
3193
|
+
}
|
3194
|
+
|
2736
3195
|
if (char.match(lunr.QueryLexer.termSeparator)) {
|
2737
3196
|
return lunr.QueryLexer.lexTerm
|
2738
3197
|
}
|
@@ -2750,7 +3209,7 @@ lunr.QueryParser.prototype.parse = function () {
|
|
2750
3209
|
this.lexer.run()
|
2751
3210
|
this.lexemes = this.lexer.lexemes
|
2752
3211
|
|
2753
|
-
var state = lunr.QueryParser.
|
3212
|
+
var state = lunr.QueryParser.parseClause
|
2754
3213
|
|
2755
3214
|
while (state) {
|
2756
3215
|
state = state(this)
|
@@ -2775,7 +3234,7 @@ lunr.QueryParser.prototype.nextClause = function () {
|
|
2775
3234
|
this.currentClause = {}
|
2776
3235
|
}
|
2777
3236
|
|
2778
|
-
lunr.QueryParser.
|
3237
|
+
lunr.QueryParser.parseClause = function (parser) {
|
2779
3238
|
var lexeme = parser.peekLexeme()
|
2780
3239
|
|
2781
3240
|
if (lexeme == undefined) {
|
@@ -2783,6 +3242,8 @@ lunr.QueryParser.parseFieldOrTerm = function (parser) {
|
|
2783
3242
|
}
|
2784
3243
|
|
2785
3244
|
switch (lexeme.type) {
|
3245
|
+
case lunr.QueryLexer.PRESENCE:
|
3246
|
+
return lunr.QueryParser.parsePresence
|
2786
3247
|
case lunr.QueryLexer.FIELD:
|
2787
3248
|
return lunr.QueryParser.parseField
|
2788
3249
|
case lunr.QueryLexer.TERM:
|
@@ -2798,6 +3259,43 @@ lunr.QueryParser.parseFieldOrTerm = function (parser) {
|
|
2798
3259
|
}
|
2799
3260
|
}
|
2800
3261
|
|
3262
|
+
lunr.QueryParser.parsePresence = function (parser) {
|
3263
|
+
var lexeme = parser.consumeLexeme()
|
3264
|
+
|
3265
|
+
if (lexeme == undefined) {
|
3266
|
+
return
|
3267
|
+
}
|
3268
|
+
|
3269
|
+
switch (lexeme.str) {
|
3270
|
+
case "-":
|
3271
|
+
parser.currentClause.presence = lunr.Query.presence.PROHIBITED
|
3272
|
+
break
|
3273
|
+
case "+":
|
3274
|
+
parser.currentClause.presence = lunr.Query.presence.REQUIRED
|
3275
|
+
break
|
3276
|
+
default:
|
3277
|
+
var errorMessage = "unrecognised presence operator'" + lexeme.str + "'"
|
3278
|
+
throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)
|
3279
|
+
}
|
3280
|
+
|
3281
|
+
var nextLexeme = parser.peekLexeme()
|
3282
|
+
|
3283
|
+
if (nextLexeme == undefined) {
|
3284
|
+
var errorMessage = "expecting term or field, found nothing"
|
3285
|
+
throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)
|
3286
|
+
}
|
3287
|
+
|
3288
|
+
switch (nextLexeme.type) {
|
3289
|
+
case lunr.QueryLexer.FIELD:
|
3290
|
+
return lunr.QueryParser.parseField
|
3291
|
+
case lunr.QueryLexer.TERM:
|
3292
|
+
return lunr.QueryParser.parseTerm
|
3293
|
+
default:
|
3294
|
+
var errorMessage = "expecting term or field, found '" + nextLexeme.type + "'"
|
3295
|
+
throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)
|
3296
|
+
}
|
3297
|
+
}
|
3298
|
+
|
2801
3299
|
lunr.QueryParser.parseField = function (parser) {
|
2802
3300
|
var lexeme = parser.consumeLexeme()
|
2803
3301
|
|
@@ -2861,6 +3359,9 @@ lunr.QueryParser.parseTerm = function (parser) {
|
|
2861
3359
|
return lunr.QueryParser.parseEditDistance
|
2862
3360
|
case lunr.QueryLexer.BOOST:
|
2863
3361
|
return lunr.QueryParser.parseBoost
|
3362
|
+
case lunr.QueryLexer.PRESENCE:
|
3363
|
+
parser.nextClause()
|
3364
|
+
return lunr.QueryParser.parsePresence
|
2864
3365
|
default:
|
2865
3366
|
var errorMessage = "Unexpected lexeme type '" + nextLexeme.type + "'"
|
2866
3367
|
throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)
|
@@ -2901,6 +3402,9 @@ lunr.QueryParser.parseEditDistance = function (parser) {
|
|
2901
3402
|
return lunr.QueryParser.parseEditDistance
|
2902
3403
|
case lunr.QueryLexer.BOOST:
|
2903
3404
|
return lunr.QueryParser.parseBoost
|
3405
|
+
case lunr.QueryLexer.PRESENCE:
|
3406
|
+
parser.nextClause()
|
3407
|
+
return lunr.QueryParser.parsePresence
|
2904
3408
|
default:
|
2905
3409
|
var errorMessage = "Unexpected lexeme type '" + nextLexeme.type + "'"
|
2906
3410
|
throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)
|
@@ -2941,6 +3445,9 @@ lunr.QueryParser.parseBoost = function (parser) {
|
|
2941
3445
|
return lunr.QueryParser.parseEditDistance
|
2942
3446
|
case lunr.QueryLexer.BOOST:
|
2943
3447
|
return lunr.QueryParser.parseBoost
|
3448
|
+
case lunr.QueryLexer.PRESENCE:
|
3449
|
+
parser.nextClause()
|
3450
|
+
return lunr.QueryParser.parsePresence
|
2944
3451
|
default:
|
2945
3452
|
var errorMessage = "Unexpected lexeme type '" + nextLexeme.type + "'"
|
2946
3453
|
throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)
|
@@ -2974,4 +3481,4 @@ lunr.QueryParser.parseBoost = function (parser) {
|
|
2974
3481
|
*/
|
2975
3482
|
return lunr
|
2976
3483
|
}))
|
2977
|
-
})();
|
3484
|
+
})();
|