mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
Chore: Add types to search engine
This commit is contained in:
parent
af91fd99cc
commit
357a3e2e7b
@ -711,6 +711,7 @@ packages/lib/services/searchengine/SearchFilter.test.js
|
|||||||
packages/lib/services/searchengine/filterParser.js
|
packages/lib/services/searchengine/filterParser.js
|
||||||
packages/lib/services/searchengine/filterParser.test.js
|
packages/lib/services/searchengine/filterParser.test.js
|
||||||
packages/lib/services/searchengine/gotoAnythingStyleQuery.js
|
packages/lib/services/searchengine/gotoAnythingStyleQuery.js
|
||||||
|
packages/lib/services/searchengine/gotoAnythingStyleQuery.test.js
|
||||||
packages/lib/services/searchengine/queryBuilder.js
|
packages/lib/services/searchengine/queryBuilder.js
|
||||||
packages/lib/services/share/ShareService.js
|
packages/lib/services/share/ShareService.js
|
||||||
packages/lib/services/share/ShareService.test.js
|
packages/lib/services/share/ShareService.test.js
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -698,6 +698,7 @@ packages/lib/services/searchengine/SearchFilter.test.js
|
|||||||
packages/lib/services/searchengine/filterParser.js
|
packages/lib/services/searchengine/filterParser.js
|
||||||
packages/lib/services/searchengine/filterParser.test.js
|
packages/lib/services/searchengine/filterParser.test.js
|
||||||
packages/lib/services/searchengine/gotoAnythingStyleQuery.js
|
packages/lib/services/searchengine/gotoAnythingStyleQuery.js
|
||||||
|
packages/lib/services/searchengine/gotoAnythingStyleQuery.test.js
|
||||||
packages/lib/services/searchengine/queryBuilder.js
|
packages/lib/services/searchengine/queryBuilder.js
|
||||||
packages/lib/services/share/ShareService.js
|
packages/lib/services/share/ShareService.js
|
||||||
packages/lib/services/share/ShareService.test.js
|
packages/lib/services/share/ShareService.test.js
|
||||||
|
@ -431,6 +431,7 @@ describe('services_SearchEngine', () => {
|
|||||||
['title:abcd efgh', { _: ['efgh'], title: ['abcd'] }],
|
['title:abcd efgh', { _: ['efgh'], title: ['abcd'] }],
|
||||||
['title:abcd', { title: ['abcd'] }],
|
['title:abcd', { title: ['abcd'] }],
|
||||||
['"abcd efgh"', { _: ['abcd efgh'] }],
|
['"abcd efgh"', { _: ['abcd efgh'] }],
|
||||||
|
['"abcd efgh" ijkl', { _: ['abcd efgh', 'ijkl'] }],
|
||||||
['title:abcd title:efgh', { title: ['abcd', 'efgh'] }],
|
['title:abcd title:efgh', { title: ['abcd', 'efgh'] }],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -5,20 +5,32 @@ import Note from '../../models/Note';
|
|||||||
import BaseModel from '../../BaseModel';
|
import BaseModel from '../../BaseModel';
|
||||||
import ItemChangeUtils from '../ItemChangeUtils';
|
import ItemChangeUtils from '../ItemChangeUtils';
|
||||||
import shim from '../../shim';
|
import shim from '../../shim';
|
||||||
import filterParser from './filterParser';
|
import filterParser, { Term } from './filterParser';
|
||||||
import queryBuilder from './queryBuilder';
|
import queryBuilder from './queryBuilder';
|
||||||
import { ItemChangeEntity, NoteEntity } from '../database/types';
|
import { ItemChangeEntity, NoteEntity } from '../database/types';
|
||||||
const { sprintf } = require('sprintf-js');
|
const { sprintf } = require('sprintf-js');
|
||||||
const { pregQuote, scriptType, removeDiacritics } = require('../../string-utils.js');
|
const { pregQuote, scriptType, removeDiacritics } = require('../../string-utils.js');
|
||||||
|
|
||||||
|
enum SearchType {
|
||||||
|
Auto = 'auto',
|
||||||
|
Basic = 'basic',
|
||||||
|
Nonlatin = 'nonlatin',
|
||||||
|
Fts = 'fts',
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SearchOptions {
|
||||||
|
searchType: SearchType;
|
||||||
|
fuzzy: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export default class SearchEngine {
|
export default class SearchEngine {
|
||||||
|
|
||||||
public static instance_: SearchEngine = null;
|
public static instance_: SearchEngine = null;
|
||||||
public static relevantFields = 'id, title, body, user_created_time, user_updated_time, is_todo, todo_completed, todo_due, parent_id, latitude, longitude, altitude, source_url';
|
public static relevantFields = 'id, title, body, user_created_time, user_updated_time, is_todo, todo_completed, todo_due, parent_id, latitude, longitude, altitude, source_url';
|
||||||
public static SEARCH_TYPE_AUTO = 'auto';
|
public static SEARCH_TYPE_AUTO = SearchType.Auto;
|
||||||
public static SEARCH_TYPE_BASIC = 'basic';
|
public static SEARCH_TYPE_BASIC = SearchType.Basic;
|
||||||
public static SEARCH_TYPE_NONLATIN_SCRIPT = 'nonlatin';
|
public static SEARCH_TYPE_NONLATIN_SCRIPT = SearchType.Nonlatin;
|
||||||
public static SEARCH_TYPE_FTS = 'fts';
|
public static SEARCH_TYPE_FTS = SearchType.Fts;
|
||||||
|
|
||||||
public dispatch: Function = (_o: any) => {};
|
public dispatch: Function = (_o: any) => {};
|
||||||
private logger_ = new Logger();
|
private logger_ = new Logger();
|
||||||
@ -417,7 +429,7 @@ export default class SearchEngine {
|
|||||||
|
|
||||||
const trimQuotes = (str: string) => str.startsWith('"') ? str.substr(1, str.length - 2) : str;
|
const trimQuotes = (str: string) => str.startsWith('"') ? str.substr(1, str.length - 2) : str;
|
||||||
|
|
||||||
let allTerms: any[] = [];
|
let allTerms: Term[] = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
allTerms = filterParser(query);
|
allTerms = filterParser(query);
|
||||||
@ -429,7 +441,20 @@ export default class SearchEngine {
|
|||||||
const titleTerms = allTerms.filter(x => x.name === 'title' && !x.negated).map(x => trimQuotes(x.value));
|
const titleTerms = allTerms.filter(x => x.name === 'title' && !x.negated).map(x => trimQuotes(x.value));
|
||||||
const bodyTerms = allTerms.filter(x => x.name === 'body' && !x.negated).map(x => trimQuotes(x.value));
|
const bodyTerms = allTerms.filter(x => x.name === 'body' && !x.negated).map(x => trimQuotes(x.value));
|
||||||
|
|
||||||
const terms: any = { _: textTerms, 'title': titleTerms, 'body': bodyTerms };
|
interface ComplexTerm {
|
||||||
|
type: 'regex' | 'text';
|
||||||
|
value: string;
|
||||||
|
scriptType: any;
|
||||||
|
valueRegex?: RegExp;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Terms {
|
||||||
|
_: (string | ComplexTerm)[];
|
||||||
|
title: (string | ComplexTerm)[];
|
||||||
|
body: (string | ComplexTerm)[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const terms: Terms = { _: textTerms, 'title': titleTerms, 'body': bodyTerms };
|
||||||
|
|
||||||
// Filter terms:
|
// Filter terms:
|
||||||
// - Convert wildcards to regex
|
// - Convert wildcards to regex
|
||||||
@ -438,7 +463,9 @@ export default class SearchEngine {
|
|||||||
|
|
||||||
let termCount = 0;
|
let termCount = 0;
|
||||||
const keys = [];
|
const keys = [];
|
||||||
for (const col in terms) {
|
for (const col2 in terms) {
|
||||||
|
const col = col2 as '_' | 'title' | 'body';
|
||||||
|
|
||||||
if (!terms.hasOwnProperty(col)) continue;
|
if (!terms.hasOwnProperty(col)) continue;
|
||||||
|
|
||||||
if (!terms[col].length) {
|
if (!terms[col].length) {
|
||||||
@ -447,7 +474,7 @@ export default class SearchEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (let i = terms[col].length - 1; i >= 0; i--) {
|
for (let i = terms[col].length - 1; i >= 0; i--) {
|
||||||
const term = terms[col][i];
|
const term = terms[col][i] as string;
|
||||||
|
|
||||||
// SQlLite FTS doesn't allow "*" queries and neither shall we
|
// SQlLite FTS doesn't allow "*" queries and neither shall we
|
||||||
if (term === '*') {
|
if (term === '*') {
|
||||||
@ -468,12 +495,16 @@ export default class SearchEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// The object "allTerms" is used for query construction purposes (this contains all the filter terms)
|
// The object "allTerms" is used for query construction purposes (this
|
||||||
// Since this is used for the FTS match query, we need to normalize text, title and body terms.
|
// contains all the filter terms) Since this is used for the FTS match
|
||||||
// Note, we're not normalizing terms like tag because these are matched using SQL LIKE operator and so we must preserve their diacritics.
|
// query, we need to normalize text, title and body terms. Note, we're
|
||||||
|
// not normalizing terms like tag because these are matched using SQL
|
||||||
|
// LIKE operator and so we must preserve their diacritics.
|
||||||
//
|
//
|
||||||
// The object "terms" only include text, title, body terms and is used for highlighting.
|
// The object "terms" only include text, title, body terms and is used
|
||||||
// By not normalizing the text, title, body in "terms", highlighting still works correctly for words with diacritics.
|
// for highlighting. By not normalizing the text, title, body in
|
||||||
|
// "terms", highlighting still works correctly for words with
|
||||||
|
// diacritics.
|
||||||
//
|
//
|
||||||
|
|
||||||
allTerms = allTerms.map(x => {
|
allTerms = allTerms.map(x => {
|
||||||
@ -521,9 +552,9 @@ export default class SearchEngine {
|
|||||||
const searchOptions: any = {};
|
const searchOptions: any = {};
|
||||||
|
|
||||||
for (const key of parsedQuery.keys) {
|
for (const key of parsedQuery.keys) {
|
||||||
if (parsedQuery.terms[key].length === 0) continue;
|
if ((parsedQuery.terms as any)[key].length === 0) continue;
|
||||||
|
|
||||||
const term = parsedQuery.terms[key].map((x: any) => x.value).join(' ');
|
const term = (parsedQuery.terms as any)[key].map((x: Term) => x.value).join(' ');
|
||||||
if (key === '_') searchOptions.anywherePattern = `*${term}*`;
|
if (key === '_') searchOptions.anywherePattern = `*${term}*`;
|
||||||
if (key === 'title') searchOptions.titlePattern = `*${term}*`;
|
if (key === 'title') searchOptions.titlePattern = `*${term}*`;
|
||||||
if (key === 'body') searchOptions.bodyPattern = `*${term}*`;
|
if (key === 'body') searchOptions.bodyPattern = `*${term}*`;
|
||||||
@ -561,12 +592,13 @@ export default class SearchEngine {
|
|||||||
return SearchEngine.SEARCH_TYPE_FTS;
|
return SearchEngine.SEARCH_TYPE_FTS;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async search(searchString: string, options: any = null) {
|
public async search(searchString: string, options: SearchOptions = null) {
|
||||||
if (!searchString) return [];
|
if (!searchString) return [];
|
||||||
|
|
||||||
options = Object.assign({}, {
|
options = {
|
||||||
searchType: SearchEngine.SEARCH_TYPE_AUTO,
|
searchType: SearchEngine.SEARCH_TYPE_AUTO,
|
||||||
}, options);
|
...options,
|
||||||
|
};
|
||||||
|
|
||||||
const searchType = this.determineSearchType_(searchString, options.searchType);
|
const searchType = this.determineSearchType_(searchString, options.searchType);
|
||||||
const parsedQuery = await this.parseQuery(searchString);
|
const parsedQuery = await this.parseQuery(searchString);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
interface Term {
|
export interface Term {
|
||||||
name: string;
|
name: string;
|
||||||
value: string;
|
value: string;
|
||||||
negated: boolean;
|
negated: boolean;
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
import gotoAnythingStyleQuery from './gotoAnythingStyleQuery';
|
||||||
|
|
||||||
|
describe('searchengine/gotoAnythingStyleQuery', () => {
|
||||||
|
|
||||||
|
it('should prepare queries', () => {
|
||||||
|
const testCases: [string, string][] = [
|
||||||
|
['hello', 'hello*'],
|
||||||
|
['hello welc', 'hello* welc*'],
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const [input, expected] of testCases) {
|
||||||
|
const actual = gotoAnythingStyleQuery(input);
|
||||||
|
expect(actual).toBe(expected);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user