Files
cv-site/internal/handlers/cv_cmdk.go
T
juanatsap 9a848e8c53 feat: Add CMD+K command palette with ninja-keys integration
Implement a command palette accessible via CMD+K/Ctrl+K using the ninja-keys
web component. Features include:

- New /api/cmd-k endpoint serving dynamic CV entries (experiences, projects, courses)
- Language-aware responses with 1-hour cache headers
- Scroll-to-section functionality for quick navigation
- Enhanced keyboard shortcuts modal with CMD+K documentation
- Comprehensive test coverage for API and UI interactions

Also includes cleanup of deprecated debug test files and various UI polish
improvements to contact form, themes, and action bar components.
2025-12-01 13:03:06 +00:00

105 lines
2.7 KiB
Go

package handlers
import (
"encoding/json"
"log"
"net/http"
"github.com/juanatsap/cv-site/internal/models"
)
// CmdKAction represents a single action for the ninja-keys command palette
type CmdKAction struct {
ID string `json:"id"`
Title string `json:"title"`
Section string `json:"section"`
Keywords string `json:"keywords"`
}
// CmdKResponse represents the response for the CMD+K API endpoint
type CmdKResponse struct {
Experiences []CmdKAction `json:"experiences"`
Projects []CmdKAction `json:"projects"`
Courses []CmdKAction `json:"courses"`
}
// CmdKData returns JSON data for the ninja-keys command palette
// This endpoint provides dynamic entries for experiences, projects, and courses
// that can be searched via CMD+K
func (h *CVHandler) CmdKData(w http.ResponseWriter, r *http.Request) {
// Get language from query parameter, default to "en"
lang := r.URL.Query().Get("lang")
if lang == "" {
lang = "en"
}
if lang != "en" && lang != "es" {
lang = "en"
}
// Load CV data
cv, err := models.LoadCV(lang)
if err != nil {
log.Printf("ERROR loading CV data: %v", err)
http.Error(w, "Failed to load CV data", http.StatusInternalServerError)
return
}
// Build response
response := CmdKResponse{
Experiences: make([]CmdKAction, 0, len(cv.Experience)),
Projects: make([]CmdKAction, 0, len(cv.Projects)),
Courses: make([]CmdKAction, 0, len(cv.Courses)),
}
// Map experiences
for _, exp := range cv.Experience {
if exp.CompanyID == "" {
continue // Skip entries without ID
}
response.Experiences = append(response.Experiences, CmdKAction{
ID: "exp-" + exp.CompanyID,
Title: exp.Company,
Section: "Experience",
Keywords: exp.Company + " " + exp.Position,
})
}
// Map projects
for _, proj := range cv.Projects {
if proj.ProjectID == "" {
continue // Skip entries without ID
}
title := proj.ProjectName
if title == "" {
title = proj.Title
}
response.Projects = append(response.Projects, CmdKAction{
ID: "proj-" + proj.ProjectID,
Title: title,
Section: "Projects",
Keywords: title + " " + proj.ShortDescription,
})
}
// Map courses
for _, course := range cv.Courses {
if course.CourseID == "" {
continue // Skip entries without ID
}
response.Courses = append(response.Courses, CmdKAction{
ID: "course-" + course.CourseID,
Title: course.Title,
Section: "Courses",
Keywords: course.Title + " " + course.Institution,
})
}
// Set headers and encode response
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Cache-Control", "public, max-age=3600") // Cache for 1 hour
if err := json.NewEncoder(w).Encode(response); err != nil {
log.Printf("ERROR encoding CMD+K response: %v", err)
}
}