diff --git a/internal/handlers/cv_cmdk.go b/internal/handlers/cv_cmdk.go
index ffd6f29..d876c01 100644
--- a/internal/handlers/cv_cmdk.go
+++ b/internal/handlers/cv_cmdk.go
@@ -15,6 +15,8 @@ type CmdKAction struct {
Title string `json:"title"`
Section string `json:"section"`
Keywords string `json:"keywords"`
+ Category string `json:"category,omitempty"` // cli, app, web, plugin, sdk, contrib
+ Icon string `json:"icon,omitempty"` // Project logo filename
}
// CmdKResponse represents the response for the CMD+K API endpoint
@@ -48,43 +50,76 @@ func (h *CVHandler) CmdKData(w http.ResponseWriter, r *http.Request) {
// Map experiences
for _, exp := range cv.Experience {
if exp.CompanyID == "" {
- continue // Skip entries without ID
+ continue
+ }
+ keywords := exp.Company + " " + exp.Position
+ for _, tech := range exp.Technologies {
+ keywords += " " + tech
+ }
+ keywords += " work job career"
+ icon := ""
+ if exp.CompanyLogo != "" {
+ icon = exp.CompanyLogo
}
response.Experiences = append(response.Experiences, CmdKAction{
ID: "exp-" + exp.CompanyID,
- Title: exp.Company,
+ Title: exp.Company + " — " + exp.Position,
Section: "Experience",
- Keywords: exp.Company + " " + exp.Position,
+ Keywords: keywords,
+ Icon: icon,
})
}
// Map projects
for _, proj := range cv.Projects {
if proj.ProjectID == "" {
- continue // Skip entries without ID
+ continue
}
title := proj.ProjectName
if title == "" {
title = proj.Title
}
+ keywords := title + " " + proj.ShortDescription
+ for _, tech := range proj.Technologies {
+ keywords += " " + tech
+ }
+ if proj.OpenSource {
+ keywords += " open source open-source oss github"
+ }
+ if proj.Category != "" {
+ keywords += " " + proj.Category
+ }
+ icon := ""
+ if proj.ProjectLogo != "" {
+ icon = proj.ProjectLogo
+ }
response.Projects = append(response.Projects, CmdKAction{
ID: "proj-" + proj.ProjectID,
Title: title,
Section: "Projects",
- Keywords: title + " " + proj.ShortDescription,
+ Keywords: keywords,
+ Category: proj.Category,
+ Icon: icon,
})
}
// Map courses
for _, course := range cv.Courses {
if course.CourseID == "" {
- continue // Skip entries without ID
+ continue
+ }
+ keywords := course.Title + " " + course.Institution
+ keywords += " course training certification"
+ icon := ""
+ if course.CourseLogo != "" {
+ icon = course.CourseLogo
}
response.Courses = append(response.Courses, CmdKAction{
ID: "course-" + course.CourseID,
Title: course.Title,
Section: "Courses",
- Keywords: course.Title + " " + course.Institution,
+ Keywords: keywords,
+ Icon: icon,
})
}
diff --git a/static/js/ninja-keys-init.js b/static/js/ninja-keys-init.js
index 37059d5..7265b81 100644
--- a/static/js/ninja-keys-init.js
+++ b/static/js/ninja-keys-init.js
@@ -111,50 +111,54 @@
}
}
- /**
- * Convert API experience entries to ninja-keys actions
- * @param {Array} experiences - Experience entries from API
- * @returns {Array} ninja-keys actions
- */
+ /** Category icon mapping */
+ const categoryIcons = {
+ cli: 'mdi:console',
+ app: 'mdi:apple',
+ web: 'mdi:web',
+ webapp: 'mdi:web',
+ plugin: 'mdi:puzzle',
+ sdk: 'mdi:package-variant',
+ contrib: 'mdi:source-pull'
+ };
+
+ /** Build icon HTML — use project logo if available, fallback to iconify */
+ function makeIcon(logoFile, folder, fallbackIcon) {
+ if (logoFile) {
+ return `
`;
+ }
+ return ``;
+ }
+
function mapExperienceActions(experiences) {
return experiences.map(exp => ({
id: exp.id,
title: exp.title,
section: exp.section,
- keywords: `${exp.keywords} work job career`.toLowerCase(),
- icon: '',
+ keywords: exp.keywords.toLowerCase(),
+ icon: makeIcon(exp.icon, 'companies', 'mdi:office-building'),
handler: () => scrollToSection(exp.id)
}));
}
- /**
- * Convert API project entries to ninja-keys actions
- * @param {Array} projects - Project entries from API
- * @returns {Array} ninja-keys actions
- */
function mapProjectActions(projects) {
return projects.map(proj => ({
id: proj.id,
title: proj.title,
section: proj.section,
- keywords: `${proj.keywords} project website app`.toLowerCase(),
- icon: '',
+ keywords: proj.keywords.toLowerCase(),
+ icon: makeIcon(proj.icon, 'projects', categoryIcons[proj.category] || 'mdi:web'),
handler: () => scrollToSection(proj.id)
}));
}
- /**
- * Convert API course entries to ninja-keys actions
- * @param {Array} courses - Course entries from API
- * @returns {Array} ninja-keys actions
- */
function mapCourseActions(courses) {
return courses.map(course => ({
id: course.id,
title: course.title,
section: course.section,
- keywords: `${course.keywords} course training certification`.toLowerCase(),
- icon: '',
+ keywords: course.keywords.toLowerCase(),
+ icon: makeIcon(course.icon, 'courses', 'mdi:school'),
handler: () => scrollToSection(course.id)
}));
}