1
0
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:
Laurent Cozic 2023-10-25 14:41:05 +01:00
parent 6b2e577be6
commit 3667bf3ed9
7 changed files with 81 additions and 5 deletions

View File

@ -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
View File

@ -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

View File

@ -0,0 +1,3 @@
export default (id: string) => {
return id.match(/^[0-9a-zA-Z]{32}$/);
};

View File

@ -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([]);
}));
});

View File

@ -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() {

View File

@ -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) {

View File

@ -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(' ');