1
0
mirror of https://github.com/xuthus83/pigallery2.git synced 2025-01-14 14:43:17 +08:00

improving date autocomplete and parsing for search #58

This commit is contained in:
Patrik J. Braun 2021-04-04 11:40:43 +02:00
parent f30d6988ec
commit 5405a6f9d3
3 changed files with 77 additions and 27 deletions

View File

@ -48,6 +48,41 @@ export class SearchQueryParser {
constructor(private keywords: QueryKeywords) { constructor(private keywords: QueryKeywords) {
} }
public static stringifyText(text: string, matchType = TextSearchQueryMatchTypes.like): string {
if (matchType === TextSearchQueryMatchTypes.exact_match) {
return '"' + text + '"';
}
if (text.indexOf(' ') !== -1) {
return '(' + text + ')';
}
return text;
}
private static stringifyDate(time: number): string {
const date = new Date(time);
// simplify date with yeah only if its first of jan
if (date.getMonth() === 0 && date.getDate() === 1) {
return date.getFullYear().toString();
}
return this.stringifyText(date.toLocaleDateString());
}
private static parseDate(text: string): number {
if (text.charAt(0) === '"' || text.charAt(0) === '(') {
text = text.substring(1);
}
if (text.charAt(text.length - 1) === '"' || text.charAt(text.length - 1) === ')') {
text = text.substring(0, text.length - 1);
}
// it is the year only
if (text.length === 4) {
const d = new Date(2000, 0, 1);
d.setFullYear(parseInt(text, 10));
return d.getTime();
}
return Date.parse(text);
}
public parse(str: string, implicitOR = true): SearchQueryDTO { public parse(str: string, implicitOR = true): SearchQueryDTO {
str = str.replace(/\s\s+/g, ' ') // remove double spaces str = str.replace(/\s\s+/g, ' ') // remove double spaces
.replace(/:\s+/g, ':').replace(/\)(?=\S)/g, ') ').trim(); .replace(/:\s+/g, ':').replace(/\)(?=\S)/g, ') ').trim();
@ -136,13 +171,13 @@ export class SearchQueryParser {
if (str.startsWith(this.keywords.from + ':')) { if (str.startsWith(this.keywords.from + ':')) {
return <FromDateSearch>{ return <FromDateSearch>{
type: SearchQueryTypes.from_date, type: SearchQueryTypes.from_date,
value: Date.parse(str.slice((this.keywords.from + ':').length + 1, str.length - 1)) value: SearchQueryParser.parseDate(str.substring((this.keywords.from + ':').length))
}; };
} }
if (str.startsWith(this.keywords.to + ':')) { if (str.startsWith(this.keywords.to + ':')) {
return <ToDateSearch>{ return <ToDateSearch>{
type: SearchQueryTypes.to_date, type: SearchQueryTypes.to_date,
value: Date.parse(str.slice((this.keywords.to + ':').length + 1, str.length - 1)) value: SearchQueryParser.parseDate(str.substring((this.keywords.to + ':').length))
}; };
} }
@ -240,12 +275,14 @@ export class SearchQueryParser {
if (!(<FromDateSearch>query).value) { if (!(<FromDateSearch>query).value) {
return ''; return '';
} }
return this.keywords.from + ':(' + new Date((<FromDateSearch>query).value).toLocaleDateString() + ')'.trim(); return this.keywords.from + ':' +
SearchQueryParser.stringifyDate((<FromDateSearch>query).value);
case SearchQueryTypes.to_date: case SearchQueryTypes.to_date:
if (!(<ToDateSearch>query).value) { if (!(<ToDateSearch>query).value) {
return ''; return '';
} }
return this.keywords.to + ':(' + new Date((<ToDateSearch>query).value).toLocaleDateString() + ')'.trim(); return this.keywords.to + ':' +
SearchQueryParser.stringifyDate((<ToDateSearch>query).value);
case SearchQueryTypes.min_rating: case SearchQueryTypes.min_rating:
return this.keywords.minRating + ':' + (isNaN((<RangeSearch>query).value) ? '' : (<RangeSearch>query).value); return this.keywords.minRating + ':' + (isNaN((<RangeSearch>query).value) ? '' : (<RangeSearch>query).value);
case SearchQueryTypes.max_rating: case SearchQueryTypes.max_rating:
@ -261,13 +298,7 @@ export class SearchQueryParser {
return (<DistanceSearch>query).distance + '-' + this.keywords.kmFrom + ':' + (<DistanceSearch>query).from.text; return (<DistanceSearch>query).distance + '-' + this.keywords.kmFrom + ':' + (<DistanceSearch>query).from.text;
case SearchQueryTypes.any_text: case SearchQueryTypes.any_text:
if ((<TextSearch>query).matchType === TextSearchQueryMatchTypes.exact_match) { return SearchQueryParser.stringifyText((<TextSearch>query).text, (<TextSearch>query).matchType);
return '"' + (<TextSearch>query).text + '"';
} else if ((<TextSearch>query).text.indexOf(' ') !== -1) {
return '(' + (<TextSearch>query).text + ')';
}
return (<TextSearch>query).text;
case SearchQueryTypes.person: case SearchQueryTypes.person:
case SearchQueryTypes.position: case SearchQueryTypes.position:
@ -278,13 +309,8 @@ export class SearchQueryParser {
if (!(<TextSearch>query).text) { if (!(<TextSearch>query).text) {
return ''; return '';
} }
if ((<TextSearch>query).matchType === TextSearchQueryMatchTypes.exact_match) { return (<any>this.keywords)[SearchQueryTypes[query.type]] + ':' +
return (<any>this.keywords)[SearchQueryTypes[query.type]] + ':"' + (<TextSearch>query).text + '"'; SearchQueryParser.stringifyText((<TextSearch>query).text, (<TextSearch>query).matchType);
} else if ((<TextSearch>query).text.indexOf(' ') !== -1) {
return (<any>this.keywords)[SearchQueryTypes[query.type]] + ':(' + (<TextSearch>query).text + ')';
}
return (<any>this.keywords)[SearchQueryTypes[query.type]] + ':' + (<TextSearch>query).text;
default: default:
throw new Error('Unknown type: ' + query.type); throw new Error('Unknown type: ' + query.type);

View File

@ -6,6 +6,7 @@ import {SearchQueryParserService} from './search-query-parser.service';
import {BehaviorSubject} from 'rxjs'; import {BehaviorSubject} from 'rxjs';
import {SearchQueryTypes, TextSearchQueryTypes} from '../../../../../common/entities/SearchQueryDTO'; import {SearchQueryTypes, TextSearchQueryTypes} from '../../../../../common/entities/SearchQueryDTO';
import {QueryParams} from '../../../../../common/QueryParams'; import {QueryParams} from '../../../../../common/QueryParams';
import {SearchQueryParser} from '../../../../../common/SearchQueryParser';
@Injectable() @Injectable()
export class AutoCompleteService { export class AutoCompleteService {
@ -31,6 +32,15 @@ export class AutoCompleteService {
this.keywords.push(i + this._searchQueryParserService.keywords.NSomeOf); this.keywords.push(i + this._searchQueryParserService.keywords.NSomeOf);
} }
this.keywords.push(this._searchQueryParserService.keywords.to + ':' +
SearchQueryParser.stringifyText((new Date).getFullYear().toString()));
this.keywords.push(this._searchQueryParserService.keywords.to + ':' +
SearchQueryParser.stringifyText((new Date).toLocaleDateString()));
this.keywords.push(this._searchQueryParserService.keywords.from + ':' +
SearchQueryParser.stringifyText((new Date).getFullYear().toString()));
this.keywords.push(this._searchQueryParserService.keywords.from + ':' +
SearchQueryParser.stringifyText((new Date).toLocaleDateString()));
TextSearchQueryTypes.forEach(t => { TextSearchQueryTypes.forEach(t => {
this.textSearchKeywordsMap[(<any>this._searchQueryParserService.keywords)[SearchQueryTypes[t]]] = t; this.textSearchKeywordsMap[(<any>this._searchQueryParserService.keywords)[SearchQueryTypes[t]]] = t;
}); });
@ -69,14 +79,6 @@ export class AutoCompleteService {
return items; return items;
} }
private getTypeFromPrefix(text: string): SearchQueryTypes {
const tokens = text.split(':');
if (tokens.length !== 2) {
return null;
}
return this.textSearchKeywordsMap[tokens[0]] || null;
}
public getPrefixLessSearchText(text: string): string { public getPrefixLessSearchText(text: string): string {
const tokens = text.split(':'); const tokens = text.split(':');
if (tokens.length !== 2) { if (tokens.length !== 2) {
@ -89,6 +91,14 @@ export class AutoCompleteService {
return tokens[1]; return tokens[1];
} }
private getTypeFromPrefix(text: string): SearchQueryTypes {
const tokens = text.split(':');
if (tokens.length !== 2) {
return null;
}
return this.textSearchKeywordsMap[tokens[0]] || null;
}
private ACItemToRenderable(item: IAutoCompleteItem): RenderableAutoCompleteItem { private ACItemToRenderable(item: IAutoCompleteItem): RenderableAutoCompleteItem {
if (!item.type) { if (!item.type) {
return {text: item.text, queryHint: item.text}; return {text: item.text, queryHint: item.text};

View File

@ -9,6 +9,7 @@ import {
MinResolutionSearch, MinResolutionSearch,
OrientationSearch, OrientationSearch,
ORSearchQuery, ORSearchQuery,
RangeSearch,
SearchQueryDTO, SearchQueryDTO,
SearchQueryTypes, SearchQueryTypes,
SomeOfSearchQuery, SomeOfSearchQuery,
@ -66,8 +67,21 @@ describe('SearchQueryParser', () => {
}); });
it('Date search', () => { it('Date search', () => {
check(<FromDateSearch>{type: SearchQueryTypes.from_date, value: (new Date(2020, 1, 10)).getTime()});
check(<FromDateSearch>{type: SearchQueryTypes.from_date, value: (new Date(2020, 1, 1)).getTime()}); check(<FromDateSearch>{type: SearchQueryTypes.from_date, value: (new Date(2020, 1, 1)).getTime()});
check(<ToDateSearch>{type: SearchQueryTypes.to_date, value: (new Date(2020, 1, 2)).getTime()}); check(<ToDateSearch>{type: SearchQueryTypes.to_date, value: (new Date(2020, 1, 20)).getTime()});
check(<ToDateSearch>{type: SearchQueryTypes.to_date, value: (new Date(2020, 1, 1)).getTime()});
const parser = new SearchQueryParser(keywords);
// test if date gets simplified on 1st of Jan.
let query: RangeSearch = <ToDateSearch>{type: SearchQueryTypes.to_date, value: (new Date(2020, 0, 1)).getTime()};
expect(parser.parse(keywords.to + ':' + (new Date(query.value)).getFullYear()))
.to.deep.equals(query, parser.stringify(query));
query = <FromDateSearch>{type: SearchQueryTypes.from_date, value: (new Date(2020, 0, 1)).getTime()};
expect(parser.parse(keywords.from + ':' + (new Date(query.value)).getFullYear()))
.to.deep.equals(query, parser.stringify(query));
}); });
it('Rating search', () => { it('Rating search', () => {
check(<MinRatingSearch>{type: SearchQueryTypes.min_rating, value: 10}); check(<MinRatingSearch>{type: SearchQueryTypes.min_rating, value: 10});