coveo-search-ui
Version:
Coveo JavaScript Search Framework
219 lines (203 loc) • 7.75 kB
text/typescript
///<reference path='Omnibox.ts'/>
import { Omnibox, IOmniboxSuggestion } from './Omnibox';
import { OmniboxEvents, IPopulateOmniboxSuggestionsEventArgs } from '../../events/OmniboxEvents';
import { IFieldDescription } from '../../rest/FieldDescription';
import { IEndpointError } from '../../rest/EndpointError';
import * as _ from 'underscore';
import { Result } from '../../magicbox/Result/Result';
import { MagicBoxUtils } from '../../magicbox/MagicBoxUtils';
interface IFieldAddonHash {
type: string;
before: string;
after: string;
current: string;
field?: string;
}
export class FieldAddon {
static INDEX = 64;
cache: { [hash: string]: Promise<IOmniboxSuggestion[]> } = {};
constructor(public omnibox: Omnibox) {
this.omnibox.bind.on(this.omnibox.element, OmniboxEvents.populateOmniboxSuggestions, (args: IPopulateOmniboxSuggestionsEventArgs) => {
args.suggestions.push(this.getSuggestion());
});
}
public getSuggestion(): Promise<IOmniboxSuggestion[]> {
const hash = this.getHash();
if (hash == null) {
return null;
}
const hashString = this.hashToString(hash);
if (this.cache[hashString] != null) {
return this.hashValueToSuggestion(hash, this.cache[hashString]);
}
let values: Promise<IOmniboxSuggestion[]>;
if (hash.type == 'FieldName') {
values = this.fieldNames(hash.current);
}
if (hash.type == 'FieldValue') {
values = this.fieldValues(hash.field, hash.current);
}
if (hash.type == 'SimpleFieldName') {
values = this.simpleFieldNames(hash.current);
}
this.cache[hashString] = values;
values.catch(() => {
delete this.cache[hashString];
});
return this.hashValueToSuggestion(hash, values);
}
private getHash(): IFieldAddonHash {
let fieldName: Result = _.last(this.omnibox.resultAtCursor('FieldName'));
if (fieldName != null) {
fieldName = fieldName.findParent('Field') || fieldName;
const currentField = fieldName.toString();
const before = fieldName.before();
const after = fieldName.after();
return { type: 'FieldName', current: currentField, before: before, after: after };
}
const fieldValue: Result = _.last(this.omnibox.resultAtCursor('FieldValue'));
if (fieldValue) {
const fieldQuery =
fieldValue.findParent('FieldQuery') || (this.omnibox.options.enableSimpleFieldAddon && fieldValue.findParent('FieldSimpleQuery'));
if (fieldQuery) {
let field = fieldQuery.find('FieldName').toString();
if (this.omnibox.options.fieldAlias) {
if (field in this.omnibox.options.fieldAlias) {
field = this.omnibox.options.fieldAlias[field];
}
}
const value = fieldValue.toString();
const before = fieldValue.before();
const after = fieldValue.after();
return { type: 'FieldValue', field: field, current: value, before: before, after: after };
}
}
if (this.omnibox.options.enableSimpleFieldAddon) {
const word: Result = _.last(this.omnibox.resultAtCursor('Word'));
if (word != null) {
const current = word.toString();
const before = word.before();
const after = word.after();
return { type: 'SimpleFieldName', current, before, after };
}
}
}
private hashToString(hash: IFieldAddonHash) {
if (hash == null) {
return null;
}
return hash.type + hash.current + (hash.field || '');
}
private hashValueToSuggestion(hash: IFieldAddonHash, promise: Promise<IOmniboxSuggestion[]>): Promise<IOmniboxSuggestion[]> {
return promise.then(values => {
const suggestions = _.map<any, IOmniboxSuggestion>(values, (value: string, i): IOmniboxSuggestion => {
const suggestion: IOmniboxSuggestion = {
text:
hash.before +
(hash.current.toLowerCase().indexOf(value.toLowerCase()) == 0 ? hash.current + value.substr(hash.current.length) : value) +
hash.after,
html: MagicBoxUtils.highlightText(value, hash.current, true),
index: FieldAddon.INDEX - i / values.length
};
return suggestion;
});
return suggestions;
});
}
private fields: Promise<IOmniboxSuggestion[]>;
private getFields(): Promise<IOmniboxSuggestion[]> {
if (this.fields == null) {
this.fields = new Promise<any[]>((resolve, reject) => {
if (this.omnibox.options.listOfFields != null) {
resolve(<string[]>this.omnibox.options.listOfFields);
} else {
const promise: Promise<IFieldDescription[] | IEndpointError> = this.omnibox.queryController.getEndpoint().listFields();
promise
.then((fieldDescriptions: IFieldDescription[]) => {
const fieldNames = _.chain(fieldDescriptions)
.filter((fieldDescription: IFieldDescription) => fieldDescription.includeInQuery && fieldDescription.groupByField)
.map((fieldDescription: IFieldDescription) => fieldDescription.name.substr(1))
.value();
resolve(fieldNames);
})
.catch(() => {
reject();
});
}
});
}
return this.fields;
}
private fieldNames(current: string): Promise<IOmniboxSuggestion[]> {
const withAt = current.length > 0 && current[0] == '@';
const fieldName = withAt ? current.substr(1) : current;
const fieldNameLC = fieldName.toLowerCase();
return this.getFields().then((fields: string[] | IOmniboxSuggestion[]): any[] => {
let matchFields = _.chain(fields)
.map((fieldName: any) => {
const fieldNameBeginsWithAt = fieldName.length > 0 && fieldName[0] == '@';
return {
index: fieldName.toLowerCase().indexOf(fieldNameLC),
field: fieldNameBeginsWithAt ? fieldName : '@' + fieldName
};
})
.filter(field => {
return field.index != -1 && field.field.length > current.length;
})
.sortBy('index')
.map(field => field.field)
.value();
matchFields = _.first(matchFields, 5);
return matchFields;
});
}
private fieldValues(field: string, current: string): Promise<any[]> {
return this.omnibox.queryController
.getEndpoint()
.listFieldValues({
pattern: '.*' + current + '.*',
patternType: 'RegularExpression',
sortCriteria: 'occurrences',
field: '@' + field,
maximumNumberOfValues: 5
})
.then(values => {
return _.chain(values)
.map(value => {
return {
index: value.value.toLowerCase().indexOf(current),
value: value.value
};
})
.filter(value => {
return value.value.length > current.length;
})
.sortBy('index')
.map(value => {
return value.value.replace(/ /g, '\u00A0');
})
.value();
});
}
private simpleFieldNames(current: string): Promise<IOmniboxSuggestion[]> {
const fieldName = current;
const fieldNameLC = fieldName.toLowerCase();
return this.getFields().then((fields: any[]): IOmniboxSuggestion[] => {
let matchFields: any = _.chain(fields)
.map((field: string) => {
return {
index: field.toLowerCase().indexOf(fieldNameLC),
field: field + ':'
};
})
.filter(field => {
return field.index != -1 && field.field.length > current.length;
})
.sortBy('index')
.map(field => field.field)
.value();
matchFields = _.first(matchFields, 5);
return matchFields;
});
}
}