diff --git a/assets/js/lunr/lunr.js b/assets/js/lunr/lunr.js index f208eba5..0ede1a1a 100644 --- a/assets/js/lunr/lunr.js +++ b/assets/js/lunr/lunr.js @@ -1,6 +1,6 @@ /** - * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.1.5 - * Copyright (C) 2017 Oliver Nightingale + * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.3 + * Copyright (C) 2018 Oliver Nightingale * @license MIT */ @@ -54,14 +54,15 @@ var lunr = function (config) { return builder.build() } -lunr.version = "2.1.5" +lunr.version = "2.3.3" /*! * lunr.utils - * Copyright (C) 2017 Oliver Nightingale + * Copyright (C) 2018 Oliver Nightingale */ /** * A namespace containing utils for the rest of the lunr library + * @namespace lunr.utils */ lunr.utils = {} @@ -69,7 +70,8 @@ lunr.utils = {} * Print a warning message to the console. * * @param {String} message The message to be printed. - * @memberOf Utils + * @memberOf lunr.utils + * @function */ lunr.utils.warn = (function (global) { /* eslint-disable no-console */ @@ -90,7 +92,7 @@ lunr.utils.warn = (function (global) { * * @param {Any} obj The object to convert to a string. * @return {String} string representation of the passed object. - * @memberOf Utils + * @memberOf lunr.utils */ lunr.utils.asString = function (obj) { if (obj === void 0 || obj === null) { @@ -99,6 +101,52 @@ lunr.utils.asString = function (obj) { return obj.toString() } } + +/** + * Clones an object. + * + * Will create a copy of an existing object such that any mutations + * on the copy cannot affect the original. + * + * Only shallow objects are supported, passing a nested object to this + * function will cause a TypeError. + * + * Objects with primitives, and arrays of primitives are supported. + * + * @param {Object} obj The object to clone. + * @return {Object} a clone of the passed object. + * @throws {TypeError} when a nested object is passed. + * @memberOf Utils + */ +lunr.utils.clone = function (obj) { + if (obj === null || obj === undefined) { + return obj + } + + var clone = Object.create(null), + keys = Object.keys(obj) + + for (var i = 0; i < keys.length; i++) { + var key = keys[i], + val = obj[key] + + if (Array.isArray(val)) { + clone[key] = val.slice() + continue + } + + if (typeof val === 'string' || + typeof val === 'number' || + typeof val === 'boolean') { + clone[key] = val + continue + } + + throw new TypeError("clone is not deep and does not support nested objects") + } + + return clone +} lunr.FieldRef = function (docRef, fieldName, stringValue) { this.docRef = docRef this.fieldName = fieldName @@ -127,6 +175,139 @@ lunr.FieldRef.prototype.toString = function () { return this._stringValue } +/*! + * lunr.Set + * Copyright (C) 2018 Oliver Nightingale + */ + +/** + * A lunr set. + * + * @constructor + */ +lunr.Set = function (elements) { + this.elements = Object.create(null) + + if (elements) { + this.length = elements.length + + for (var i = 0; i < this.length; i++) { + this.elements[elements[i]] = true + } + } else { + this.length = 0 + } +} + +/** + * A complete set that contains all elements. + * + * @static + * @readonly + * @type {lunr.Set} + */ +lunr.Set.complete = { + intersect: function (other) { + return other + }, + + union: function (other) { + return other + }, + + contains: function () { + return true + } +} + +/** + * An empty set that contains no elements. + * + * @static + * @readonly + * @type {lunr.Set} + */ +lunr.Set.empty = { + intersect: function () { + return this + }, + + union: function (other) { + return other + }, + + contains: function () { + return false + } +} + +/** + * Returns true if this set contains the specified object. + * + * @param {object} object - Object whose presence in this set is to be tested. + * @returns {boolean} - True if this set contains the specified object. + */ +lunr.Set.prototype.contains = function (object) { + return !!this.elements[object] +} + +/** + * Returns a new set containing only the elements that are present in both + * this set and the specified set. + * + * @param {lunr.Set} other - set to intersect with this set. + * @returns {lunr.Set} a new set that is the intersection of this and the specified set. + */ + +lunr.Set.prototype.intersect = function (other) { + var a, b, elements, intersection = [] + + if (other === lunr.Set.complete) { + return this + } + + if (other === lunr.Set.empty) { + return other + } + + if (this.length < other.length) { + a = this + b = other + } else { + a = other + b = this + } + + elements = Object.keys(a.elements) + + for (var i = 0; i < elements.length; i++) { + var element = elements[i] + if (element in b.elements) { + intersection.push(element) + } + } + + return new lunr.Set (intersection) +} + +/** + * Returns a new set combining the elements of this and the specified set. + * + * @param {lunr.Set} other - set to union with this set. + * @return {lunr.Set} a new set that is the union of this and the specified set. + */ + +lunr.Set.prototype.union = function (other) { + if (other === lunr.Set.complete) { + return lunr.Set.complete + } + + if (other === lunr.Set.empty) { + return this + } + + return new lunr.Set(Object.keys(this.elements).concat(Object.keys(other.elements))) +} /** * A function to calculate the inverse document frequency for * a posting. This is shared between the builder and the index @@ -208,7 +389,7 @@ lunr.Token.prototype.clone = function (fn) { } /*! * lunr.tokenizer - * Copyright (C) 2017 Oliver Nightingale + * Copyright (C) 2018 Oliver Nightingale */ /** @@ -220,18 +401,26 @@ lunr.Token.prototype.clone = function (fn) { * then will split this string on the character in `lunr.tokenizer.separator`. * Arrays will have their elements converted to strings and wrapped in a lunr.Token. * + * Optional metadata can be passed to the tokenizer, this metadata will be cloned and + * added as metadata to every token that is created from the object to be tokenized. + * * @static * @param {?(string|object|object[])} obj - The object to convert into tokens + * @param {?object} metadata - Optional metadata to associate with every token * @returns {lunr.Token[]} + * @see {@link lunr.Pipeline} */ -lunr.tokenizer = function (obj) { +lunr.tokenizer = function (obj, metadata) { if (obj == null || obj == undefined) { return [] } if (Array.isArray(obj)) { return obj.map(function (t) { - return new lunr.Token(lunr.utils.asString(t).toLowerCase()) + return new lunr.Token( + lunr.utils.asString(t).toLowerCase(), + lunr.utils.clone(metadata) + ) }) } @@ -246,11 +435,15 @@ lunr.tokenizer = function (obj) { if ((char.match(lunr.tokenizer.separator) || sliceEnd == len)) { if (sliceLength > 0) { + var tokenMetadata = lunr.utils.clone(metadata) || {} + tokenMetadata["position"] = [sliceStart, sliceLength] + tokenMetadata["index"] = tokens.length + tokens.push( - new lunr.Token (str.slice(sliceStart, sliceEnd), { - position: [sliceStart, sliceLength], - index: tokens.length - }) + new lunr.Token ( + str.slice(sliceStart, sliceEnd), + tokenMetadata + ) ) } @@ -272,7 +465,7 @@ lunr.tokenizer = function (obj) { lunr.tokenizer.separator = /[\s\-]+/ /*! * lunr.Pipeline - * Copyright (C) 2017 Oliver Nightingale + * Copyright (C) 2018 Oliver Nightingale */ /** @@ -475,14 +668,23 @@ lunr.Pipeline.prototype.run = function (tokens) { for (var i = 0; i < stackLength; i++) { var fn = this._stack[i] + var memo = [] - tokens = tokens.reduce(function (memo, token, j) { - var result = fn(token, j, tokens) + for (var j = 0; j < tokens.length; j++) { + var result = fn(tokens[j], j, tokens) - if (result === void 0 || result === '') return memo + if (result === void 0 || result === '') continue - return memo.concat(result) - }, []) + if (result instanceof Array) { + for (var k = 0; k < result.length; k++) { + memo.push(result[k]) + } + } else { + memo.push(result) + } + } + + tokens = memo } return tokens @@ -494,10 +696,12 @@ lunr.Pipeline.prototype.run = function (tokens) { * token and mapping the resulting tokens back to strings. * * @param {string} str - The string to pass through the pipeline. + * @param {?object} metadata - Optional metadata to associate with the token + * passed to the pipeline. * @returns {string[]} */ -lunr.Pipeline.prototype.runString = function (str) { - var token = new lunr.Token (str) +lunr.Pipeline.prototype.runString = function (str, metadata) { + var token = new lunr.Token (str, metadata) return this.run([token]).map(function (t) { return t.toString() @@ -528,7 +732,7 @@ lunr.Pipeline.prototype.toJSON = function () { } /*! * lunr.Vector - * Copyright (C) 2017 Oliver Nightingale + * Copyright (C) 2018 Oliver Nightingale */ /** @@ -689,15 +893,14 @@ lunr.Vector.prototype.dot = function (otherVector) { } /** - * Calculates the cosine similarity between this vector and another - * vector. + * Calculates the similarity between this vector and another vector. * * @param {lunr.Vector} otherVector - The other vector to calculate the * similarity with. * @returns {Number} */ lunr.Vector.prototype.similarity = function (otherVector) { - return this.dot(otherVector) / (this.magnitude() * otherVector.magnitude()) + return this.dot(otherVector) / this.magnitude() || 0 } /** @@ -726,7 +929,7 @@ lunr.Vector.prototype.toJSON = function () { /* eslint-disable */ /*! * lunr.stemmer - * Copyright (C) 2017 Oliver Nightingale + * Copyright (C) 2018 Oliver Nightingale * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt */ @@ -739,6 +942,7 @@ lunr.Vector.prototype.toJSON = function () { * @param {lunr.Token} token - The string to stem * @returns {lunr.Token} * @see {@link lunr.Pipeline} + * @function */ lunr.stemmer = (function(){ var step2list = { @@ -947,7 +1151,7 @@ lunr.stemmer = (function(){ lunr.Pipeline.registerFunction(lunr.stemmer, 'stemmer') /*! * lunr.stopWordFilter - * Copyright (C) 2017 Oliver Nightingale + * Copyright (C) 2018 Oliver Nightingale */ /** @@ -957,6 +1161,7 @@ lunr.Pipeline.registerFunction(lunr.stemmer, 'stemmer') * The built in lunr.stopWordFilter is built using this generator and can be used * to generate custom stopWordFilters for applications or non English languages. * + * @function * @param {Array} token The token to pass through the filter * @returns {lunr.PipelineFunction} * @see lunr.Pipeline @@ -980,6 +1185,7 @@ lunr.generateStopWordFilter = function (stopWords) { * This is intended to be used in the Pipeline. If the token does not pass the * filter then undefined will be returned. * + * @function * @implements {lunr.PipelineFunction} * @params {lunr.Token} token - A token to check for being a stop word. * @returns {lunr.Token} @@ -1110,7 +1316,7 @@ lunr.stopWordFilter = lunr.generateStopWordFilter([ lunr.Pipeline.registerFunction(lunr.stopWordFilter, 'stopWordFilter') /*! * lunr.trimmer - * Copyright (C) 2017 Oliver Nightingale + * Copyright (C) 2018 Oliver Nightingale */ /** @@ -1137,7 +1343,7 @@ lunr.trimmer = function (token) { lunr.Pipeline.registerFunction(lunr.trimmer, 'trimmer') /*! * lunr.TokenSet - * Copyright (C) 2017 Oliver Nightingale + * Copyright (C) 2018 Oliver Nightingale */ /** @@ -1379,14 +1585,13 @@ lunr.TokenSet.fromFuzzyString = function (str, editDistance) { */ lunr.TokenSet.fromString = function (str) { var node = new lunr.TokenSet, - root = node, - wildcardFound = false + root = node /* * Iterates through all characters within the passed string * appending a node for each character. * - * As soon as a wildcard character is found then a self + * When a wildcard character is found then a self * referencing edge is introduced to continually match * any number of any characters. */ @@ -1395,7 +1600,6 @@ lunr.TokenSet.fromString = function (str) { final = (i == len - 1) if (char == "*") { - wildcardFound = true node.edges[char] = node node.final = final @@ -1405,11 +1609,6 @@ lunr.TokenSet.fromString = function (str) { node.edges[char] = next node = next - - // TODO: is this needed anymore? - if (wildcardFound) { - node.edges["*"] = root - } } } @@ -1436,6 +1635,11 @@ lunr.TokenSet.prototype.toArray = function () { len = edges.length if (frame.node.final) { + /* In Safari, at this point the prefix is sometimes corrupted, see: + * https://github.com/olivernn/lunr.js/issues/279 Calling any + * String.prototype method forces Safari to "cast" this string to what + * it's supposed to be, fixing the bug. */ + frame.prefix.charAt(0) words.push(frame.prefix) } @@ -1632,7 +1836,7 @@ lunr.TokenSet.Builder.prototype.minimize = function (downTo) { } /*! * lunr.Index - * Copyright (C) 2017 Oliver Nightingale + * Copyright (C) 2018 Oliver Nightingale */ /** @@ -1646,7 +1850,7 @@ lunr.TokenSet.Builder.prototype.minimize = function (downTo) { * @constructor * @param {Object} attrs - The attributes of the built search index. * @param {Object} attrs.invertedIndex - An index of term/field to document reference. - * @param {Object} attrs.documentVectors - Document vectors keyed by document reference. + * @param {Object} attrs.fieldVectors - Field vectors * @param {lunr.TokenSet} attrs.tokenSet - An set of all corpus tokens. * @param {string[]} attrs.fields - The names of indexed document fields. * @param {lunr.Pipeline} attrs.pipeline - The pipeline to use for search terms. @@ -1692,6 +1896,12 @@ lunr.Index = function (attrs) { * to provide fuzzy matching, e.g. 'hello~2' will match documents with hello with an edit distance of 2. * Avoid large values for edit distance to improve query performance. * + * Each term also supports a presence modifier. By default a term's presence in document is optional, however + * this can be changed to either required or prohibited. For a term's presence to be required in a document the + * term should be prefixed with a '+', e.g. `+foo bar` is a search for documents that must contain 'foo' and + * optionally contain 'bar'. Conversely a leading '-' sets the terms presence to prohibited, i.e. it must not + * appear in a document, e.g. `-foo bar` is a search for documents that do not contain 'foo' but may contain 'bar'. + * * To escape special characters the backslash character '\' can be used, this allows searches to include * characters that would normally be considered modifiers, e.g. `foo\~2` will search for a term "foo~2" instead * of attempting to apply a boost of 2 to the search term "foo". @@ -1707,13 +1917,16 @@ lunr.Index = function (attrs) { * hello^10 * @example term with an edit distance of 2 * hello~2 + * @example terms with presence modifiers + * -foo +bar baz */ /** * Performs a search against the index using lunr query syntax. * * Results will be returned sorted by their score, the most relevant results - * will be returned first. + * will be returned first. For details on how the score is calculated, please see + * the {@link https://lunrjs.com/guides/searching.html#scoring|guide}. * * For more programmatic querying use lunr.Index#query. * @@ -1764,7 +1977,18 @@ lunr.Index.prototype.query = function (fn) { var query = new lunr.Query(this.fields), matchingFields = Object.create(null), queryVectors = Object.create(null), - termFieldCache = Object.create(null) + termFieldCache = Object.create(null), + requiredMatches = Object.create(null), + prohibitedMatches = Object.create(null) + + /* + * To support field level boosts a query vector is created per + * field. An empty vector is eagerly created to support negated + * queries. + */ + for (var i = 0; i < this.fields.length; i++) { + queryVectors[this.fields[i]] = new lunr.Vector + } fn.call(query, query) @@ -1778,10 +2002,13 @@ lunr.Index.prototype.query = function (fn) { * for a single query term. */ var clause = query.clauses[i], - terms = null + terms = null, + clauseMatches = lunr.Set.complete if (clause.usePipeline) { - terms = this.pipeline.runString(clause.term) + terms = this.pipeline.runString(clause.term, { + fields: clause.fields + }) } else { terms = [clause.term] } @@ -1805,6 +2032,21 @@ lunr.Index.prototype.query = function (fn) { var termTokenSet = lunr.TokenSet.fromClause(clause), expandedTerms = this.tokenSet.intersect(termTokenSet).toArray() + /* + * If a term marked as required does not exist in the tokenSet it is + * impossible for the search to return any matches. We set all the field + * scoped required matches set to empty and stop examining any further + * clauses. + */ + if (expandedTerms.length === 0 && clause.presence === lunr.Query.presence.REQUIRED) { + for (var k = 0; k < clause.fields.length; k++) { + var field = clause.fields[k] + requiredMatches[field] = lunr.Set.empty + } + + break + } + for (var j = 0; j < expandedTerms.length; j++) { /* * For each term get the posting and termIndex, this is required for @@ -1826,26 +2068,50 @@ lunr.Index.prototype.query = function (fn) { var field = clause.fields[k], fieldPosting = posting[field], matchingDocumentRefs = Object.keys(fieldPosting), - termField = expandedTerm + "/" + field + termField = expandedTerm + "/" + field, + matchingDocumentsSet = new lunr.Set(matchingDocumentRefs) /* - * To support field level boosts a query vector is created per - * field. This vector is populated using the termIndex found for - * the term and a unit value with the appropriate boost applied. + * if the presence of this term is required ensure that the matching + * documents are added to the set of required matches for this clause. * - * If the query vector for this field does not exist yet it needs - * to be created. */ - if (queryVectors[field] === undefined) { - queryVectors[field] = new lunr.Vector + if (clause.presence == lunr.Query.presence.REQUIRED) { + clauseMatches = clauseMatches.union(matchingDocumentsSet) + + if (requiredMatches[field] === undefined) { + requiredMatches[field] = lunr.Set.complete + } } /* + * if the presence of this term is prohibited ensure that the matching + * documents are added to the set of prohibited matches for this field, + * creating that set if it does not yet exist. + */ + if (clause.presence == lunr.Query.presence.PROHIBITED) { + if (prohibitedMatches[field] === undefined) { + prohibitedMatches[field] = lunr.Set.empty + } + + prohibitedMatches[field] = prohibitedMatches[field].union(matchingDocumentsSet) + + /* + * Prohibited matches should not be part of the query vector used for + * similarity scoring and no metadata should be extracted so we continue + * to the next field + */ + continue + } + + /* + * The query field vector is populated using the termIndex found for + * the term and a unit value with the appropriate boost applied. * Using upsert because there could already be an entry in the vector * for the term we are working with. In that case we just add the scores * together. */ - queryVectors[field].upsert(termIndex, 1 * clause.boost, function (a, b) { return a + b }) + queryVectors[field].upsert(termIndex, clause.boost, function (a, b) { return a + b }) /** * If we've already seen this term, field combo then we've already collected @@ -1879,12 +2145,65 @@ lunr.Index.prototype.query = function (fn) { } } } + + /** + * If the presence was required we need to update the requiredMatches field sets. + * We do this after all fields for the term have collected their matches because + * the clause terms presence is required in _any_ of the fields not _all_ of the + * fields. + */ + if (clause.presence === lunr.Query.presence.REQUIRED) { + for (var k = 0; k < clause.fields.length; k++) { + var field = clause.fields[k] + requiredMatches[field] = requiredMatches[field].intersect(clauseMatches) + } + } + } + + /** + * Need to combine the field scoped required and prohibited + * matching documents into a global set of required and prohibited + * matches + */ + var allRequiredMatches = lunr.Set.complete, + allProhibitedMatches = lunr.Set.empty + + for (var i = 0; i < this.fields.length; i++) { + var field = this.fields[i] + + if (requiredMatches[field]) { + allRequiredMatches = allRequiredMatches.intersect(requiredMatches[field]) + } + + if (prohibitedMatches[field]) { + allProhibitedMatches = allProhibitedMatches.union(prohibitedMatches[field]) + } } var matchingFieldRefs = Object.keys(matchingFields), results = [], matches = Object.create(null) + /* + * If the query is negated (contains only prohibited terms) + * we need to get _all_ fieldRefs currently existing in the + * index. This is only done when we know that the query is + * entirely prohibited terms to avoid any cost of getting all + * fieldRefs unnecessarily. + * + * Additionally, blank MatchData must be created to correctly + * populate the results. + */ + if (query.isNegated()) { + matchingFieldRefs = Object.keys(this.fieldVectors) + + for (var i = 0; i < matchingFieldRefs.length; i++) { + var matchingFieldRef = matchingFieldRefs[i] + var fieldRef = lunr.FieldRef.fromString(matchingFieldRef) + matchingFields[matchingFieldRef] = new lunr.MatchData + } + } + for (var i = 0; i < matchingFieldRefs.length; i++) { /* * Currently we have document fields that match the query, but we @@ -1895,8 +2214,17 @@ lunr.Index.prototype.query = function (fn) { * above, and combined into a final document score using addition. */ var fieldRef = lunr.FieldRef.fromString(matchingFieldRefs[i]), - docRef = fieldRef.docRef, - fieldVector = this.fieldVectors[fieldRef], + docRef = fieldRef.docRef + + if (!allRequiredMatches.contains(docRef)) { + continue + } + + if (allProhibitedMatches.contains(docRef)) { + continue + } + + var fieldVector = this.fieldVectors[fieldRef], score = queryVectors[fieldRef.fieldName].similarity(fieldVector), docMatch @@ -2000,7 +2328,7 @@ lunr.Index.load = function (serializedIndex) { } /*! * lunr.Builder - * Copyright (C) 2017 Oliver Nightingale + * Copyright (C) 2018 Oliver Nightingale */ /** @@ -2029,7 +2357,8 @@ lunr.Index.load = function (serializedIndex) { */ lunr.Builder = function () { this._ref = "id" - this._fields = [] + this._fields = Object.create(null) + this._documents = Object.create(null) this.invertedIndex = Object.create(null) this.fieldTermFrequencies = {} this.fieldLengths = {} @@ -2059,6 +2388,20 @@ lunr.Builder.prototype.ref = function (ref) { this._ref = ref } +/** + * A function that is used to extract a field from a document. + * + * Lunr expects a field to be at the top level of a document, if however the field + * is deeply nested within a document an extractor function can be used to extract + * the right field for indexing. + * + * @callback fieldExtractor + * @param {object} doc - The document being added to the index. + * @returns {?(string|object|object[])} obj - The object that will be indexed for this field. + * @example Extracting a nested field + * function (doc) { return doc.nested.field } + */ + /** * Adds a field to the list of document fields that will be indexed. Every document being * indexed should have this field. Null values for this field in indexed documents will @@ -2067,10 +2410,22 @@ lunr.Builder.prototype.ref = function (ref) { * All fields should be added before adding documents to the index. Adding fields after * a document has been indexed will have no effect on already indexed documents. * - * @param {string} field - The name of a field to index in all documents. + * Fields can be boosted at build time. This allows terms within that field to have more + * importance when ranking search results. Use a field boost to specify that matches within + * one field are more important than other fields. + * + * @param {string} fieldName - The name of a field to index in all documents. + * @param {object} attributes - Optional attributes associated with this field. + * @param {number} [attributes.boost=1] - Boost applied to all terms within this field. + * @param {fieldExtractor} [attributes.extractor] - Function to extract a field from a document. + * @throws {RangeError} fieldName cannot contain unsupported characters '/' */ -lunr.Builder.prototype.field = function (field) { - this._fields.push(field) +lunr.Builder.prototype.field = function (fieldName, attributes) { + if (/\//.test(fieldName)) { + throw new RangeError ("Field '" + fieldName + "' contains illegal character '/'") + } + + this._fields[fieldName] = attributes || {} } /** @@ -2112,17 +2467,27 @@ lunr.Builder.prototype.k1 = function (number) { * it should have all fields defined for indexing, though null or undefined values will not * cause errors. * + * Entire documents can be boosted at build time. Applying a boost to a document indicates that + * this document should rank higher in search results than other documents. + * * @param {object} doc - The document to add to the index. + * @param {object} attributes - Optional attributes associated with this document. + * @param {number} [attributes.boost=1] - Boost applied to all terms within this document. */ -lunr.Builder.prototype.add = function (doc) { - var docRef = doc[this._ref] +lunr.Builder.prototype.add = function (doc, attributes) { + var docRef = doc[this._ref], + fields = Object.keys(this._fields) + this._documents[docRef] = attributes || {} this.documentCount += 1 - for (var i = 0; i < this._fields.length; i++) { - var fieldName = this._fields[i], - field = doc[fieldName], - tokens = this.tokenizer(field), + for (var i = 0; i < fields.length; i++) { + var fieldName = fields[i], + extractor = this._fields[fieldName].extractor, + field = extractor ? extractor(doc) : doc[fieldName], + tokens = this.tokenizer(field, { + fields: [fieldName] + }), terms = this.pipeline.run(tokens), fieldRef = new lunr.FieldRef (docRef, fieldName), fieldTerms = Object.create(null) @@ -2150,8 +2515,8 @@ lunr.Builder.prototype.add = function (doc) { posting["_index"] = this.termIndex this.termIndex += 1 - for (var k = 0; k < this._fields.length; k++) { - posting[this._fields[k]] = Object.create(null) + for (var k = 0; k < fields.length; k++) { + posting[fields[k]] = Object.create(null) } this.invertedIndex[term] = posting @@ -2202,9 +2567,11 @@ lunr.Builder.prototype.calculateAverageFieldLengths = function () { accumulator[field] += this.fieldLengths[fieldRef] } - for (var i = 0; i < this._fields.length; i++) { - var field = this._fields[i] - accumulator[field] = accumulator[field] / documentsWithField[field] + var fields = Object.keys(this._fields) + + for (var i = 0; i < fields.length; i++) { + var fieldName = fields[i] + accumulator[fieldName] = accumulator[fieldName] / documentsWithField[fieldName] } this.averageFieldLength = accumulator @@ -2223,13 +2590,17 @@ lunr.Builder.prototype.createFieldVectors = function () { for (var i = 0; i < fieldRefsLength; i++) { var fieldRef = lunr.FieldRef.fromString(fieldRefs[i]), - field = fieldRef.fieldName, + fieldName = fieldRef.fieldName, fieldLength = this.fieldLengths[fieldRef], fieldVector = new lunr.Vector, termFrequencies = this.fieldTermFrequencies[fieldRef], terms = Object.keys(termFrequencies), termsLength = terms.length + + var fieldBoost = this._fields[fieldName].boost || 1, + docBoost = this._documents[fieldRef.docRef].boost || 1 + for (var j = 0; j < termsLength; j++) { var term = terms[j], tf = termFrequencies[term], @@ -2243,7 +2614,9 @@ lunr.Builder.prototype.createFieldVectors = function () { idf = termIdfCache[term] } - score = idf * ((this._k1 + 1) * tf) / (this._k1 * (1 - this._b + this._b * (fieldLength / this.averageFieldLength[field])) + tf) + score = idf * ((this._k1 + 1) * tf) / (this._k1 * (1 - this._b + this._b * (fieldLength / this.averageFieldLength[fieldName])) + tf) + score *= fieldBoost + score *= docBoost scoreWithPrecision = Math.round(score * 1000) / 1000 // Converts 1.23456789 to 1.234. // Reducing the precision so that the vectors take up less @@ -2289,7 +2662,7 @@ lunr.Builder.prototype.build = function () { invertedIndex: this.invertedIndex, fieldVectors: this.fieldVectors, tokenSet: this.tokenSet, - fields: this._fields, + fields: Object.keys(this._fields), pipeline: this.searchPipeline }) } @@ -2327,7 +2700,7 @@ lunr.Builder.prototype.use = function (fn) { */ lunr.MatchData = function (term, field, metadata) { var clonedMetadata = Object.create(null), - metadataKeys = Object.keys(metadata) + metadataKeys = Object.keys(metadata || {}) // Cloning the metadata to prevent the original // being mutated during match data combination. @@ -2340,8 +2713,11 @@ lunr.MatchData = function (term, field, metadata) { } this.metadata = Object.create(null) - this.metadata[term] = Object.create(null) - this.metadata[term][field] = clonedMetadata + + if (term !== undefined) { + this.metadata[term] = Object.create(null) + this.metadata[term][field] = clonedMetadata + } } /** @@ -2456,11 +2832,42 @@ lunr.Query = function (allFields) { * wildcard: lunr.Query.wildcard.LEADING | lunr.Query.wildcard.TRAILING * }) */ + lunr.Query.wildcard = new String ("*") lunr.Query.wildcard.NONE = 0 lunr.Query.wildcard.LEADING = 1 lunr.Query.wildcard.TRAILING = 2 +/** + * Constants for indicating what kind of presence a term must have in matching documents. + * + * @constant + * @enum {number} + * @see lunr.Query~Clause + * @see lunr.Query#clause + * @see lunr.Query#term + * @example query term with required presence + * query.term('foo', { presence: lunr.Query.presence.REQUIRED }) + */ +lunr.Query.presence = { + /** + * Term's presence in a document is optional, this is the default value. + */ + OPTIONAL: 1, + + /** + * Term's presence in a document is required, documents that do not contain + * this term will not be returned. + */ + REQUIRED: 2, + + /** + * Term's presence in a document is prohibited, documents that do contain + * this term will not be returned. + */ + PROHIBITED: 3 +} + /** * A single clause in a {@link lunr.Query} contains a term and details on how to * match that term against a {@link lunr.Index}. @@ -2470,7 +2877,8 @@ lunr.Query.wildcard.TRAILING = 2 * @property {number} [boost=1] - Any boost that should be applied when matching this clause. * @property {number} [editDistance] - Whether the term should have fuzzy matching applied, and how fuzzy the match should be. * @property {boolean} [usePipeline] - Whether the term should be passed through the search pipeline. - * @property {number} [wildcard=0] - Whether the term should have wildcards appended or prepended. + * @property {number} [wildcard=lunr.Query.wildcard.NONE] - Whether the term should have wildcards appended or prepended. + * @property {number} [presence=lunr.Query.presence.OPTIONAL] - The terms presence in any matching documents. */ /** @@ -2508,17 +2916,44 @@ lunr.Query.prototype.clause = function (clause) { clause.term = "" + clause.term + "*" } + if (!('presence' in clause)) { + clause.presence = lunr.Query.presence.OPTIONAL + } + this.clauses.push(clause) return this } +/** + * A negated query is one in which every clause has a presence of + * prohibited. These queries require some special processing to return + * the expected results. + * + * @returns boolean + */ +lunr.Query.prototype.isNegated = function () { + for (var i = 0; i < this.clauses.length; i++) { + if (this.clauses[i].presence != lunr.Query.presence.PROHIBITED) { + return false + } + } + + return true +} + /** * Adds a term to the current query, under the covers this will create a {@link lunr.Query~Clause} * to the list of clauses that make up this query. * - * @param {string} term - The term to add to the query. - * @param {Object} [options] - Any additional properties to add to the query clause. + * The term is used as is, i.e. no tokenization will be performed by this method. Instead conversion + * to a token or token-like string should be done before calling this method. + * + * The term will be converted to a string by calling `toString`. Multiple terms can be passed as an + * array, each term in the array will share the same options. + * + * @param {object|object[]} term - The term(s) to add to the query. + * @param {object} [options] - Any additional properties to add to the query clause. * @returns {lunr.Query} * @see lunr.Query#clause * @see lunr.Query~Clause @@ -2530,10 +2965,17 @@ lunr.Query.prototype.clause = function (clause) { * boost: 10, * wildcard: lunr.Query.wildcard.TRAILING * }) + * @example using lunr.tokenizer to convert a string to tokens before using them as terms + * query.term(lunr.tokenizer("foo bar")) */ lunr.Query.prototype.term = function (term, options) { + if (Array.isArray(term)) { + term.forEach(function (t) { this.term(t, lunr.utils.clone(options)) }, this) + return this + } + var clause = options || {} - clause.term = term + clause.term = term.toString() this.clause(clause) @@ -2645,6 +3087,7 @@ lunr.QueryLexer.FIELD = 'FIELD' lunr.QueryLexer.TERM = 'TERM' lunr.QueryLexer.EDIT_DISTANCE = 'EDIT_DISTANCE' lunr.QueryLexer.BOOST = 'BOOST' +lunr.QueryLexer.PRESENCE = 'PRESENCE' lunr.QueryLexer.lexField = function (lexer) { lexer.backup() @@ -2733,6 +3176,22 @@ lunr.QueryLexer.lexText = function (lexer) { return lunr.QueryLexer.lexBoost } + // "+" indicates term presence is required + // checking for length to ensure that only + // leading "+" are considered + if (char == "+" && lexer.width() === 1) { + lexer.emit(lunr.QueryLexer.PRESENCE) + return lunr.QueryLexer.lexText + } + + // "-" indicates term presence is prohibited + // checking for length to ensure that only + // leading "-" are considered + if (char == "-" && lexer.width() === 1) { + lexer.emit(lunr.QueryLexer.PRESENCE) + return lunr.QueryLexer.lexText + } + if (char.match(lunr.QueryLexer.termSeparator)) { return lunr.QueryLexer.lexTerm } @@ -2750,7 +3209,7 @@ lunr.QueryParser.prototype.parse = function () { this.lexer.run() this.lexemes = this.lexer.lexemes - var state = lunr.QueryParser.parseFieldOrTerm + var state = lunr.QueryParser.parseClause while (state) { state = state(this) @@ -2775,7 +3234,7 @@ lunr.QueryParser.prototype.nextClause = function () { this.currentClause = {} } -lunr.QueryParser.parseFieldOrTerm = function (parser) { +lunr.QueryParser.parseClause = function (parser) { var lexeme = parser.peekLexeme() if (lexeme == undefined) { @@ -2783,6 +3242,8 @@ lunr.QueryParser.parseFieldOrTerm = function (parser) { } switch (lexeme.type) { + case lunr.QueryLexer.PRESENCE: + return lunr.QueryParser.parsePresence case lunr.QueryLexer.FIELD: return lunr.QueryParser.parseField case lunr.QueryLexer.TERM: @@ -2798,6 +3259,43 @@ lunr.QueryParser.parseFieldOrTerm = function (parser) { } } +lunr.QueryParser.parsePresence = function (parser) { + var lexeme = parser.consumeLexeme() + + if (lexeme == undefined) { + return + } + + switch (lexeme.str) { + case "-": + parser.currentClause.presence = lunr.Query.presence.PROHIBITED + break + case "+": + parser.currentClause.presence = lunr.Query.presence.REQUIRED + break + default: + var errorMessage = "unrecognised presence operator'" + lexeme.str + "'" + throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end) + } + + var nextLexeme = parser.peekLexeme() + + if (nextLexeme == undefined) { + var errorMessage = "expecting term or field, found nothing" + throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end) + } + + switch (nextLexeme.type) { + case lunr.QueryLexer.FIELD: + return lunr.QueryParser.parseField + case lunr.QueryLexer.TERM: + return lunr.QueryParser.parseTerm + default: + var errorMessage = "expecting term or field, found '" + nextLexeme.type + "'" + throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end) + } +} + lunr.QueryParser.parseField = function (parser) { var lexeme = parser.consumeLexeme() @@ -2861,6 +3359,9 @@ lunr.QueryParser.parseTerm = function (parser) { return lunr.QueryParser.parseEditDistance case lunr.QueryLexer.BOOST: return lunr.QueryParser.parseBoost + case lunr.QueryLexer.PRESENCE: + parser.nextClause() + return lunr.QueryParser.parsePresence default: var errorMessage = "Unexpected lexeme type '" + nextLexeme.type + "'" throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end) @@ -2901,6 +3402,9 @@ lunr.QueryParser.parseEditDistance = function (parser) { return lunr.QueryParser.parseEditDistance case lunr.QueryLexer.BOOST: return lunr.QueryParser.parseBoost + case lunr.QueryLexer.PRESENCE: + parser.nextClause() + return lunr.QueryParser.parsePresence default: var errorMessage = "Unexpected lexeme type '" + nextLexeme.type + "'" throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end) @@ -2941,6 +3445,9 @@ lunr.QueryParser.parseBoost = function (parser) { return lunr.QueryParser.parseEditDistance case lunr.QueryLexer.BOOST: return lunr.QueryParser.parseBoost + case lunr.QueryLexer.PRESENCE: + parser.nextClause() + return lunr.QueryParser.parsePresence default: var errorMessage = "Unexpected lexeme type '" + nextLexeme.type + "'" throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end) @@ -2974,4 +3481,4 @@ lunr.QueryParser.parseBoost = function (parser) { */ return lunr })) -})(); +})(); \ No newline at end of file diff --git a/assets/js/lunr/lunr.min.js b/assets/js/lunr/lunr.min.js index 75f6a2b7..4c101276 100644 --- a/assets/js/lunr/lunr.min.js +++ b/assets/js/lunr/lunr.min.js @@ -1,6 +1,6 @@ /** - * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.1.5 - * Copyright (C) 2017 Oliver Nightingale + * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.3 + * Copyright (C) 2018 Oliver Nightingale * @license MIT */ -!function(){var e=function(t){var r=new e.Builder;return r.pipeline.add(e.trimmer,e.stopWordFilter,e.stemmer),r.searchPipeline.add(e.stemmer),t.call(r,r),r.build()};e.version="2.1.5",e.utils={},e.utils.warn=function(e){return function(t){e.console&&console.warn&&console.warn(t)}}(this),e.utils.asString=function(e){return void 0===e||null===e?"":e.toString()},e.FieldRef=function(e,t,r){this.docRef=e,this.fieldName=t,this._stringValue=r},e.FieldRef.joiner="/",e.FieldRef.fromString=function(t){var r=t.indexOf(e.FieldRef.joiner);if(-1===r)throw"malformed field ref string";var i=t.slice(0,r),n=t.slice(r+1);return new e.FieldRef(n,i,t)},e.FieldRef.prototype.toString=function(){return void 0==this._stringValue&&(this._stringValue=this.fieldName+e.FieldRef.joiner+this.docRef),this._stringValue},e.idf=function(e,t){var r=0;for(var i in e)"_index"!=i&&(r+=Object.keys(e[i]).length);var n=(t-r+.5)/(r+.5);return Math.log(1+Math.abs(n))},e.Token=function(e,t){this.str=e||"",this.metadata=t||{}},e.Token.prototype.toString=function(){return this.str},e.Token.prototype.update=function(e){return this.str=e(this.str,this.metadata),this},e.Token.prototype.clone=function(t){return t=t||function(e){return e},new e.Token(t(this.str,this.metadata),this.metadata)},e.tokenizer=function(t){if(null==t||void 0==t)return[];if(Array.isArray(t))return t.map(function(t){return new e.Token(e.utils.asString(t).toLowerCase())});for(var r=t.toString().trim().toLowerCase(),i=r.length,n=[],s=0,o=0;i>=s;s++){var a=r.charAt(s),u=s-o;(a.match(e.tokenizer.separator)||s==i)&&(u>0&&n.push(new e.Token(r.slice(o,s),{position:[o,u],index:n.length})),o=s+1)}return n},e.tokenizer.separator=/[\s\-]+/,e.Pipeline=function(){this._stack=[]},e.Pipeline.registeredFunctions=Object.create(null),e.Pipeline.registerFunction=function(t,r){r in this.registeredFunctions&&e.utils.warn("Overwriting existing registered function: "+r),t.label=r,e.Pipeline.registeredFunctions[t.label]=t},e.Pipeline.warnIfFunctionNotRegistered=function(t){var r=t.label&&t.label in this.registeredFunctions;r||e.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",t)},e.Pipeline.load=function(t){var r=new e.Pipeline;return t.forEach(function(t){var i=e.Pipeline.registeredFunctions[t];if(!i)throw new Error("Cannot load unregistered function: "+t);r.add(i)}),r},e.Pipeline.prototype.add=function(){var t=Array.prototype.slice.call(arguments);t.forEach(function(t){e.Pipeline.warnIfFunctionNotRegistered(t),this._stack.push(t)},this)},e.Pipeline.prototype.after=function(t,r){e.Pipeline.warnIfFunctionNotRegistered(r);var i=this._stack.indexOf(t);if(-1==i)throw new Error("Cannot find existingFn");i+=1,this._stack.splice(i,0,r)},e.Pipeline.prototype.before=function(t,r){e.Pipeline.warnIfFunctionNotRegistered(r);var i=this._stack.indexOf(t);if(-1==i)throw new Error("Cannot find existingFn");this._stack.splice(i,0,r)},e.Pipeline.prototype.remove=function(e){var t=this._stack.indexOf(e);-1!=t&&this._stack.splice(t,1)},e.Pipeline.prototype.run=function(e){for(var t=this._stack.length,r=0;t>r;r++){var i=this._stack[r];e=e.reduce(function(t,r,n){var s=i(r,n,e);return void 0===s||""===s?t:t.concat(s)},[])}return e},e.Pipeline.prototype.runString=function(t){var r=new e.Token(t);return this.run([r]).map(function(e){return e.toString()})},e.Pipeline.prototype.reset=function(){this._stack=[]},e.Pipeline.prototype.toJSON=function(){return this._stack.map(function(t){return e.Pipeline.warnIfFunctionNotRegistered(t),t.label})},e.Vector=function(e){this._magnitude=0,this.elements=e||[]},e.Vector.prototype.positionForIndex=function(e){if(0==this.elements.length)return 0;for(var t=0,r=this.elements.length/2,i=r-t,n=Math.floor(i/2),s=this.elements[2*n];i>1&&(e>s&&(t=n),s>e&&(r=n),s!=e);)i=r-t,n=t+Math.floor(i/2),s=this.elements[2*n];return s==e?2*n:s>e?2*n:e>s?2*(n+1):void 0},e.Vector.prototype.insert=function(e,t){this.upsert(e,t,function(){throw"duplicate index"})},e.Vector.prototype.upsert=function(e,t,r){this._magnitude=0;var i=this.positionForIndex(e);this.elements[i]==e?this.elements[i+1]=r(this.elements[i+1],t):this.elements.splice(i,0,e,t)},e.Vector.prototype.magnitude=function(){if(this._magnitude)return this._magnitude;for(var e=0,t=this.elements.length,r=1;t>r;r+=2){var i=this.elements[r];e+=i*i}return this._magnitude=Math.sqrt(e)},e.Vector.prototype.dot=function(e){for(var t=0,r=this.elements,i=e.elements,n=r.length,s=i.length,o=0,a=0,u=0,l=0;n>u&&s>l;)o=r[u],a=i[l],a>o?u+=2:o>a?l+=2:o==a&&(t+=r[u+1]*i[l+1],u+=2,l+=2);return t},e.Vector.prototype.similarity=function(e){return this.dot(e)/(this.magnitude()*e.magnitude())},e.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),t=1,r=0;ti;i++)r.insert(t[i]);return r.finish(),r.root},e.TokenSet.fromClause=function(t){return"editDistance"in t?e.TokenSet.fromFuzzyString(t.term,t.editDistance):e.TokenSet.fromString(t.term)},e.TokenSet.fromFuzzyString=function(t,r){for(var i=new e.TokenSet,n=[{node:i,editsRemaining:r,str:t}];n.length;){var s=n.pop();if(s.str.length>0){var o,a=s.str.charAt(0);a in s.node.edges?o=s.node.edges[a]:(o=new e.TokenSet,s.node.edges[a]=o),1==s.str.length?o["final"]=!0:n.push({node:o,editsRemaining:s.editsRemaining,str:s.str.slice(1)})}if(s.editsRemaining>0&&s.str.length>1){var u,a=s.str.charAt(1);a in s.node.edges?u=s.node.edges[a]:(u=new e.TokenSet,s.node.edges[a]=u),s.str.length<=2?u["final"]=!0:n.push({node:u,editsRemaining:s.editsRemaining-1,str:s.str.slice(2)})}if(s.editsRemaining>0&&1==s.str.length&&(s.node["final"]=!0),s.editsRemaining>0&&s.str.length>=1){if("*"in s.node.edges)var l=s.node.edges["*"];else{var l=new e.TokenSet;s.node.edges["*"]=l}1==s.str.length?l["final"]=!0:n.push({node:l,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)})}if(s.editsRemaining>0){if("*"in s.node.edges)var d=s.node.edges["*"];else{var d=new e.TokenSet;s.node.edges["*"]=d}0==s.str.length?d["final"]=!0:n.push({node:d,editsRemaining:s.editsRemaining-1,str:s.str})}if(s.editsRemaining>0&&s.str.length>1){var h,c=s.str.charAt(0),f=s.str.charAt(1);f in s.node.edges?h=s.node.edges[f]:(h=new e.TokenSet,s.node.edges[f]=h),1==s.str.length?h["final"]=!0:n.push({node:h,editsRemaining:s.editsRemaining-1,str:c+s.str.slice(2)})}}return i},e.TokenSet.fromString=function(t){for(var r=new e.TokenSet,i=r,n=!1,s=0,o=t.length;o>s;s++){var a=t[s],u=s==o-1;if("*"==a)n=!0,r.edges[a]=r,r["final"]=u;else{var l=new e.TokenSet;l["final"]=u,r.edges[a]=l,r=l,n&&(r.edges["*"]=i)}}return i},e.TokenSet.prototype.toArray=function(){for(var e=[],t=[{prefix:"",node:this}];t.length;){var r=t.pop(),i=Object.keys(r.node.edges),n=i.length;r.node["final"]&&e.push(r.prefix);for(var s=0;n>s;s++){var o=i[s];t.push({prefix:r.prefix.concat(o),node:r.node.edges[o]})}}return e},e.TokenSet.prototype.toString=function(){if(this._str)return this._str;for(var e=this["final"]?"1":"0",t=Object.keys(this.edges).sort(),r=t.length,i=0;r>i;i++){var n=t[i],s=this.edges[n];e=e+n+s.id}return e},e.TokenSet.prototype.intersect=function(t){for(var r=new e.TokenSet,i=void 0,n=[{qNode:t,output:r,node:this}];n.length;){i=n.pop();for(var s=Object.keys(i.qNode.edges),o=s.length,a=Object.keys(i.node.edges),u=a.length,l=0;o>l;l++)for(var d=s[l],h=0;u>h;h++){var c=a[h];if(c==d||"*"==d){var f=i.node.edges[c],p=i.qNode.edges[d],y=f["final"]&&p["final"],m=void 0;c in i.output.edges?(m=i.output.edges[c],m["final"]=m["final"]||y):(m=new e.TokenSet,m["final"]=y,i.output.edges[c]=m),n.push({qNode:p,output:m,node:f})}}}return r},e.TokenSet.Builder=function(){this.previousWord="",this.root=new e.TokenSet,this.uncheckedNodes=[],this.minimizedNodes={}},e.TokenSet.Builder.prototype.insert=function(t){var r,i=0;if(t=e;t--){var r=this.uncheckedNodes[t],i=r.child.toString();i in this.minimizedNodes?r.parent.edges[r["char"]]=this.minimizedNodes[i]:(r.child._str=i,this.minimizedNodes[i]=r.child),this.uncheckedNodes.pop()}},e.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},e.Index.prototype.search=function(t){return this.query(function(r){var i=new e.QueryParser(t,r);i.parse()})},e.Index.prototype.query=function(t){var r=new e.Query(this.fields),i=Object.create(null),n=Object.create(null),s=Object.create(null);t.call(r,r);for(var o=0;oe?this._b=0:e>1?this._b=1:this._b=e},e.Builder.prototype.k1=function(e){this._k1=e},e.Builder.prototype.add=function(t){var r=t[this._ref];this.documentCount+=1;for(var i=0;is;s++){var o=e.FieldRef.fromString(t[s]),a=o.fieldName;n[a]||(n[a]=0),n[a]+=1,i[a]||(i[a]=0),i[a]+=this.fieldLengths[o]}for(var s=0;ss;s++){for(var o=e.FieldRef.fromString(r[s]),a=o.fieldName,u=this.fieldLengths[o],l=new e.Vector,d=this.fieldTermFrequencies[o],h=Object.keys(d),c=h.length,f=0;c>f;f++){var p,y,m,v=h[f],g=d[v],x=this.invertedIndex[v]._index;void 0===n[v]?(p=e.idf(this.invertedIndex[v],this.documentCount),n[v]=p):p=n[v],y=p*((this._k1+1)*g)/(this._k1*(1-this._b+this._b*(u/this.averageFieldLength[a]))+g),m=Math.round(1e3*y)/1e3,l.insert(x,m)}t[o]=l}this.fieldVectors=t},e.Builder.prototype.createTokenSet=function(){this.tokenSet=e.TokenSet.fromArray(Object.keys(this.invertedIndex).sort())},e.Builder.prototype.build=function(){return this.calculateAverageFieldLengths(),this.createFieldVectors(),this.createTokenSet(),new e.Index({invertedIndex:this.invertedIndex,fieldVectors:this.fieldVectors,tokenSet:this.tokenSet,fields:this._fields,pipeline:this.searchPipeline})},e.Builder.prototype.use=function(e){var t=Array.prototype.slice.call(arguments,1);t.unshift(this),e.apply(this,t)},e.MatchData=function(e,t,r){for(var i=Object.create(null),n=Object.keys(r),s=0;s=this.length)return e.QueryLexer.EOS;var t=this.str.charAt(this.pos);return this.pos+=1,t},e.QueryLexer.prototype.width=function(){return this.pos-this.start},e.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},e.QueryLexer.prototype.backup=function(){this.pos-=1},e.QueryLexer.prototype.acceptDigitRun=function(){var t,r;do t=this.next(),r=t.charCodeAt(0);while(r>47&&58>r);t!=e.QueryLexer.EOS&&this.backup()},e.QueryLexer.prototype.more=function(){return this.pos1&&(t.backup(),t.emit(e.QueryLexer.TERM)),t.ignore(),t.more()?e.QueryLexer.lexText:void 0},e.QueryLexer.lexEditDistance=function(t){return t.ignore(),t.acceptDigitRun(),t.emit(e.QueryLexer.EDIT_DISTANCE),e.QueryLexer.lexText},e.QueryLexer.lexBoost=function(t){return t.ignore(),t.acceptDigitRun(),t.emit(e.QueryLexer.BOOST),e.QueryLexer.lexText},e.QueryLexer.lexEOS=function(t){t.width()>0&&t.emit(e.QueryLexer.TERM)},e.QueryLexer.termSeparator=e.tokenizer.separator,e.QueryLexer.lexText=function(t){for(;;){var r=t.next();if(r==e.QueryLexer.EOS)return e.QueryLexer.lexEOS;if(92!=r.charCodeAt(0)){if(":"==r)return e.QueryLexer.lexField;if("~"==r)return t.backup(),t.width()>0&&t.emit(e.QueryLexer.TERM),e.QueryLexer.lexEditDistance;if("^"==r)return t.backup(),t.width()>0&&t.emit(e.QueryLexer.TERM),e.QueryLexer.lexBoost;if(r.match(e.QueryLexer.termSeparator))return e.QueryLexer.lexTerm}else t.escapeCharacter()}},e.QueryParser=function(t,r){this.lexer=new e.QueryLexer(t),this.query=r,this.currentClause={},this.lexemeIdx=0},e.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var t=e.QueryParser.parseFieldOrTerm;t;)t=t(this);return this.query},e.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},e.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},e.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},e.QueryParser.parseFieldOrTerm=function(t){var r=t.peekLexeme();if(void 0!=r)switch(r.type){case e.QueryLexer.FIELD:return e.QueryParser.parseField;case e.QueryLexer.TERM:return e.QueryParser.parseTerm;default:var i="expected either a field or a term, found "+r.type;throw r.str.length>=1&&(i+=" with value '"+r.str+"'"),new e.QueryParseError(i,r.start,r.end)}},e.QueryParser.parseField=function(t){var r=t.consumeLexeme();if(void 0!=r){if(-1==t.query.allFields.indexOf(r.str)){var i=t.query.allFields.map(function(e){return"'"+e+"'"}).join(", "),n="unrecognised field '"+r.str+"', possible fields: "+i;throw new e.QueryParseError(n,r.start,r.end)}t.currentClause.fields=[r.str];var s=t.peekLexeme();if(void 0==s){var n="expecting term, found nothing";throw new e.QueryParseError(n,r.start,r.end)}switch(s.type){case e.QueryLexer.TERM:return e.QueryParser.parseTerm;default:var n="expecting term, found '"+s.type+"'";throw new e.QueryParseError(n,s.start,s.end)}}},e.QueryParser.parseTerm=function(t){var r=t.consumeLexeme();if(void 0!=r){t.currentClause.term=r.str.toLowerCase(),-1!=r.str.indexOf("*")&&(t.currentClause.usePipeline=!1);var i=t.peekLexeme();if(void 0==i)return void t.nextClause();switch(i.type){case e.QueryLexer.TERM:return t.nextClause(),e.QueryParser.parseTerm;case e.QueryLexer.FIELD:return t.nextClause(),e.QueryParser.parseField;case e.QueryLexer.EDIT_DISTANCE:return e.QueryParser.parseEditDistance;case e.QueryLexer.BOOST:return e.QueryParser.parseBoost;default:var n="Unexpected lexeme type '"+i.type+"'";throw new e.QueryParseError(n,i.start,i.end)}}},e.QueryParser.parseEditDistance=function(t){var r=t.consumeLexeme();if(void 0!=r){var i=parseInt(r.str,10);if(isNaN(i)){var n="edit distance must be numeric";throw new e.QueryParseError(n,r.start,r.end)}t.currentClause.editDistance=i;var s=t.peekLexeme();if(void 0==s)return void t.nextClause();switch(s.type){case e.QueryLexer.TERM:return t.nextClause(),e.QueryParser.parseTerm;case e.QueryLexer.FIELD:return t.nextClause(),e.QueryParser.parseField;case e.QueryLexer.EDIT_DISTANCE:return e.QueryParser.parseEditDistance;case e.QueryLexer.BOOST:return e.QueryParser.parseBoost;default:var n="Unexpected lexeme type '"+s.type+"'";throw new e.QueryParseError(n,s.start,s.end)}}},e.QueryParser.parseBoost=function(t){var r=t.consumeLexeme();if(void 0!=r){var i=parseInt(r.str,10);if(isNaN(i)){var n="boost must be numeric";throw new e.QueryParseError(n,r.start,r.end)}t.currentClause.boost=i;var s=t.peekLexeme();if(void 0==s)return void t.nextClause();switch(s.type){case e.QueryLexer.TERM:return t.nextClause(),e.QueryParser.parseTerm;case e.QueryLexer.FIELD:return t.nextClause(),e.QueryParser.parseField;case e.QueryLexer.EDIT_DISTANCE:return e.QueryParser.parseEditDistance;case e.QueryLexer.BOOST:return e.QueryParser.parseBoost;default:var n="Unexpected lexeme type '"+s.type+"'";throw new e.QueryParseError(n,s.start,s.end)}}},function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():e.lunr=t()}(this,function(){return e})}(); \ No newline at end of file +!function(){var e,t,r,i,n,s,o,a,u,l,c,d,h,f,p,y,m,g,x,v,w,Q,k,S,E,L,b,P,T=function(e){var t=new T.Builder;return t.pipeline.add(T.trimmer,T.stopWordFilter,T.stemmer),t.searchPipeline.add(T.stemmer),e.call(t,t),t.build()};T.version="2.3.3",T.utils={},T.utils.warn=(e=this,function(t){e.console&&console.warn&&console.warn(t)}),T.utils.asString=function(e){return void 0===e||null===e?"":e.toString()},T.utils.clone=function(e){if(null===e||void 0===e)return e;for(var t=Object.create(null),r=Object.keys(e),i=0;i0){var u=T.utils.clone(t)||{};u.position=[o,a],u.index=n.length,n.push(new T.Token(r.slice(o,s),u))}o=s+1}}return n},T.tokenizer.separator=/[\s\-]+/,T.Pipeline=function(){this._stack=[]},T.Pipeline.registeredFunctions=Object.create(null),T.Pipeline.registerFunction=function(e,t){t in this.registeredFunctions&&T.utils.warn("Overwriting existing registered function: "+t),e.label=t,T.Pipeline.registeredFunctions[e.label]=e},T.Pipeline.warnIfFunctionNotRegistered=function(e){e.label&&e.label in this.registeredFunctions||T.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},T.Pipeline.load=function(e){var t=new T.Pipeline;return e.forEach(function(e){var r=T.Pipeline.registeredFunctions[e];if(!r)throw new Error("Cannot load unregistered function: "+e);t.add(r)}),t},T.Pipeline.prototype.add=function(){Array.prototype.slice.call(arguments).forEach(function(e){T.Pipeline.warnIfFunctionNotRegistered(e),this._stack.push(e)},this)},T.Pipeline.prototype.after=function(e,t){T.Pipeline.warnIfFunctionNotRegistered(t);var r=this._stack.indexOf(e);if(-1==r)throw new Error("Cannot find existingFn");r+=1,this._stack.splice(r,0,t)},T.Pipeline.prototype.before=function(e,t){T.Pipeline.warnIfFunctionNotRegistered(t);var r=this._stack.indexOf(e);if(-1==r)throw new Error("Cannot find existingFn");this._stack.splice(r,0,t)},T.Pipeline.prototype.remove=function(e){var t=this._stack.indexOf(e);-1!=t&&this._stack.splice(t,1)},T.Pipeline.prototype.run=function(e){for(var t=this._stack.length,r=0;r1&&(se&&(r=n),s!=e);)i=r-t,n=t+Math.floor(i/2),s=this.elements[2*n];return s==e?2*n:s>e?2*n:sa?l+=2:o==a&&(t+=r[u+1]*i[l+1],u+=2,l+=2);return t},T.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},T.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),t=1,r=0;t0)(s=a.str.charAt(0))in a.node.edges?n=a.node.edges[s]:(n=new T.TokenSet,a.node.edges[s]=n),1==a.str.length?n.final=!0:i.push({node:n,editsRemaining:a.editsRemaining,str:a.str.slice(1)});if(a.editsRemaining>0&&a.str.length>1)(s=a.str.charAt(1))in a.node.edges?o=a.node.edges[s]:(o=new T.TokenSet,a.node.edges[s]=o),a.str.length<=2?o.final=!0:i.push({node:o,editsRemaining:a.editsRemaining-1,str:a.str.slice(2)});if(a.editsRemaining>0&&1==a.str.length&&(a.node.final=!0),a.editsRemaining>0&&a.str.length>=1){if("*"in a.node.edges)var u=a.node.edges["*"];else{u=new T.TokenSet;a.node.edges["*"]=u}1==a.str.length?u.final=!0:i.push({node:u,editsRemaining:a.editsRemaining-1,str:a.str.slice(1)})}if(a.editsRemaining>0){if("*"in a.node.edges)var l=a.node.edges["*"];else{l=new T.TokenSet;a.node.edges["*"]=l}0==a.str.length?l.final=!0:i.push({node:l,editsRemaining:a.editsRemaining-1,str:a.str})}if(a.editsRemaining>0&&a.str.length>1){var c,d=a.str.charAt(0),h=a.str.charAt(1);h in a.node.edges?c=a.node.edges[h]:(c=new T.TokenSet,a.node.edges[h]=c),1==a.str.length?c.final=!0:i.push({node:c,editsRemaining:a.editsRemaining-1,str:d+a.str.slice(2)})}}return r},T.TokenSet.fromString=function(e){for(var t=new T.TokenSet,r=t,i=0,n=e.length;i=e;t--){var r=this.uncheckedNodes[t],i=r.child.toString();i in this.minimizedNodes?r.parent.edges[r.char]=this.minimizedNodes[i]:(r.child._str=i,this.minimizedNodes[i]=r.child),this.uncheckedNodes.pop()}},T.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},T.Index.prototype.search=function(e){return this.query(function(t){new T.QueryParser(e,t).parse()})},T.Index.prototype.query=function(e){for(var t=new T.Query(this.fields),r=Object.create(null),i=Object.create(null),n=Object.create(null),s=Object.create(null),o=Object.create(null),a=0;a1?1:e},T.Builder.prototype.k1=function(e){this._k1=e},T.Builder.prototype.add=function(e,t){var r=e[this._ref],i=Object.keys(this._fields);this._documents[r]=t||{},this.documentCount+=1;for(var n=0;n=this.length)return T.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},T.QueryLexer.prototype.width=function(){return this.pos-this.start},T.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},T.QueryLexer.prototype.backup=function(){this.pos-=1},T.QueryLexer.prototype.acceptDigitRun=function(){var e,t;do{t=(e=this.next()).charCodeAt(0)}while(t>47&&t<58);e!=T.QueryLexer.EOS&&this.backup()},T.QueryLexer.prototype.more=function(){return this.pos1&&(e.backup(),e.emit(T.QueryLexer.TERM)),e.ignore(),e.more())return T.QueryLexer.lexText},T.QueryLexer.lexEditDistance=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(T.QueryLexer.EDIT_DISTANCE),T.QueryLexer.lexText},T.QueryLexer.lexBoost=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(T.QueryLexer.BOOST),T.QueryLexer.lexText},T.QueryLexer.lexEOS=function(e){e.width()>0&&e.emit(T.QueryLexer.TERM)},T.QueryLexer.termSeparator=T.tokenizer.separator,T.QueryLexer.lexText=function(e){for(;;){var t=e.next();if(t==T.QueryLexer.EOS)return T.QueryLexer.lexEOS;if(92!=t.charCodeAt(0)){if(":"==t)return T.QueryLexer.lexField;if("~"==t)return e.backup(),e.width()>0&&e.emit(T.QueryLexer.TERM),T.QueryLexer.lexEditDistance;if("^"==t)return e.backup(),e.width()>0&&e.emit(T.QueryLexer.TERM),T.QueryLexer.lexBoost;if("+"==t&&1===e.width())return e.emit(T.QueryLexer.PRESENCE),T.QueryLexer.lexText;if("-"==t&&1===e.width())return e.emit(T.QueryLexer.PRESENCE),T.QueryLexer.lexText;if(t.match(T.QueryLexer.termSeparator))return T.QueryLexer.lexTerm}else e.escapeCharacter()}},T.QueryParser=function(e,t){this.lexer=new T.QueryLexer(e),this.query=t,this.currentClause={},this.lexemeIdx=0},T.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var e=T.QueryParser.parseClause;e;)e=e(this);return this.query},T.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},T.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},T.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},T.QueryParser.parseClause=function(e){var t=e.peekLexeme();if(void 0!=t)switch(t.type){case T.QueryLexer.PRESENCE:return T.QueryParser.parsePresence;case T.QueryLexer.FIELD:return T.QueryParser.parseField;case T.QueryLexer.TERM:return T.QueryParser.parseTerm;default:var r="expected either a field or a term, found "+t.type;throw t.str.length>=1&&(r+=" with value '"+t.str+"'"),new T.QueryParseError(r,t.start,t.end)}},T.QueryParser.parsePresence=function(e){var t=e.consumeLexeme();if(void 0!=t){switch(t.str){case"-":e.currentClause.presence=T.Query.presence.PROHIBITED;break;case"+":e.currentClause.presence=T.Query.presence.REQUIRED;break;default:var r="unrecognised presence operator'"+t.str+"'";throw new T.QueryParseError(r,t.start,t.end)}var i=e.peekLexeme();if(void 0==i){r="expecting term or field, found nothing";throw new T.QueryParseError(r,t.start,t.end)}switch(i.type){case T.QueryLexer.FIELD:return T.QueryParser.parseField;case T.QueryLexer.TERM:return T.QueryParser.parseTerm;default:r="expecting term or field, found '"+i.type+"'";throw new T.QueryParseError(r,i.start,i.end)}}},T.QueryParser.parseField=function(e){var t=e.consumeLexeme();if(void 0!=t){if(-1==e.query.allFields.indexOf(t.str)){var r=e.query.allFields.map(function(e){return"'"+e+"'"}).join(", "),i="unrecognised field '"+t.str+"', possible fields: "+r;throw new T.QueryParseError(i,t.start,t.end)}e.currentClause.fields=[t.str];var n=e.peekLexeme();if(void 0==n){i="expecting term, found nothing";throw new T.QueryParseError(i,t.start,t.end)}switch(n.type){case T.QueryLexer.TERM:return T.QueryParser.parseTerm;default:i="expecting term, found '"+n.type+"'";throw new T.QueryParseError(i,n.start,n.end)}}},T.QueryParser.parseTerm=function(e){var t=e.consumeLexeme();if(void 0!=t){e.currentClause.term=t.str.toLowerCase(),-1!=t.str.indexOf("*")&&(e.currentClause.usePipeline=!1);var r=e.peekLexeme();if(void 0!=r)switch(r.type){case T.QueryLexer.TERM:return e.nextClause(),T.QueryParser.parseTerm;case T.QueryLexer.FIELD:return e.nextClause(),T.QueryParser.parseField;case T.QueryLexer.EDIT_DISTANCE:return T.QueryParser.parseEditDistance;case T.QueryLexer.BOOST:return T.QueryParser.parseBoost;case T.QueryLexer.PRESENCE:return e.nextClause(),T.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+r.type+"'";throw new T.QueryParseError(i,r.start,r.end)}else e.nextClause()}},T.QueryParser.parseEditDistance=function(e){var t=e.consumeLexeme();if(void 0!=t){var r=parseInt(t.str,10);if(isNaN(r)){var i="edit distance must be numeric";throw new T.QueryParseError(i,t.start,t.end)}e.currentClause.editDistance=r;var n=e.peekLexeme();if(void 0!=n)switch(n.type){case T.QueryLexer.TERM:return e.nextClause(),T.QueryParser.parseTerm;case T.QueryLexer.FIELD:return e.nextClause(),T.QueryParser.parseField;case T.QueryLexer.EDIT_DISTANCE:return T.QueryParser.parseEditDistance;case T.QueryLexer.BOOST:return T.QueryParser.parseBoost;case T.QueryLexer.PRESENCE:return e.nextClause(),T.QueryParser.parsePresence;default:i="Unexpected lexeme type '"+n.type+"'";throw new T.QueryParseError(i,n.start,n.end)}else e.nextClause()}},T.QueryParser.parseBoost=function(e){var t=e.consumeLexeme();if(void 0!=t){var r=parseInt(t.str,10);if(isNaN(r)){var i="boost must be numeric";throw new T.QueryParseError(i,t.start,t.end)}e.currentClause.boost=r;var n=e.peekLexeme();if(void 0!=n)switch(n.type){case T.QueryLexer.TERM:return e.nextClause(),T.QueryParser.parseTerm;case T.QueryLexer.FIELD:return e.nextClause(),T.QueryParser.parseField;case T.QueryLexer.EDIT_DISTANCE:return T.QueryParser.parseEditDistance;case T.QueryLexer.BOOST:return T.QueryParser.parseBoost;case T.QueryLexer.PRESENCE:return e.nextClause(),T.QueryParser.parsePresence;default:i="Unexpected lexeme type '"+n.type+"'";throw new T.QueryParseError(i,n.start,n.end)}else e.nextClause()}},b=this,P=function(){return T},"function"==typeof define&&define.amd?define(P):"object"==typeof exports?module.exports=P():b.lunr=P()}(); \ No newline at end of file