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) {}
}