Skip to content

Conversation

lexafaxine
Copy link
Contributor

@lexafaxine lexafaxine commented Jun 17, 2025

closes #1541

Changes

Add search history for web

Use local storage to store last 5 search history,
The popover showing search history will appear when input is focused.
According to the current search query implementation, history will only be saved to local storage after the input is blur, to prevent unintended saving.

Add search history for mobile

Use react-native async-storage to store last 5 search history,
Because we are showing user search history now in the search screen, user need to hit return to submit a search.
(Please refer to the demo video)

Demo video

web: https://drive.google.com/file/d/1VCfapgyx8nqwxw7JajyC8HlXiJYvjNR7/view?usp=sharing
https://drive.google.com/file/d/1sNfqTqymk3r3Mhbd9mvdOwWNMYvjobzK/view?usp=sharing
mobile: https://drive.google.com/file/d/1b5Xj5ekeFxb6IrzoWVUmZpMLWBHpCnBk/view?usp=sharing

@xuatz
Copy link
Collaborator

xuatz commented Jun 17, 2025

(nit) @lexafaxine sorry, can you update your description to something like

closes #1541

so that this PR is autolinked to the issue? ^^

@@ -0,0 +1,60 @@
import React from "react";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import React from "react";

not necessary

))}
</CommandGroup>
</CommandList>
</Command>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion(ux): might be unrelated to this feature specifically, but I think there is a ux improvement that can be done

  1. after firing the search query upon typing, we should dismiss the "Command" dialog
    2. or otherwise at least it should not show "No matching recent searches"
  2. we can also maybe consider to dismiss the dialog if the user press "enter" button
    3. seems like an intuitive thing to do, seeing that the dialog doesn't go away

search ux improvement

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @xuatz Thank u for the suggestions! They are really good points. You're right that showing "no results" when there is no result is not necessary in this situation. And regarding the close of the popover, I am considering UX like this:

  1. When there are search history that match user's typing, show the history. Close popover if there is no match.
  2. After user select one history item the popover will close. (Current behavior)
  3. No matter if there is matched history, close the popover if user press Enter, and in the same time blur the input and save the term to history.

Let me know if this sounds OK to you :)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah sounds good to me 👍

@lexafaxine lexafaxine requested a review from xuatz June 20, 2025 12:26
setIsPopoverOpen(false);
inputRef.current?.blur();
}
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion(ux): when testing the feature, i found myself instinctively trying to press down on the inputbox when there are search history options, in an attempt to navigate to those items. I think this would be a UX improvement

image

Copy link
Contributor Author

@lexafaxine lexafaxine Jun 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the suggestion! U r right so I tried changing to use the CommandInput which already has good UX out of box :) Therefore I requested a review again, please help review when available! Thanks🙏

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the ux is feeling great 💪

Comment on lines 34 to 35
<CommandEmpty>No matching recent searches.</CommandEmpty>
<CommandGroup heading="Recent Searches">
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these texts should be... what do you call this... t("label")'ed? lol

Copy link
Contributor Author

@lexafaxine lexafaxine Jun 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i18n added ^^
b33cdb0

Copy link
Collaborator

@xuatz xuatz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, but I didn't test mobile tho ^^

inputRef.current?.blur();
};

const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {

just nitpicks ^^

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess cmdk has a restriction on keyboard event's type, if only using KeyboardEvent there will be type error :(

@lexafaxine lexafaxine requested a review from xuatz June 21, 2025 01:56
Copy link
Collaborator

@xuatz xuatz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTMv2 👍

Copy link
Collaborator

@MohamedBassem MohamedBassem left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tired it and it feels great! Left a bunch of comments.

@@ -45,7 +45,7 @@ export function useDoBookmarkSearch() {
}
const id = setTimeout(() => {
doSearch(val);
}, 10);
}, 200);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason why we increased the debounce? It makes the search feels slower. If we're adding the value onBlur, then the debounce value probably doesn't make a difference?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, that change left because I tried to save search during user's typing, reverted already in 6f83a20


export function getSearchHistory(): string[] {
try {
const rawHistory = localStorage.getItem(BOOKMARK_SEARCH_HISTORY_KEY);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're keeping stuff in the user's localstorage, we'll probably need to clean it up otherwise it will leak between accounts on the same device. Which makes me wonder if it might be a good idea to store it in the database instead? If we're storing them in the database, they'll get synced across device, and it'll allow you to use mostly the same logic between the mobile and web app.

We don't need to address the localstorage idea in this PR, but if you agree, we can followup on it with a separate PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I thought about it these days, and I think it's a good idea to implement it in database so we have sync across device and prevent leak across accounts on same device. And I also realized actually there are a lot of apps are doing something like this XD.
I would like to implement it as a separate PR as improvement :) Thank you for the idea!

Comment on lines 8 to 14
const parsed = JSON.parse(rawHistory) as string[];
if (
Array.isArray(parsed) &&
parsed.every((item) => typeof item === "string")
) {
return parsed;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be clear to use a simple zod schema here instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, fixed in 6f83a20

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic in this file seems exactly the same as the one in the mobile version (except for the storage utilities). It seems to me that we can move the hooks to the shared-react package and pass in the getter and setter as inputs to the hook? This will allow you to share most of the code?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved in 4e214bf

@@ -0,0 +1,37 @@
const BOOKMARK_SEARCH_HISTORY_KEY = "karakeep_search_history";
const MAX_HISTORY_ITEMS = 5;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about we store something like 50, but by default render last 5 and only show the others as the user types?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed in 4e214bf


const handleCommandKeyDown = useCallback(
(e: React.KeyboardEvent) => {
if (e.key === "Enter") {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something is wrong with the key down handler. Pressing enter correctly goes to the selected item when the search history first show up. But if you attempt to use Enter on any filtered result, it doesn't work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch! there was actually some issue there 🙇 fixed in b5e0acf

@lexafaxine lexafaxine requested a review from MohamedBassem June 25, 2025 06:53
@MohamedBassem MohamedBassem merged commit 39fcda0 into karakeep-app:main Jul 14, 2025
5 checks passed
@MohamedBassem
Copy link
Collaborator

@lexafaxine This is such a great addition, thanks a lot for all the effort you put in this PR and addressing all of our comments. Sorry it took long to merge!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

FR: search history
3 participants