From ec33acb52f476834547e9094ea17efbfa8eb49c1 Mon Sep 17 00:00:00 2001 From: Aviv Ben Shahar Date: Sat, 8 Jun 2024 03:16:41 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20support=20fuzzy=20search=20across=20all?= =?UTF-8?q?=20bookmarks=20=F0=9F=A5=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 16 +++++++++++++- package.json | 3 ++- src/main/bookmarks.ts | 39 ++++++++++++++++----------------- src/services/fetch-bookmarks.ts | 7 +++--- src/services/search.config.ts | 4 ++++ src/services/search.service.ts | 21 ++++++++++++++++++ 6 files changed, 64 insertions(+), 26 deletions(-) create mode 100644 src/services/search.config.ts create mode 100644 src/services/search.service.ts diff --git a/package-lock.json b/package-lock.json index 86016f4..a831695 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,8 @@ "version": "2.0.6", "license": "MIT", "dependencies": { - "fast-alfred": "^2.0.0" + "fast-alfred": "^2.0.0", + "fuse.js": "^7.0.0" }, "devDependencies": { "@commitlint/cli": "^18.4.3", @@ -7424,6 +7425,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/fuse.js": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.0.0.tgz", + "integrity": "sha512-14F4hBIxqKvD4Zz/XjDc3y94mNZN6pRv3U13Udo0lNLCWRBUsrMv2xwcF/y/Z5sV6+FQW+/ow68cHpm4sunt8Q==", + "engines": { + "node": ">=10" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -21978,6 +21987,11 @@ "integrity": "sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA==", "dev": true }, + "fuse.js": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.0.0.tgz", + "integrity": "sha512-14F4hBIxqKvD4Zz/XjDc3y94mNZN6pRv3U13Udo0lNLCWRBUsrMv2xwcF/y/Z5sV6+FQW+/ow68cHpm4sunt8Q==" + }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", diff --git a/package.json b/package.json index 591605d..36f468a 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,8 @@ "test": "jest" }, "dependencies": { - "fast-alfred": "^2.0.0" + "fast-alfred": "^2.0.0", + "fuse.js": "^7.0.0" }, "devDependencies": { "@commitlint/cli": "^18.4.3", diff --git a/src/main/bookmarks.ts b/src/main/bookmarks.ts index 8d29b33..9d92659 100644 --- a/src/main/bookmarks.ts +++ b/src/main/bookmarks.ts @@ -4,6 +4,7 @@ import { CACHE_BOOKMARKS_KEY, CACHE_TTL } from '@common/constants' import { Variables } from '@common/variables' import type { IUIBookmark } from '@models/bookmark.model' import { getBookmarks } from '@services/fetch-bookmarks' +import { searchBookmarks } from '@services/search.service' ;(async () => { const alfredClient = new FastAlfred() @@ -13,27 +14,25 @@ import { getBookmarks } from '@services/fetch-bookmarks' const profiles: string[] = profilesConfig.split(',') - const data: IUIBookmark[] = - alfredClient.cache.get(CACHE_BOOKMARKS_KEY) ?? (await getBookmarks(profiles)) - - alfredClient.cache.setWithTTL(CACHE_BOOKMARKS_KEY, data, { maxAge: CACHE_TTL }) - - const items: AlfredScriptFilter['items'] = alfredClient - .inputMatches( - data.map(({ name, url, profile }) => ({ name, url, profile })), - ({ name }) => name, - ) - .map(({ name, url, profile }) => ({ - title: name, - subtitle: `[${profile}] - ${url}`, - arg: JSON.stringify({ url, profile }), - mods: { - cmd: { - subtitle: `Open in Incognito Mode`, - arg: JSON.stringify({ url, profile, incognito: true }), - }, + let bookmarks: IUIBookmark[] | null = alfredClient.cache.get(CACHE_BOOKMARKS_KEY) + if (!bookmarks) { + bookmarks = await getBookmarks(profiles) + alfredClient.cache.setWithTTL(CACHE_BOOKMARKS_KEY, bookmarks, { maxAge: CACHE_TTL }) + } + + const filteredBookmarks = await searchBookmarks(bookmarks, alfredClient.input, sliceAmount) + + const items: AlfredScriptFilter['items'] = filteredBookmarks.map(({ name, url, profile }) => ({ + title: name, + subtitle: `[${profile}] - ${url}`, + arg: JSON.stringify({ url, profile }), + mods: { + cmd: { + subtitle: `Open in Incognito Mode`, + arg: JSON.stringify({ url, profile, incognito: true }), }, - })) + }, + })) const sliced = items.slice(0, sliceAmount) diff --git a/src/services/fetch-bookmarks.ts b/src/services/fetch-bookmarks.ts index 5ff0b14..540d291 100644 --- a/src/services/fetch-bookmarks.ts +++ b/src/services/fetch-bookmarks.ts @@ -1,7 +1,6 @@ -import { readFile } from 'fs/promises' -import { join } from 'path' -import type { IBookmark, IBookmarkRes, IUIBookmark } from '../models/bookmark.model' -import { Type } from '../models/bookmark.model' +import { readFile } from 'node:fs/promises' +import { join } from 'node:path' +import { type IBookmark, type IBookmarkRes, type IUIBookmark, Type } from '@models/bookmark.model' const BOOKMARKS_PATH = (profiles: string[]): string[] => profiles.map((profileName) => diff --git a/src/services/search.config.ts b/src/services/search.config.ts new file mode 100644 index 0000000..ef7bb33 --- /dev/null +++ b/src/services/search.config.ts @@ -0,0 +1,4 @@ +import type { IUIBookmark } from '@models/bookmark.model' + +type SearchField = keyof IUIBookmark +export const SEARCH_FIELDS_CONFIG: SearchField[] = ['name', 'url'] diff --git a/src/services/search.service.ts b/src/services/search.service.ts new file mode 100644 index 0000000..b567906 --- /dev/null +++ b/src/services/search.service.ts @@ -0,0 +1,21 @@ +import type { IUIBookmark } from '@models/bookmark.model.js' +import { SEARCH_FIELDS_CONFIG } from './search.config.js' + +export async function searchBookmarks( + bookmarks: IUIBookmark[], + searchTerm: string, + limit: number, +): Promise { + const Fuse = (await import('fuse.js/min-basic')).default + + const fuse = new Fuse(bookmarks, { + keys: SEARCH_FIELDS_CONFIG, + isCaseSensitive: false, + shouldSort: true, + threshold: 0.4, + }) + + const res = fuse.search(searchTerm, { limit }) + + return res.map((item) => item.item) +}