@sajari/sdk-react
Version:
React SDK for the Sajari API
1,359 lines (1,330 loc) • 140 kB
JavaScript
import Cookies from 'js-cookie';
import { DefaultSession, InteractiveSession, TrackingType, Client, withEndpoint, TransportError } from '@sajari/sdk-js';
import React__default, { createElement, Component, Fragment, PureComponent, createRef, useRef, useState, useEffect, createContext } from 'react';
import { ThemeProvider, withTheme } from 'emotion-theming';
import { LiveAnnouncer, LiveMessage } from 'react-aria-live';
import memoize from 'fast-memoize';
import Downshift from 'downshift';
import i18next from 'i18next';
import LngDetector from 'i18next-browser-languagedetector';
import { jsx, css } from '@emotion/core';
import classnames from 'classnames';
import styled from '@emotion/styled';
import chroma from 'chroma-js';
import ReactSelect from 'react-select';
import { Range, Handle } from 'rc-slider';
var Listener = /** @class */ (function () {
/**
* Constructs a listener object.
*/
function Listener() {
this.listeners = [];
}
/**
* Adds a callback to the listener.
* Returns a function that will unregister the callback from the listener when called.
* @param callback The callback to be registered.
* @return The unregister function to remove the callback from the listener.
*/
Listener.prototype.listen = function (callback) {
var _this = this;
this.listeners.push(callback);
return function () { return _this.unlisten(callback); };
};
/**
* Removes a callback from the listener.
*/
Listener.prototype.unlisten = function (callback) {
var index = this.listeners.indexOf(callback);
if (index >= 0) {
this.listeners.splice(index, 1);
}
};
/**
* Notify takes a function and calls it for every listener.
* The listener is supplied as the first argument to the function.
* @param {function(callback: Function)} fn Function to call each of the callbacks in the listener with.
*/
Listener.prototype.notify = function (fn) {
this.listeners.forEach(function (l) {
try {
fn(l);
}
catch (e) {
// tslint:disable-next-line no-console
if (console && console.error) {
// tslint:disable-next-line no-console
console.error(e);
}
}
});
};
return Listener;
}());
var EVENT_SEARCH_SENT = "search-sent";
var EVENT_RESPONSE_UPDATED = "response-updated";
var EVENT_RESULT_CLICKED = "result-clicked";
var EVENT_VALUES_UPDATED = "values-changed";
var EVENT_TRACKING_RESET = "tracking-reset";
var EVENT_ANALYTICS_PAGE_CLOSED = "page-close-analytics";
var EVENT_ANALYTICS_BODY_RESET = "body-reset-analytics";
var EVENT_ANALYTICS_RESULT_CLICKED = "result-clicked-analytics";
var EVENT_SELECTION_UPDATED = "selection-updated";
var EVENT_OPTIONS_UPDATED = "options-updated";
var events = [
EVENT_ANALYTICS_PAGE_CLOSED,
EVENT_ANALYTICS_BODY_RESET,
EVENT_ANALYTICS_RESULT_CLICKED
];
/**
* Analytics is an adaptor which listens for events on Pipeline and
* Tracking and re-emits them as analytics-based events.
*/
var Analytics = /** @class */ (function () {
/**
* Constructs an analytics object that operates on the specified pipeline.
*/
function Analytics(pipeline, tracking) {
var _a;
var _this = this;
/**
* Runs before the page is closed/navigated away from. Can trigger a ga onPageClose call.
*/
this.beforeunload = function () {
if (_this.enabled && _this.body) {
_this.listeners.get(EVENT_ANALYTICS_PAGE_CLOSED).notify(function (callback) {
callback(_this.body);
});
_this.enabled = false; // TODO(tbillington): unload -> disable!!
}
};
/**
* Resets the currently held parameters. Can trigger a ga onBodyReset call.
*/
this.resetBody = function () {
if (_this.enabled) {
_this.listeners.get(EVENT_ANALYTICS_BODY_RESET).notify(function (callback) {
callback(_this.body);
});
_this.longestNonAutocompletedBody = "";
_this.longestAutocompletedBody = "";
_this.enabled = false;
}
};
/**
* Runs when the response has been updated. Updates the currently held search parameters.
*/
this.responseUpdated = function (response) {
if (response.isEmpty() || response.isError()) {
return;
}
_this.enabled = true;
var originalBody = response.getQueryValues().get(_this.bodyLabel) ||
"";
var responseBody = response.getValues().get(_this.bodyAutocompletedLabel) || originalBody;
_this.body = responseBody;
// Here we check the lengths of the non-autocompleted bodies.
// We do this because while the user is backspacing their query
// the new autocompleted body may be longer than their actual input,
// but we want to record their input rather than a completion resulting
// from them removing chars.
if (originalBody.length >= _this.longestNonAutocompletedBody.length) {
_this.longestNonAutocompletedBody = originalBody;
_this.longestAutocompletedBody = responseBody;
}
};
/**
* Runs when a result has been clicked. Can trigger a ga onResultClicked call.
*/
this.resultClicked = function () {
if (_this.enabled && _this.body) {
_this.listeners.get(EVENT_ANALYTICS_RESULT_CLICKED).notify(function (callback) {
callback(_this.body);
});
_this.longestNonAutocompletedBody = "";
_this.longestAutocompletedBody = "";
_this.enabled = false;
}
};
this.enabled = false;
this.body = "";
this.pipeline = pipeline;
this.tracking = tracking;
this.listeners = new Map(Object.entries((_a = {},
_a[EVENT_ANALYTICS_PAGE_CLOSED] = new Listener(),
_a[EVENT_ANALYTICS_BODY_RESET] = new Listener(),
_a[EVENT_ANALYTICS_RESULT_CLICKED] = new Listener(),
_a)));
// longest values are for sending the users last intended query on reset
this.longestNonAutocompletedBody = "";
this.longestAutocompletedBody = "";
// default to working with website pipeline values
this.bodyLabel = "q";
this.bodyAutocompletedLabel = "q";
window.addEventListener("beforeunload", this.beforeunload);
this.pipeline.listen(EVENT_RESPONSE_UPDATED, this.responseUpdated);
this.pipeline.listen(EVENT_RESULT_CLICKED, this.resultClicked);
this.tracking.listen(EVENT_TRACKING_RESET, this.resetBody);
}
/**
* Register a listener for a specific event.
* @param event Event to listen for
* @param callback Callback to run when the event happens.
* @return The unregister function to remove the callback from the listener.
*/
Analytics.prototype.listen = function (event, callback) {
if (events.indexOf(event) === -1) {
throw new Error("unknown event type \"" + event + "\"");
}
return this.listeners.get(event).listen(callback);
};
return Analytics;
}());
var GoogleAnalyticsObjects;
(function (GoogleAnalyticsObjects) {
GoogleAnalyticsObjects["UniversalAnalytics"] = "_ua";
GoogleAnalyticsObjects["AnalyticsJS"] = "ga";
GoogleAnalyticsObjects["GTag"] = "gtag";
})(GoogleAnalyticsObjects || (GoogleAnalyticsObjects = {}));
var GoogleAnalytics = /** @class */ (function () {
/**
* Constructs a GoogleAnalytics object.
* @param {Analytics} analytics The analytics object to attach to.
* @param {string} [id=undefined] The name of the ga global object. Defaults to "ga" or "_ua" if one isn't supplied.
* @param {string} [param="q"] The URL parameter to use to indicate a search. Default to "q".
*/
function GoogleAnalytics(analytics, id, param) {
var _this = this;
if (param === void 0) { param = "q"; }
this.unregisterFunctions = [];
/**
* Stops this object listening for events.
*/
this.detatch = function () { return _this.unregisterFunctions.forEach(function (fn) { return fn(); }); };
/**
* Callback for when the body has been reset. Calls sendGAPageView.
*/
this.onBodyReset = function (body) { return _this.sendGAPageView(body); };
/**
* Callback for when a result has been clicked. Calls sendGAPageView.
*/
this.onResultClicked = function (body) { return _this.sendGAPageView(body); };
/**
* Callback for when the page has been closed. Calls sendGAPageView.
*/
this.onPageClose = function (body) { return _this.sendGAPageView(body); };
this.unregisterFunctions.push(analytics.listen(EVENT_ANALYTICS_PAGE_CLOSED, this.onPageClose));
this.unregisterFunctions.push(analytics.listen(EVENT_ANALYTICS_BODY_RESET, this.onBodyReset));
this.unregisterFunctions.push(analytics.listen(EVENT_ANALYTICS_RESULT_CLICKED, this.onResultClicked));
if (id !== undefined) {
this.id = id;
}
else if (isFunction(window[GoogleAnalyticsObjects.AnalyticsJS])) {
this.id = GoogleAnalyticsObjects.AnalyticsJS;
}
else if (isFunction(window[GoogleAnalyticsObjects.UniversalAnalytics])) {
this.id = GoogleAnalyticsObjects.UniversalAnalytics;
}
else if (isFunction(window[GoogleAnalyticsObjects.GTag])) {
this.id = GoogleAnalyticsObjects.GTag;
}
else {
this.id = null;
}
this.param = param;
}
/**
* Sends a page view event if ga is found on the page and we're not in dev mode.
*/
GoogleAnalytics.prototype.sendGAPageView = function (body) {
var _a;
// @ts-ignore: window is an object
if (this.id && isFunction(window[this.id])) {
// Merge the body in with the existing query params in the url
var pageAddress = url.augmentUri(
// Take only the portion of the url following the domain
location.href.substring(location.origin.length), (_a = {}, _a[this.param] = body, _a));
if (this.id === GoogleAnalyticsObjects.GTag) {
window[this.id]("event", "page_view", {
page_location: pageAddress
});
}
else {
window[this.id]("send", "pageview", pageAddress);
}
}
};
return GoogleAnalytics;
}());
var url = {
/**
* Convert a query string in to an object
*/
decodeUriArgs: function (queryStr) {
var args = {};
var a = queryStr.split("&");
for (var i in a) {
if (a.hasOwnProperty(i)) {
var b = a[i].split("=");
args[decodeURIComponent(b[0])] = decodeURIComponent(b[1]);
}
}
return args;
},
/**
* Convert an arguments object in to a query string
*/
encodeUriArgs: function (args) {
var queryParts = [];
Object.keys(args).forEach(function (key) {
return queryParts.push(encodeURIComponent(key) + "=" + encodeURIComponent(args[key]));
});
return queryParts.join("&");
},
/**
* Merges query strings or objects into a single query string. Accepts a variable number of query string/objects
* to merge. The latter overrides the former.
*/
mergeQueryStr: function (first) {
var _this = this;
var rest = [];
for (var _i = 1; _i < arguments.length; _i++) {
rest[_i - 1] = arguments[_i];
}
var args = typeof first === "string" ? this.decodeUriArgs(first) : first;
rest.forEach(function (arg) {
var next = typeof arg === "string" ? _this.decodeUriArgs(arg) : arg;
Object.keys(next).forEach(function (prop) { return (args[prop] = next[prop]); });
});
return this.encodeUriArgs(args);
},
/**
* Takes an existing URL and merges additional data into the query string
*/
augmentUri: function (uri, args) {
var m = /^([^?]+)\?(.+)+$/.exec(uri);
if (m) {
return m[1] + "?" + this.mergeQueryStr(m[2], args);
}
else {
return uri + "?" + this.encodeUriArgs(args);
}
},
/**
* Get a parameter from the URL
*/
getURLParameter: function (name) {
var value = new RegExp("[?|&]" + name + "=" + "([^&;]+?)(&|#|;|$)").exec(location.search) || [undefined, ""];
return (decodeURIComponent(value[1].replace(/\+/g, "%20")) || null);
}
};
var isFunction = function (x) { return typeof x === "function"; };
var DebugAnalytics = /** @class */ (function () {
/**
*
* @param {Analytics} analytics Analytics adaptor to listen for events on.
*/
function DebugAnalytics(analytics) {
this.pageClosed = function (bodyToSend) {
// tslint:disable-next-line no-console
console.log("DebugAnalytics: pageClosed, body:", bodyToSend);
};
this.bodyReset = function (bodyToSend) {
// tslint:disable-next-line no-console
console.log("DebugAnalytics: bodyReset, body:", bodyToSend);
};
this.resultClicked = function (bodyToSend) {
// tslint:disable-next-line no-console
console.log("DebugAnalytics: resultClicked, body:", bodyToSend);
};
analytics.listen(EVENT_ANALYTICS_PAGE_CLOSED, this.pageClosed);
analytics.listen(EVENT_ANALYTICS_BODY_RESET, this.bodyReset);
analytics.listen(EVENT_ANALYTICS_RESULT_CLICKED, this.resultClicked);
}
return DebugAnalytics;
}());
var Response = /** @class */ (function () {
/**
* Constructs a Response object.
* @param error
* @param queryValues
* @param response
* @param values
*/
function Response(error, queryValues, response, values) {
this.error = error;
this.queryValues = queryValues;
this.response = response;
this.values = values;
}
/**
* Is this response empty?
*/
Response.prototype.isEmpty = function () {
return (this.error === null &&
this.response === undefined &&
this.values === undefined &&
this.queryValues === undefined);
};
/**
* Is this response an error?
*/
Response.prototype.isError = function () {
return this.error !== null;
};
/**
* The error associated with this response.
*/
Response.prototype.getError = function () {
return this.error;
};
/**
* Return the query values used in the search which created this response.
*/
Response.prototype.getQueryValues = function () {
return this.queryValues;
};
/**
* Returns the response, which includes results and aggregates etc.
*/
Response.prototype.getResponse = function () {
return this.response;
};
/**
* Return the pipeline values returned by the search.
*/
Response.prototype.getValues = function () {
return this.values;
};
/**
* Return results from the response.
*/
Response.prototype.getResults = function () {
return this.response !== undefined
? this.response.get("results")
: undefined;
};
/**
* Return the total number of results.
*/
Response.prototype.getTotalResults = function () {
return this.response !== undefined
? this.response.get("totalResults")
: undefined;
};
/**
* Return time from the response.
*/
Response.prototype.getTime = function () {
return this.response !== undefined
? this.response.get("time")
: undefined;
};
/**
* Return the aggregates in the response.
*/
Response.prototype.getAggregates = function () {
if (this.response === undefined) {
return undefined;
}
var aggregates = this.response.get("aggregates");
if (aggregates === undefined) {
return undefined;
}
return aggregates;
};
/**
* Return the aggregateFilters in the response.
*/
Response.prototype.getAggregateFilters = function () {
if (this.response === undefined) {
return undefined;
}
var aggregates = this.response.get("aggregateFilters");
if (aggregates === undefined) {
return undefined;
}
return aggregates;
};
return Response;
}());
var events$1 = [EVENT_TRACKING_RESET];
var Tracking = /** @class */ (function () {
function Tracking() {
var _a;
this.clientTracking = null;
this.listeners = new Map(Object.entries((_a = {},
_a[EVENT_TRACKING_RESET] = new Listener(),
_a)));
}
/**
* Register a listener for a specific event.
* @param event Event to listen for
* @param callback Callback to run when the event happens.
* @return The unregister function to remove the callback from the listener.
*/
Tracking.prototype.listen = function (event, callback) {
if (events$1.indexOf(event) === -1) {
throw new Error("unknown event type \"" + event + "\"");
}
return this.listeners.get(event).listen(callback);
};
/**
* Emits a tracking reset event to the tracking reset event listener.
* @private
*/
Tracking.prototype._emitTrackingReset = function (values) {
this.listeners.get(EVENT_TRACKING_RESET).notify(function (listener) {
listener(values);
});
};
/**
* Reset the tracking.
* @param values Key-value pair parameters to use in the pipeline.
*/
Tracking.prototype.reset = function (values) {
throw new Error("method 'reset' unimplemented");
};
/**
* Tracking returns the tracking data to be attached to the pipeline request.
* @param values Key-value pair parameters to use in the pipeline.
* @return Tracking values to be used in the search request.
*/
Tracking.prototype.next = function (values) {
throw new Error("method 'next' unimplemented");
};
return Tracking;
}());
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
/* global Reflect, Promise */
var extendStatics = function(d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
function __extends(d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
}
var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
function __rest(s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
}
function __makeTemplateObject(cooked, raw) {
if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
return cooked;
}
// @ts-ignore: module missing defintion file
var getTrackingData = function () {
var data = {};
var ga = Cookies.get("_ga");
if (ga) {
data.ga = ga;
}
var sjID = Cookies.get("sjID");
if (sjID) {
data.sjID = sjID;
}
return data;
};
var ClickTracking = /** @class */ (function (_super) {
__extends(ClickTracking, _super);
/**
* Construct a ClickTracking instance.
*
* @param field Field to use for click token generation.
* @param qParam Value to use for full-text query param.
*/
function ClickTracking(field, qParam) {
if (field === void 0) { field = "url"; }
if (qParam === void 0) { qParam = "q"; }
var _this = _super.call(this) || this;
_this.field = field;
_this.qParam = qParam;
_this.clientTracking = new InteractiveSession(qParam, new DefaultSession(TrackingType.Click, field, getTrackingData()));
_this.prevQ = "";
return _this;
}
/**
* Reset the tracking.
* @param values Key-value pair parameters to use in the pipeline.
*/
ClickTracking.prototype.reset = function (values) {
this.clientTracking.reset();
if (values !== undefined) {
this._emitTrackingReset(values);
}
};
/**
* Construct a tracking session to be used in a search.
*
* @param values Key-value pair parameters to use in the pipeline.
*/
ClickTracking.prototype.next = function (values) {
if (this.clientTracking === null) {
throw new Error("clientTracking is null");
}
return this.clientTracking.next(values);
};
return ClickTracking;
}(Tracking));
var NoTracking = /** @class */ (function (_super) {
__extends(NoTracking, _super);
/**
* Construct a NoTracking instance.
*/
function NoTracking() {
var _this = _super.call(this) || this;
_this.clientTracking = new DefaultSession(TrackingType.None, "_id", getTrackingData());
return _this;
}
/**
* Reset the tracking.
* @param values Key-value pair parameters to use in the pipeline.
*/
NoTracking.prototype.reset = function (values) {
this.clientTracking.reset();
if (values !== undefined) {
this._emitTrackingReset(values);
}
};
/**
* Construct a tracking session to be used in a search.
*/
NoTracking.prototype.next = function (values) {
if (this.clientTracking === null) {
throw new Error("clientTracking is null");
}
return this.clientTracking.next(values);
};
return NoTracking;
}(Tracking));
var PosNegTracking = /** @class */ (function (_super) {
__extends(PosNegTracking, _super);
/**
* Construct a PosNegTracking instance.
*/
function PosNegTracking(field) {
var _this = _super.call(this) || this;
_this.clientTracking = new DefaultSession(TrackingType.PosNeg, field, getTrackingData());
return _this;
}
/**
* Reset the tracking.
* @param values Key-value pair parameters to use in the pipeline.
*/
PosNegTracking.prototype.reset = function (values) {
this.clientTracking.reset();
if (values !== undefined) {
this._emitTrackingReset(values);
}
};
/**
* Construct a tracking session to be used in a search.
* @param values Key-value pair parameters to use in the pipeline.
*/
PosNegTracking.prototype.next = function (values) {
if (this.clientTracking === null) {
throw new Error("clientTracking is null");
}
return this.clientTracking.next(values);
};
return PosNegTracking;
}(Tracking));
var events$2 = [
EVENT_SEARCH_SENT,
EVENT_RESPONSE_UPDATED,
EVENT_RESULT_CLICKED
];
var Pipeline = /** @class */ (function () {
/**
* Constructs a Pipeline object.
* @param config Project, Collection config
* @param name Name of the pipeline.
* @param [tracking=ClickTracking()] Default tracking to use in searches.
* @param [analyticsAdapters=GoogleAnalytics]
*/
function Pipeline(config, name, tracking, analyticsAdapters) {
var _this = this;
if (tracking === void 0) { tracking = new NoTracking(); }
if (analyticsAdapters === void 0) { analyticsAdapters = [GoogleAnalytics]; }
this.response = new Response(null);
var project = config.project, collection = config.collection, endpoint = config.endpoint;
this.config = config;
var opts = endpoint !== undefined ? [withEndpoint(endpoint)] : [];
var p = {
name: undefined,
version: undefined
};
if (typeof name === "string") {
p.name = name;
}
else if ("name" in name) {
p.name = name.name;
p.version = name.version;
}
this.pipeline = new Client(project, collection, opts).pipeline(p.name, p.version);
this.name = p.name;
this.version = p.version;
this.tracking = tracking;
this.listeners = new Map([
[EVENT_SEARCH_SENT, new Listener()],
[EVENT_RESPONSE_UPDATED, new Listener()],
[EVENT_RESULT_CLICKED, new Listener()]
]);
this.searchCount = 0;
this.response = new Response(null);
this.analytics = new Analytics(this, this.tracking);
analyticsAdapters.forEach(function (Adapter) {
// tslint:disable-next-line
new Adapter(_this.analytics);
});
}
/**
* Register a listener for a specific event.
* @param event Event to listen for
* @param callback Callback to run when the event happens.
* @return The unregister function to remove the callback from the listener.
*/
Pipeline.prototype.listen = function (event, callback) {
if (events$2.indexOf(event) === -1) {
throw new Error("unknown event type \"" + event + "\"");
}
return this.listeners.get(event).listen(callback);
};
/**
* Emits a search event to the search event listener.
* @private
*/
Pipeline.prototype._emitSearchSent = function (values) {
this.listeners.get(EVENT_SEARCH_SENT).notify(function (listener) {
listener(values);
});
};
/**
* Emits a results event to the results event listener.
* @private
*/
Pipeline.prototype._emitResponseUpdated = function (response) {
this.listeners.get(EVENT_RESPONSE_UPDATED).notify(function (listener) {
listener(response);
});
};
/**
* Emits a result clicked event to the results clicked event listeners.
* @param value Value to send to the listeners.
*/
Pipeline.prototype.emitResultClicked = function (value) {
this.listeners.get(EVENT_RESULT_CLICKED).notify(function (listener) {
listener(value);
});
};
/**
* Perform a search.
* @param values Key-value parameters to pass to the pipeline.
*/
Pipeline.prototype.search = function (values) {
var _this = this;
this.searchCount++;
var currentSearch = this.searchCount;
this.pipeline.search(values, this.tracking, function (error, response, responseValues) {
if (responseValues === void 0) { responseValues = {}; }
if (currentSearch < _this.searchCount) {
return;
}
_this.response = new Response(error ? error : null, new Map(Object.entries(values)), response !== undefined
? new Map(Object.entries(response))
: undefined, new Map(Object.entries(responseValues)));
// tslint:disable-next-line no-console
if (error && console && console.error) {
// tslint:disable-next-line no-console
console.error(error);
}
_this._emitResponseUpdated(_this.response);
});
this._emitSearchSent(values);
};
/**
* Clears the error, response, and response values from this object.
* @param values Key-value pair parameters.
*/
Pipeline.prototype.clearResponse = function (values) {
this.tracking.next(values);
this.searchCount++;
this.response = new Response(null);
this._emitResponseUpdated(this.response);
};
/**
* The current response.
*/
Pipeline.prototype.getResponse = function () {
return this.response;
};
/**
* The analytics adaptor connected to this pipeline.
*/
Pipeline.prototype.getAnalytics = function () {
return this.analytics;
};
return Pipeline;
}());
var Values = /** @class */ (function () {
/**
* Constructor for Values object.
* @param values Initial values.
*/
function Values(values) {
if (values === void 0) { values = {}; }
this.listeners = new Map([[EVENT_VALUES_UPDATED, new Listener()]]);
this.values = new Map(Object.entries(values));
}
/**
* Register a listener for a specific event.
* @param event Event to listen for
* @param callback Callback to run when the event happens.
*/
Values.prototype.listen = function (event, callback) {
if (event !== EVENT_VALUES_UPDATED) {
throw new Error("unknown event type \"" + event + "\"");
}
return this.listeners.get(event).listen(callback);
};
/**
* Merge values into the value map.
*
* Set a value to undefined to remove it.
*/
Values.prototype.set = function (values) {
this._set(values);
this._emitUpdated(values);
};
/**
* get returns the current values.
*/
Values.prototype.get = function () {
var values = {};
this.values.forEach(function (value, key) {
if (typeof value === "function") {
values[key] = value();
}
else if (Array.isArray(value)) {
values[key] = value.join(",");
}
else {
values[key] = String(value);
}
});
return values;
};
/**
* Emits an event to notify listener that the values have been updated.
*
* @private
*/
Values.prototype._emitUpdated = function (changes) {
var _this = this;
this.listeners.get(EVENT_VALUES_UPDATED).notify(function (listener) {
return listener(changes, function (values) { return _this._set(values); });
});
};
/**
* Sets values without triggering an event, internal use only.
*/
Values.prototype._set = function (values) {
var _this = this;
Object.keys(values).forEach(function (key) {
if (values[key] === undefined) {
_this.values.delete(key);
}
else {
_this.values.set(key, values[key]);
}
});
};
return Values;
}());
var events$3 = [EVENT_SELECTION_UPDATED, EVENT_OPTIONS_UPDATED];
/**
* Filter is a helper class for building filters from UI components.
*/
var Filter = /** @class */ (function () {
/**
* Constructs an instance of Filter.
*
* @example
* const filter = new Filter({});
*/
function Filter(options, // Dictionary of name -> filter pairs
initial, // List of initially selected items
multi, // Multiple selections allowed?
joinOperator // Join operator used if multi = true
) {
var _a;
if (initial === void 0) { initial = []; }
if (multi === void 0) { multi = false; }
if (joinOperator === void 0) { joinOperator = "OR"; }
if (typeof initial === "string") {
initial = [initial];
}
/** @private */
this.current = initial;
/** @private */
this.options = options;
/** @private */
this.multi = multi;
/** @private */
this.joinOperator = joinOperator;
/** @private */
this.listeners = (_a = {},
_a[EVENT_SELECTION_UPDATED] = new Listener(),
_a[EVENT_OPTIONS_UPDATED] = new Listener(),
_a);
}
/**
* Register a listener for a specific event.
*/
Filter.prototype.listen = function (event, callback) {
if (events$3.indexOf(event) === -1) {
throw new Error("unknown event type \"" + event + "\"");
}
return this.listeners[event].listen(callback);
};
/**
* Set the state of the filter.
*/
Filter.prototype.set = function (name, on) {
if (this.multi) {
this._setMulti(name, on);
return;
}
// if on add to current
if (on) {
this.current = [name];
}
else {
// clear current
this.current = [];
}
this._emitSelectionUpdated();
};
/**
* returns whether the filter is set or not.
*/
Filter.prototype.isSet = function (name) {
return this.current.indexOf(name) !== -1;
};
/**
* Merge options into the filter options.
*
* Set an option to null to remove it.
*/
Filter.prototype.setOptions = function (options) {
var _this = this;
Object.keys(options).forEach(function (k) {
if (options[k] === null) {
delete _this.options[k];
_this.current = _this.current.filter(function (n) { return n !== k; });
}
else {
_this.options[k] = options[k];
}
});
this._emitOptionsUpdated();
};
/**
* Get all the options defined in this filter.
*/
Filter.prototype.getOptions = function () {
var fields = [];
for (var _i = 0; _i < arguments.length; _i++) {
fields[_i] = arguments[_i];
}
return this.options;
};
/**
* Get the current selection in this filter.
*/
Filter.prototype.get = function () {
var fields = [];
for (var _i = 0; _i < arguments.length; _i++) {
fields[_i] = arguments[_i];
}
return this.current;
};
/**
* Builds up the filter string from the current filter and it's children.
*/
Filter.prototype.filter = function () {
var _this = this;
var filters = this.current
.map(function (c) {
var f = _this.options[c];
if (typeof f === "function") {
f = f();
}
if (f !== "") {
f = "(" + f + ")";
}
return f;
})
.filter(Boolean);
switch (filters.length) {
case 0:
return "";
case 1:
return filters[0];
default:
return filters.join(" " + this.joinOperator + " ");
}
};
/**
* Emits a selection updated event to the selection updated listener.
* @private
*/
Filter.prototype._emitSelectionUpdated = function () {
this.listeners[EVENT_SELECTION_UPDATED].notify(function (listener) {
listener();
});
};
/**
* Emits an options updated event to the options updated listener.
* @private
*/
Filter.prototype._emitOptionsUpdated = function () {
this.listeners[EVENT_OPTIONS_UPDATED].notify(function (listener) {
listener();
});
};
/** @private */
Filter.prototype._setMulti = function (name, on) {
if (on && this.current.indexOf(name) === -1) {
this.current = this.current.concat(name);
}
else {
this.current = this.current.filter(function (n) { return n !== name; });
}
this._emitSelectionUpdated();
};
return Filter;
}());
/**
* CombineFilters is a helper for combining multiple Filter instances
* into one.
*
* Whenever any of the combined filters are updated, the events are
* propagated up to the returned "root" filter.
*
* @param filters Array of filters to combine.
* @param [operator="AND"] Operator to apply between them ("AND" | "OR").
* @return The resulting Filter.
*/
var CombineFilters = function (filters, operator) {
if (operator === void 0) { operator = "AND"; }
var opts = {};
var count = 1;
var on = [];
filters.forEach(function (f) {
opts["" + count] = function () { return f.filter(); };
on = on.concat(["" + count]);
count++;
});
var combFilter = new Filter(opts, on, true, operator);
filters.forEach(function (f) {
f.listen(EVENT_SELECTION_UPDATED, function () {
// @ts-ignore
combFilter._emitSelectionUpdated();
});
f.listen(EVENT_OPTIONS_UPDATED, function () {
// @ts-ignore
combFilter._emitOptionsUpdated();
});
});
return combFilter;
};
var CountAggregateFilter = /** @class */ (function (_super) {
__extends(CountAggregateFilter, _super);
function CountAggregateFilter(field, pipeline, values, multi, type, listField) {
if (multi === void 0) { multi = false; }
if (type === void 0) { type = "~"; }
if (listField === void 0) { listField = false; }
var _this = _super.call(this, {}, [], multi) || this;
_this._field = "";
_this._counts = [];
_this._field = field;
_this._addCountToValues(values);
pipeline.listen(EVENT_RESPONSE_UPDATED, function (response) {
var current = _this.get();
var aggregates = response.getAggregates();
_this._counts = _this._getCounts(aggregates);
_this._clearFilterOptions(current);
_this.setOptions(_this._genFilterOptions(current, type, listField));
});
return _this;
}
CountAggregateFilter.prototype.getCounts = function () {
return this._counts;
};
CountAggregateFilter.prototype.reset = function () {
var _this = this;
this.get().forEach(function (item) {
_this.set(item, false);
});
this._counts = [];
};
CountAggregateFilter.prototype._addCountToValues = function (values) {
var count = values.get().count;
var fields = typeof count === "string" ? count.split(",") : [];
if (!fields.includes(this._field)) {
fields.push(this._field);
values.set({ count: fields.join(",") });
}
};
CountAggregateFilter.prototype._getCounts = function (aggregates) {
if (!aggregates || !aggregates["count." + this._field]) {
return [];
}
var counts = aggregates["count." + this._field];
return Object.keys(counts)
.map(function (key) {
return { name: key, count: counts[key] };
})
.filter(function (x) { return x.name !== ""; })
.sort(function (_a, _b) {
var a = _a.count;
var b = _b.count;
return b - a;
});
};
CountAggregateFilter.prototype._genFilterOptions = function (current, type, listField) {
var _this = this;
if (type === void 0) { type = "~"; }
if (listField === void 0) { listField = false; }
return this._counts.reduce(function (opts, _a) {
var name = _a.name;
if (!current.includes(name)) {
opts[name] = filterOptValue(name, _this._field, type, listField);
}
return opts;
}, {});
};
CountAggregateFilter.prototype._clearFilterOptions = function (current) {
var clear = Object.keys(this.getOptions()).reduce(function (opts, key) {
if (!current.includes(key)) {
opts[key] = null;
}
return opts;
}, {});
this.setOptions(clear);
};
return CountAggregateFilter;
}(Filter));
function filterOptValue(name, field, type, listField) {
if (type === void 0) { type = "~"; }
if (listField === void 0) { listField = false; }
var item = "\"" + name + "\"";
if (listField) {
item = "[\"" + name + "\"]";
}
return "" + field + type + item;
}
var RangeFilter = /** @class */ (function (_super) {
__extends(RangeFilter, _super);
function RangeFilter(field, limit) {
var _this = _super.call(this, {}, []) || this;
_this._field = "";
_this._range = [0, 0];
_this._limit = [0, 0]; // [min, max]
_this._current = "";
_this._filter = "";
_this.range = function () { return _this._range; };
_this.filter = function () { return _this._filter; };
_this.limit = function () { return _this._limit; };
_this.get = function () { return (_this._current === "" ? [] : [_this._current]); };
// @ts-ignore: override method
_this.set = function (from, to) {
_this._range = [from, to];
_this._filter = getFilterQuery(_this._range, _this._limit, _this._field);
_this._emitSelectionUpdated();
};
_this.reset = function () {
_this._range = _this._limit.map(function (r) { return r; });
if (_this._filter !== "") {
_this.clear();
}
};
_this.clear = function () {
_this._filter = "";
_this._emitSelectionUpdated();
};
_this._field = field;
_this._limit = limit;
_this._range = limit;
return _this;
}
RangeFilter.prototype.getRange = function () {
return this._range;
};
return RangeFilter;
}(Filter));
function getFilterQuery(range, limit, field) {
if (range[1] === limit[1] && range[0] === limit[0]) {
return "";
}
return "(" + field + " >= " + range[0] + " AND " + field + " <= " + range[1] + ")";
}
var RangeAggregateFilter = /** @class */ (function (_super) {
__extends(RangeAggregateFilter, _super);
function RangeAggregateFilter(field, pipeline, values) {
var _this = _super.call(this, field, [0, 0]) || this;
_this._prevInput = "";
_this._count = "";
_this._limitChangeListeners = [];
_this._field = field;
_this._addMinMaxToValues(values);
pipeline.listen(EVENT_SEARCH_SENT, function (sent) {
var q = sent.q, count = sent.count;
if (_this._prevInput !== q || (_this._count === "" && count !== "")) {
var removeListener_1 = pipeline.listen(EVENT_RESPONSE_UPDATED, function (response) {
var aggregates = response.getAggregates();
var bounce = _this._getLimit(aggregates);
var range = _this._calculateRange(bounce);
_this._limit = bounce;
_this._range = range;
_this._fireLimitChangeEvent({ bounce: bounce, range: range });
removeListener_1();
});
_this._prevInput = q;
_this._count = count;
}
});
return _this;
}
RangeAggregateFilter.prototype.addLimitChangeListener = function (listener) {
this._limitChangeListeners.push(listener);
};
RangeAggregateFilter.prototype.removeLimitChangeListener = function (listener) {
this._limitChangeListeners = this._limitChangeListeners.filter(function (item) { return item !== listener; });
};
RangeAggregateFilter.prototype._calculateRange = function (bounce) {
var range = this._range.map(function (r) { return r; });
if (this._range[0] < bounce[0]) {
range[0] = bounce[0];
}
if (this._range[1] > bounce[1] || this._range[1] <= range[0]) {
range[1] = bounce[1];
}
return range;
};
RangeAggregateFilter.prototype._fireLimitChangeEvent = function (_a) {
var bounce = _a.bounce, range = _a.range;
this._limitChangeListeners.forEach(function (func) { return func({ bounce: bounce, range: range }); });
};
RangeAggregateFilter.prototype._getLimit = function (aggregates) {
if (isEmpty(aggregates, this._field)) {
return this._limit.map(function (r) { return r; });
}
var min = aggregates["min." + this._field] || 0;
var max = aggregates["max." + this._field] || 0;
return [min, max];
};
RangeAggregateFilter.prototype._addMinMaxToValues = function (values) {
var _a = values.get(), min = _a.min, max = _a.max;
var minFields = typeof min === "string" ? min.split(",") : [];
if (!minFields.includes(this._field)) {
minFields.push(this._field);
values.set({ min: minFields.join(",") });
}
var maxFields = typeof max === "string" ? max.split(",") : [];
if (!maxFields.includes(this._field)) {
maxFields.push(this._field);
values.set({ max: maxFields.join(",") });
}
};
return RangeAggregateFilter;
}(RangeFilter));
var isEmpty = function (aggregates, field) {
return !aggregates && (!aggregates["max." + field] || !aggregates["min." + field]);
};
var PipelineContext = createContext({});
var Consumer = PipelineContext.Consumer;
var defaultConfig = {
qParam: "q",
qOverrideParam: "q.override",
qSuggestionsParam: "q.suggestions",
maxSuggestions: 5,
resultsPerPageParam: "resultsPerPage",
pageParam: "page"
};
var defaultState = {
response: null,
query: "",
completion: "",
suggestions: [],
config: defaultConfig
};
var Provider = /** @class */ (function (_super) {
__extends(Provider, _super);
function Provider() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.state = {
search: defaultState,
instant: defaultState
};
_this.unregisterFunctions = [];
_this.getContext = function (state) {
return (__assign(__assign({}, state), { search: __assign(__assign({}, state.search), { search: _this.search("search"), clear: _this.clear("search") }), instant: __assign(__assign({}, state.instant), { search: _this.search("instant"), clear: _this.clear("instant") }), resultClicked: _this.handleResultClicked, paginate: _this.handlePaginate }));
};
_this.search = function (key) {
return debounce(function (query, override) {
var _a;
if (override === void 0) { override = false; }
var _b = _this.props[key] || _this.instant, pipeline = _b.pipeline, values = _b.values;
var config = _this.state[key].config;
var text = (_a = {},
_a[config.qParam] = query,
_a[config.qOverrideParam] = undefined,
_a);
if (override) {
text[config.qOverrideParam] = "true";
}
values.set(text);
if (text[config.qParam]) {
pipeline.search(values.get());
}
else {
pipeline.clearResponse(values.get());
}
}, 50);
};
_this.clear = function (key) { return function (vals) {
var _a = _this.props[key] || _this.instan