9a848e8c53
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.
105 lines
2.7 KiB
Go
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)
|
|
}
|
|
}
|