You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	All: Allow searching by note ID or using a callback URL
This commit is contained in:
		| @@ -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(' '); | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user