diff --git a/open-isle-cli/src/components/SearchDropdown.vue b/open-isle-cli/src/components/SearchDropdown.vue index 7cbae58ac..012aa1966 100644 --- a/open-isle-cli/src/components/SearchDropdown.vue +++ b/open-isle-cli/src/components/SearchDropdown.vue @@ -10,7 +10,11 @@ @@ -33,7 +37,13 @@ export default { const res = await fetch(`${API_BASE_URL}/api/search/global?keyword=${encodeURIComponent(kw)}`) if (!res.ok) return [] const data = await res.json() - return data.map(r => ({ id: r.id, text: r.text, type: r.type })) + return data.map(r => ({ + id: r.id, + text: r.text, + type: r.type, + subText: r.subText, + extra: r.extra + })) } const highlight = (text) => { @@ -93,4 +103,19 @@ export default { .result-icon { opacity: 0.6; } + +.result-body { + display: flex; + flex-direction: column; +} + +.result-main { + font-weight: bold; +} + +.result-sub, +.result-extra { + font-size: 12px; + color: #666; +} diff --git a/src/main/java/com/openisle/controller/SearchController.java b/src/main/java/com/openisle/controller/SearchController.java index 1068bec3c..d9e49bf7d 100644 --- a/src/main/java/com/openisle/controller/SearchController.java +++ b/src/main/java/com/openisle/controller/SearchController.java @@ -56,6 +56,8 @@ public class SearchController { dto.setType(r.type()); dto.setId(r.id()); dto.setText(r.text()); + dto.setSubText(r.subText()); + dto.setExtra(r.extra()); return dto; }) .collect(Collectors.toList()); @@ -92,5 +94,7 @@ public class SearchController { private String type; private Long id; private String text; + private String subText; + private String extra; } } diff --git a/src/main/java/com/openisle/service/SearchService.java b/src/main/java/com/openisle/service/SearchService.java index 14a692f8b..758583379 100644 --- a/src/main/java/com/openisle/service/SearchService.java +++ b/src/main/java/com/openisle/service/SearchService.java @@ -47,15 +47,33 @@ public class SearchService { public List globalSearch(String keyword) { Stream users = searchUsers(keyword).stream() - .map(u -> new SearchResult("user", u.getId(), u.getUsername())); + .map(u -> new SearchResult( + "user", + u.getId(), + u.getUsername(), + u.getIntroduction(), + null + )); // Merge post results while removing duplicates between search by content // and search by title List mergedPosts = Stream.concat( searchPosts(keyword).stream() - .map(p -> new SearchResult("post", p.getId(), p.getTitle())), + .map(p -> new SearchResult( + "post", + p.getId(), + p.getTitle(), + p.getCategory().getName(), + extractSnippet(p.getContent(), keyword, false) + )), searchPostsByTitle(keyword).stream() - .map(p -> new SearchResult("post_title", p.getId(), p.getTitle())) + .map(p -> new SearchResult( + "post_title", + p.getId(), + p.getTitle(), + p.getCategory().getName(), + extractSnippet(p.getContent(), keyword, true) + )) ) .collect(java.util.stream.Collectors.toMap( SearchResult::id, @@ -68,11 +86,33 @@ public class SearchService { .toList(); Stream comments = searchComments(keyword).stream() - .map(c -> new SearchResult("comment", c.getId(), c.getContent())); + .map(c -> new SearchResult( + "comment", + c.getId(), + extractSnippet(c.getContent(), keyword, false), + c.getAuthor().getUsername(), + c.getPost().getTitle() + )); return Stream.concat(Stream.concat(users, mergedPosts.stream()), comments) .toList(); } - public record SearchResult(String type, Long id, String text) {} + private String extractSnippet(String content, String keyword, boolean fromStart) { + if (content == null) return ""; + if (fromStart) { + return content.length() > 50 ? content.substring(0, 50) : content; + } + String lower = content.toLowerCase(); + String kw = keyword.toLowerCase(); + int idx = lower.indexOf(kw); + if (idx == -1) { + return content.length() > 50 ? content.substring(0, 50) : content; + } + int start = Math.max(0, idx - 20); + int end = Math.min(content.length(), idx + kw.length() + 20); + return content.substring(start, end); + } + + public record SearchResult(String type, Long id, String text, String subText, String extra) {} }