mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-21 09:38:01 +02:00
All: Allow searching by note ID or using a callback URL
This commit is contained in:
parent
6b2e577be6
commit
3667bf3ed9
@ -660,6 +660,7 @@ packages/lib/models/SmartFilter.js
|
||||
packages/lib/models/Tag.js
|
||||
packages/lib/models/dateTimeFormats.test.js
|
||||
packages/lib/models/settings/FileHandler.js
|
||||
packages/lib/models/utils/isItemId.js
|
||||
packages/lib/models/utils/itemCanBeEncrypted.js
|
||||
packages/lib/models/utils/paginatedFeed.js
|
||||
packages/lib/models/utils/paginationToSql.js
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -642,6 +642,7 @@ packages/lib/models/SmartFilter.js
|
||||
packages/lib/models/Tag.js
|
||||
packages/lib/models/dateTimeFormats.test.js
|
||||
packages/lib/models/settings/FileHandler.js
|
||||
packages/lib/models/utils/isItemId.js
|
||||
packages/lib/models/utils/itemCanBeEncrypted.js
|
||||
packages/lib/models/utils/paginatedFeed.js
|
||||
packages/lib/models/utils/paginationToSql.js
|
||||
|
3
packages/lib/models/utils/isItemId.ts
Normal file
3
packages/lib/models/utils/isItemId.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export default (id: string) => {
|
||||
return id.match(/^[0-9a-zA-Z]{32}$/);
|
||||
};
|
@ -3,6 +3,7 @@
|
||||
const { setupDatabaseAndSynchronizer, db, sleep, switchClient, msleep } = require('../../testing/test-utils.js');
|
||||
const SearchEngine = require('../../services/searchengine/SearchEngine').default;
|
||||
const Note = require('../../models/Note').default;
|
||||
const Folder = require('../../models/Folder').default;
|
||||
const ItemChange = require('../../models/ItemChange').default;
|
||||
const Setting = require('../../models/Setting').default;
|
||||
|
||||
@ -526,4 +527,21 @@ describe('services_SearchEngine', () => {
|
||||
expect((await engine.search('hello')).length).toBe(0);
|
||||
expect((await engine.search('hello', { appendWildCards: true })).length).toBe(2);
|
||||
}));
|
||||
|
||||
it('should search by item ID if no other result was found', (async () => {
|
||||
const f1 = await Folder.save({});
|
||||
const n1 = await Note.save({ title: 'hello1', parent_id: f1.id });
|
||||
const n2 = await Note.save({ title: 'hello2' });
|
||||
|
||||
await engine.syncTables();
|
||||
|
||||
const results = await engine.search(n1.id);
|
||||
expect(results.length).toBe(1);
|
||||
expect(results[0].id).toBe(n1.id);
|
||||
expect(results[0].title).toBe(n1.title);
|
||||
expect(results[0].parent_id).toBe(n1.parent_id);
|
||||
|
||||
expect((await engine.search(n2.id))[0].id).toBe(n2.id);
|
||||
expect(await engine.search(f1.id)).toEqual([]);
|
||||
}));
|
||||
});
|
||||
|
@ -9,6 +9,9 @@ import filterParser, { Term } from './filterParser';
|
||||
import queryBuilder from './queryBuilder';
|
||||
import { ItemChangeEntity, NoteEntity } from '../database/types';
|
||||
import JoplinDatabase from '../../JoplinDatabase';
|
||||
import isItemId from '../../models/utils/isItemId';
|
||||
import BaseItem from '../../models/BaseItem';
|
||||
import { isCallbackUrl, parseCallbackUrl } from '../../callbackUrlUtils';
|
||||
const { sprintf } = require('sprintf-js');
|
||||
const { pregQuote, scriptType, removeDiacritics } = require('../../string-utils.js');
|
||||
|
||||
@ -31,8 +34,11 @@ interface SearchOptions {
|
||||
|
||||
export interface ProcessResultsRow {
|
||||
id: string;
|
||||
parent_id: string;
|
||||
title: string;
|
||||
offsets: string;
|
||||
user_updated_time: number;
|
||||
user_created_time: number;
|
||||
matchinfo: Buffer;
|
||||
item_type?: ModelType;
|
||||
fields?: string[];
|
||||
@ -613,6 +619,39 @@ export default class SearchEngine {
|
||||
return SearchEngine.SEARCH_TYPE_FTS;
|
||||
}
|
||||
|
||||
private async searchFromItemIds(searchString: string): Promise<ProcessResultsRow[]> {
|
||||
let itemId = '';
|
||||
|
||||
if (isCallbackUrl(searchString)) {
|
||||
const parsed = parseCallbackUrl(searchString);
|
||||
itemId = parsed.params.id;
|
||||
} else if (isItemId(searchString)) {
|
||||
itemId = searchString;
|
||||
}
|
||||
|
||||
if (itemId) {
|
||||
const item = await BaseItem.loadItemById(itemId);
|
||||
|
||||
// We only return notes for now because the UI doesn't handle anything else.
|
||||
if (item && item.type_ === ModelType.Note) {
|
||||
return [
|
||||
{
|
||||
id: item.id,
|
||||
parent_id: item.parent_id || '',
|
||||
matchinfo: Buffer.from(''),
|
||||
offsets: '',
|
||||
title: item.title || item.id,
|
||||
user_updated_time: item.user_updated_time || item.updated_time,
|
||||
user_created_time: item.user_created_time || item.created_time,
|
||||
fields: ['id'],
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public async search(searchString: string, options: SearchOptions = null): Promise<ProcessResultsRow[]> {
|
||||
if (!searchString) return [];
|
||||
|
||||
@ -625,11 +664,12 @@ export default class SearchEngine {
|
||||
const searchType = this.determineSearchType_(searchString, options.searchType);
|
||||
const parsedQuery = await this.parseQuery(searchString);
|
||||
|
||||
let rows: ProcessResultsRow[] = [];
|
||||
|
||||
if (searchType === SearchEngine.SEARCH_TYPE_BASIC) {
|
||||
searchString = this.normalizeText_(searchString);
|
||||
const rows = await this.basicSearch(searchString);
|
||||
rows = await this.basicSearch(searchString);
|
||||
this.processResults_(rows, parsedQuery, true);
|
||||
return rows;
|
||||
} else {
|
||||
// SEARCH_TYPE_FTS
|
||||
// FTS will ignore all special characters, like "-" in the index. So if
|
||||
@ -654,14 +694,19 @@ export default class SearchEngine {
|
||||
const useFts = searchType === SearchEngine.SEARCH_TYPE_FTS;
|
||||
try {
|
||||
const { query, params } = queryBuilder(parsedQuery.allTerms, useFts);
|
||||
const rows = (await this.db().selectAll(query, params)) as ProcessResultsRow[];
|
||||
rows = (await this.db().selectAll(query, params)) as ProcessResultsRow[];
|
||||
this.processResults_(rows, parsedQuery, !useFts);
|
||||
return rows;
|
||||
} catch (error) {
|
||||
this.logger().warn(`Cannot execute MATCH query: ${searchString}: ${error.message}`);
|
||||
return [];
|
||||
rows = [];
|
||||
}
|
||||
}
|
||||
|
||||
if (!rows.length) {
|
||||
rows = await this.searchFromItemIds(searchString);
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
public async destroy() {
|
||||
|
@ -6,6 +6,9 @@ describe('searchengine/gotoAnythingStyleQuery', () => {
|
||||
const testCases: [string, string][] = [
|
||||
['hello', 'hello*'],
|
||||
['hello welc', 'hello* welc*'],
|
||||
['joplin://x-callback-url/openNote?id=3600e074af0e4b06aeb0ae76d3d96af7', 'joplin://x-callback-url/openNote?id=3600e074af0e4b06aeb0ae76d3d96af7'],
|
||||
['3600e074af0e4b06aeb0ae76d3d96af7', '3600e074af0e4b06aeb0ae76d3d96af7'],
|
||||
['', ''],
|
||||
];
|
||||
|
||||
for (const [input, expected] of testCases) {
|
||||
|
@ -1,6 +1,11 @@
|
||||
import { isCallbackUrl } from '../../callbackUrlUtils';
|
||||
import isItemId from '../../models/utils/isItemId';
|
||||
|
||||
export default (query: string) => {
|
||||
if (!query) return '';
|
||||
|
||||
if (isItemId(query) || isCallbackUrl(query)) return query;
|
||||
|
||||
const output = [];
|
||||
const splitted = query.split(' ');
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user