diff --git a/apps/client/src/widgets/quick_search.ts b/apps/client/src/widgets/quick_search.ts
index 2d06baafe..3c23eaea5 100644
--- a/apps/client/src/widgets/quick_search.ts
+++ b/apps/client/src/widgets/quick_search.ts
@@ -45,6 +45,13 @@ const MAX_DISPLAYED_NOTES = 15;
// TODO: Deduplicate with server.
interface QuickSearchResponse {
searchResultNoteIds: string[];
+ searchResults?: Array<{
+ notePath: string;
+ noteTitle: string;
+ notePathTitle: string;
+ highlightedNotePathTitle: string;
+ icon: string;
+ }>;
error: string;
}
@@ -115,7 +122,7 @@ export default class QuickSearchWidget extends BasicWidget {
this.$dropdownMenu.empty();
this.$dropdownMenu.append(`${t("quick-search.searching")}`);
- const { searchResultNoteIds, error } = await server.get(`quick-search/${encodeURIComponent(searchString)}`);
+ const { searchResultNoteIds, searchResults, error } = await server.get(`quick-search/${encodeURIComponent(searchString)}`);
if (error) {
let tooltip = new Tooltip(this.$searchString[0], {
@@ -129,44 +136,88 @@ export default class QuickSearchWidget extends BasicWidget {
setTimeout(() => tooltip.dispose(), 4000);
}
- const displayedNoteIds = searchResultNoteIds.slice(0, Math.min(MAX_DISPLAYED_NOTES, searchResultNoteIds.length));
-
this.$dropdownMenu.empty();
- if (displayedNoteIds.length === 0) {
- this.$dropdownMenu.append(`${t("quick-search.no-results")}`);
- }
+ // Use highlighted search results if available, otherwise fall back to basic display
+ if (searchResults && searchResults.length > 0) {
+ const displayedResults = searchResults.slice(0, Math.min(MAX_DISPLAYED_NOTES, searchResults.length));
- for (const note of await froca.getNotes(displayedNoteIds)) {
- const $link = await linkService.createLink(note.noteId, { showNotePath: true, showNoteIcon: true });
- $link.addClass("dropdown-item");
- $link.attr("tabIndex", "0");
- $link.on("click", (e) => {
- this.dropdown.hide();
+ if (displayedResults.length === 0) {
+ this.$dropdownMenu.append(`${t("quick-search.no-results")}`);
+ }
+
+ for (const result of displayedResults) {
+ const noteId = result.notePath.split("/").pop();
+ if (!noteId) continue;
+
+ const $item = $('');
+ $item.html(` ${result.highlightedNotePathTitle}`);
+
+ $item.on("click", (e) => {
+ this.dropdown.hide();
+ e.preventDefault();
+
+ const activeContext = appContext.tabManager.getActiveContext();
+ if (activeContext) {
+ activeContext.setNote(noteId);
+ }
+ });
+
+ shortcutService.bindElShortcut($item, "return", () => {
+ this.dropdown.hide();
+
+ const activeContext = appContext.tabManager.getActiveContext();
+ if (activeContext) {
+ activeContext.setNote(noteId);
+ }
+ });
+
+ this.$dropdownMenu.append($item);
+ }
+
+ if (searchResults.length > MAX_DISPLAYED_NOTES) {
+ const numRemainingResults = searchResults.length - MAX_DISPLAYED_NOTES;
+ this.$dropdownMenu.append(`${t("quick-search.more-results", { number: numRemainingResults })}`);
+ }
+ } else {
+ // Fallback to original behavior if no highlighted results
+ const displayedNoteIds = searchResultNoteIds.slice(0, Math.min(MAX_DISPLAYED_NOTES, searchResultNoteIds.length));
+
+ if (displayedNoteIds.length === 0) {
+ this.$dropdownMenu.append(`${t("quick-search.no-results")}`);
+ }
+
+ for (const note of await froca.getNotes(displayedNoteIds)) {
+ const $link = await linkService.createLink(note.noteId, { showNotePath: true, showNoteIcon: true });
+ $link.addClass("dropdown-item");
+ $link.attr("tabIndex", "0");
+ $link.on("click", (e) => {
+ this.dropdown.hide();
+
+ if (!e.target || e.target.nodeName !== "A") {
+ // click on the link is handled by link handling, but we want the whole item clickable
+ const activeContext = appContext.tabManager.getActiveContext();
+ if (activeContext) {
+ activeContext.setNote(note.noteId);
+ }
+ }
+ });
+ shortcutService.bindElShortcut($link, "return", () => {
+ this.dropdown.hide();
- if (!e.target || e.target.nodeName !== "A") {
- // click on the link is handled by link handling, but we want the whole item clickable
const activeContext = appContext.tabManager.getActiveContext();
if (activeContext) {
activeContext.setNote(note.noteId);
}
- }
- });
- shortcutService.bindElShortcut($link, "return", () => {
- this.dropdown.hide();
+ });
- const activeContext = appContext.tabManager.getActiveContext();
- if (activeContext) {
- activeContext.setNote(note.noteId);
- }
- });
+ this.$dropdownMenu.append($link);
+ }
- this.$dropdownMenu.append($link);
- }
-
- if (searchResultNoteIds.length > MAX_DISPLAYED_NOTES) {
- const numRemainingResults = searchResultNoteIds.length - MAX_DISPLAYED_NOTES;
- this.$dropdownMenu.append(`${t("quick-search.more-results", { number: numRemainingResults })}`);
+ if (searchResultNoteIds.length > MAX_DISPLAYED_NOTES) {
+ const numRemainingResults = searchResultNoteIds.length - MAX_DISPLAYED_NOTES;
+ this.$dropdownMenu.append(`${t("quick-search.more-results", { number: numRemainingResults })}`);
+ }
}
const $showInFullButton = $('').text(t("quick-search.show-in-full-search"));
diff --git a/apps/server/src/routes/api/search.ts b/apps/server/src/routes/api/search.ts
index 0e9304a07..29d75c6dc 100644
--- a/apps/server/src/routes/api/search.ts
+++ b/apps/server/src/routes/api/search.ts
@@ -52,10 +52,15 @@ function quickSearch(req: Request) {
fuzzyAttributeSearch: false
});
- const resultNoteIds = searchService.findResultsWithQuery(searchString, searchContext).map((sr) => sr.noteId);
+ // Use the same highlighting logic as autocomplete for consistency
+ const searchResults = searchService.searchNotesForAutocomplete(searchString, false);
+
+ // Extract note IDs for backward compatibility
+ const resultNoteIds = searchResults.map((result) => result.notePath.split("/").pop()).filter(Boolean) as string[];
return {
searchResultNoteIds: resultNoteIds,
+ searchResults: searchResults,
error: searchContext.getError()
};
}