search added
This commit is contained in:
@@ -547,3 +547,104 @@ func (h *Handlers) TreeAPIHandler(c *gin.Context) {
|
||||
|
||||
c.JSON(http.StatusOK, notesTree)
|
||||
}
|
||||
|
||||
// SearchHandler performs a simple full-text search across markdown and allowed text files
|
||||
// within the notes directory, honoring skipped directories.
|
||||
// GET /api/search?q=term
|
||||
func (h *Handlers) SearchHandler(c *gin.Context) {
|
||||
query := strings.TrimSpace(c.Query("q"))
|
||||
if query == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "missing query"})
|
||||
return
|
||||
}
|
||||
|
||||
// Case-insensitive search
|
||||
qLower := strings.ToLower(query)
|
||||
|
||||
type Snippet struct {
|
||||
Line int `json:"line"`
|
||||
Preview string `json:"preview"`
|
||||
}
|
||||
type Result struct {
|
||||
Path string `json:"path"`
|
||||
Type string `json:"type"` // "md" or "text"
|
||||
Snippets []Snippet `json:"snippets"`
|
||||
}
|
||||
|
||||
results := make([]Result, 0, 32)
|
||||
|
||||
// Walk the notes directory
|
||||
err := filepath.Walk(h.config.NotesDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return nil // skip on error
|
||||
}
|
||||
if info.IsDir() {
|
||||
// Compute relative dir path to check skip list
|
||||
rel, _ := filepath.Rel(h.config.NotesDir, path)
|
||||
rel = filepath.ToSlash(rel)
|
||||
if rel == "." {
|
||||
rel = ""
|
||||
}
|
||||
if utils.IsPathInSkippedDirs(rel, h.config.NotesDirSkip) {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Compute relative file path
|
||||
relPath, _ := filepath.Rel(h.config.NotesDir, path)
|
||||
relPath = filepath.ToSlash(relPath)
|
||||
|
||||
// Skip disallowed files
|
||||
ext := strings.ToLower(filepath.Ext(relPath))
|
||||
isMD := ext == ".md"
|
||||
if !isMD && !models.IsAllowedFile(relPath, h.config.AllowedFileExtensions) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read file content (limit size to prevent huge memory usage)
|
||||
data, readErr := os.ReadFile(path)
|
||||
if readErr != nil {
|
||||
return nil
|
||||
}
|
||||
content := string(data)
|
||||
contentLower := strings.ToLower(content)
|
||||
if !strings.Contains(contentLower, qLower) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Build snippets: show up to 3 matches with 2 lines of context
|
||||
lines := strings.Split(content, "\n")
|
||||
linesLower := strings.Split(contentLower, "\n")
|
||||
snippets := make([]Snippet, 0, 3)
|
||||
for i := 0; i < len(linesLower) && len(snippets) < 3; i++ {
|
||||
if strings.Contains(linesLower[i], qLower) {
|
||||
start := i - 2
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
end := i + 2
|
||||
if end >= len(lines) {
|
||||
end = len(lines) - 1
|
||||
}
|
||||
// Join preview lines
|
||||
preview := strings.Join(lines[start:end+1], "\n")
|
||||
snippets = append(snippets, Snippet{Line: i + 1, Preview: preview})
|
||||
}
|
||||
}
|
||||
|
||||
rtype := "text"
|
||||
if isMD {
|
||||
rtype = "md"
|
||||
}
|
||||
results = append(results, Result{Path: relPath, Type: rtype, Snippets: snippets})
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"query": query, "results": results})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user