feat: restore global search in app header
This commit is contained in:
@@ -209,10 +209,13 @@ type WorklogDTO struct {
|
||||
}
|
||||
|
||||
type SearchResultDTO struct {
|
||||
NodeID string `json:"nodeId"`
|
||||
Title string `json:"title"`
|
||||
Snippet string `json:"snippet"`
|
||||
Type string `json:"type"`
|
||||
NodeID string `json:"nodeId"`
|
||||
TargetID string `json:"targetId,omitempty"`
|
||||
Title string `json:"title"`
|
||||
Snippet string `json:"snippet"`
|
||||
Type string `json:"type"`
|
||||
Path string `json:"path,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
type EventDTO struct {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"verstak/internal/core/config"
|
||||
"verstak/internal/core/nodes"
|
||||
@@ -262,25 +263,140 @@ func (a *App) Search(query string) ([]SearchResultDTO, error) {
|
||||
if err := a.requireVault(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
query = strings.TrimSpace(query)
|
||||
if query == "" {
|
||||
return []SearchResultDTO{}, nil
|
||||
}
|
||||
out := []SearchResultDTO{}
|
||||
seen := map[string]bool{}
|
||||
add := func(r SearchResultDTO) {
|
||||
if r.Title == "" {
|
||||
return
|
||||
}
|
||||
key := r.Type + ":" + r.NodeID + ":" + r.TargetID + ":" + r.Title
|
||||
if seen[key] {
|
||||
return
|
||||
}
|
||||
seen[key] = true
|
||||
out = append(out, r)
|
||||
}
|
||||
results, err := a.search.Search(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]SearchResultDTO, len(results))
|
||||
for i, r := range results {
|
||||
out[i] = SearchResultDTO{
|
||||
for _, r := range results {
|
||||
path := r.Path
|
||||
if path == "" && r.NodeID != "" {
|
||||
path = a.nodes.Path(r.NodeID)
|
||||
}
|
||||
add(SearchResultDTO{
|
||||
NodeID: r.NodeID,
|
||||
Title: r.Title,
|
||||
Snippet: r.Snippet,
|
||||
Type: r.Type,
|
||||
Path: path,
|
||||
})
|
||||
}
|
||||
|
||||
if len(out) < 20 {
|
||||
nodes, err := a.nodes.Search(query, 20-len(out))
|
||||
if err == nil {
|
||||
for i := range nodes {
|
||||
if nodes[i].IsDeleted() {
|
||||
continue
|
||||
}
|
||||
add(SearchResultDTO{
|
||||
NodeID: nodes[i].ID,
|
||||
Title: nodes[i].Title,
|
||||
Type: nodes[i].Type,
|
||||
Path: a.nodes.Path(nodes[i].ID),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(out) < 20 {
|
||||
rows, err := a.db.Query(
|
||||
`SELECT l.id,l.node_id,l.title,l.url,l.hostname,COALESCE(l.note,''),n.deleted_at
|
||||
FROM links l
|
||||
LEFT JOIN nodes n ON n.id = l.node_id
|
||||
WHERE n.deleted_at IS NULL
|
||||
AND (LOWER(l.title) LIKE ? OR LOWER(l.url) LIKE ? OR LOWER(l.hostname) LIKE ? OR LOWER(COALESCE(l.note,'')) LIKE ?)
|
||||
ORDER BY l.created_at DESC
|
||||
LIMIT ?`,
|
||||
likeQuery(query), likeQuery(query), likeQuery(query), likeQuery(query), 20-len(out))
|
||||
if err == nil {
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var id, nodeID, title, url, hostname, note string
|
||||
var deletedAt interface{}
|
||||
if err := rows.Scan(&id, &nodeID, &title, &url, &hostname, ¬e, &deletedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
snippet := url
|
||||
if note != "" {
|
||||
snippet = note
|
||||
}
|
||||
add(SearchResultDTO{
|
||||
NodeID: nodeID,
|
||||
TargetID: id,
|
||||
Title: title,
|
||||
Snippet: snippet,
|
||||
Type: "link",
|
||||
Path: a.nodes.Path(nodeID),
|
||||
URL: url,
|
||||
})
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(out) < 20 {
|
||||
rows, err := a.db.Query(
|
||||
`SELECT ac.id,ac.node_id,ac.title,ac.kind,COALESCE(ac.url,''),COALESCE(ac.command,''),n.deleted_at
|
||||
FROM actions ac
|
||||
LEFT JOIN nodes n ON n.id = ac.node_id
|
||||
WHERE n.deleted_at IS NULL
|
||||
AND (LOWER(ac.title) LIKE ? OR LOWER(ac.kind) LIKE ? OR LOWER(COALESCE(ac.url,'')) LIKE ? OR LOWER(COALESCE(ac.command,'')) LIKE ?)
|
||||
ORDER BY ac.created_at DESC
|
||||
LIMIT ?`,
|
||||
likeQuery(query), likeQuery(query), likeQuery(query), likeQuery(query), 20-len(out))
|
||||
if err == nil {
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var id, nodeID, title, kind, url, command string
|
||||
var deletedAt interface{}
|
||||
if err := rows.Scan(&id, &nodeID, &title, &kind, &url, &command, &deletedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
snippet := url
|
||||
if snippet == "" {
|
||||
snippet = command
|
||||
}
|
||||
add(SearchResultDTO{
|
||||
NodeID: nodeID,
|
||||
TargetID: id,
|
||||
Title: title,
|
||||
Snippet: snippet,
|
||||
Type: "action",
|
||||
Path: a.nodes.Path(nodeID),
|
||||
})
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func likeQuery(query string) string {
|
||||
return "%" + strings.ToLower(strings.TrimSpace(query)) + "%"
|
||||
}
|
||||
|
||||
func (a *App) VerstakVersion() string {
|
||||
return "verstak-gui/v2"
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -19,8 +19,8 @@
|
||||
background: #13131f;
|
||||
}
|
||||
</style>
|
||||
<script type="module" crossorigin src="/assets/main-Co5H5J-5.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/main-BW6W9uAx.css">
|
||||
<script type="module" crossorigin src="/assets/main-2ehud8s_.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/main-N_NpVgn3.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
Reference in New Issue
Block a user