@rmdes/indiekit-endpoint-cv 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -140,34 +140,23 @@ export default class CvEndpoint {
140
140
 
141
141
  // CRUD for individual sections
142
142
  protectedRouter.post("/experience/add", dashboardController.addExperience);
143
- protectedRouter.post(
144
- "/experience/:index/delete",
145
- dashboardController.deleteExperience,
146
- );
143
+ protectedRouter.post("/experience/:index/edit", dashboardController.editExperience);
144
+ protectedRouter.post("/experience/:index/delete", dashboardController.deleteExperience);
147
145
 
148
146
  protectedRouter.post("/projects/add", dashboardController.addProject);
149
- protectedRouter.post(
150
- "/projects/:index/delete",
151
- dashboardController.deleteProject,
152
- );
147
+ protectedRouter.post("/projects/:index/edit", dashboardController.editProject);
148
+ protectedRouter.post("/projects/:index/delete", dashboardController.deleteProject);
153
149
 
154
150
  protectedRouter.post("/education/add", dashboardController.addEducation);
155
- protectedRouter.post(
156
- "/education/:index/delete",
157
- dashboardController.deleteEducation,
158
- );
151
+ protectedRouter.post("/education/:index/edit", dashboardController.editEducation);
152
+ protectedRouter.post("/education/:index/delete", dashboardController.deleteEducation);
159
153
 
160
154
  protectedRouter.post("/languages/add", dashboardController.addLanguage);
161
- protectedRouter.post(
162
- "/languages/:index/delete",
163
- dashboardController.deleteLanguage,
164
- );
155
+ protectedRouter.post("/languages/:index/edit", dashboardController.editLanguage);
156
+ protectedRouter.post("/languages/:index/delete", dashboardController.deleteLanguage);
165
157
 
166
158
  protectedRouter.post("/skills/add", dashboardController.addSkillCategory);
167
- protectedRouter.post(
168
- "/skills/:category/delete",
169
- dashboardController.deleteSkillCategory,
170
- );
159
+ protectedRouter.post("/skills/:category/delete", dashboardController.deleteSkillCategory);
171
160
 
172
161
  return protectedRouter;
173
162
  }
@@ -8,6 +8,7 @@ import {
8
8
  saveCvData,
9
9
  getDefaultCvData,
10
10
  addToSection,
11
+ updateInSection,
11
12
  removeFromSection,
12
13
  addSkillCategory,
13
14
  removeSkillCategory,
@@ -102,6 +103,28 @@ export const dashboardController = {
102
103
  }
103
104
  },
104
105
 
106
+ async editExperience(request, response) {
107
+ const { application } = request.app.locals;
108
+ try {
109
+ const index = Number.parseInt(request.params.index, 10);
110
+ const { title, company, location, startDate, endDate, type, description, highlights } = request.body;
111
+ await updateInSection(application, "experience", index, {
112
+ title: title || "",
113
+ company: company || "",
114
+ location: location || "",
115
+ startDate: startDate || "",
116
+ endDate: endDate || null,
117
+ type: type || "full-time",
118
+ description: description || "",
119
+ highlights: parseLines(highlights),
120
+ });
121
+ response.redirect(application.cvEndpoint + "?saved=1#experience");
122
+ } catch (error) {
123
+ console.error("[CV] Edit experience error:", error);
124
+ response.redirect(application.cvEndpoint + "?error=1#experience");
125
+ }
126
+ },
127
+
105
128
  async deleteExperience(request, response) {
106
129
  const { application } = request.app.locals;
107
130
  try {
@@ -118,12 +141,12 @@ export const dashboardController = {
118
141
  async addProject(request, response) {
119
142
  const { application } = request.app.locals;
120
143
  try {
121
- const { name, url, description, technologies, status } = request.body;
144
+ const { name, url, description, tags, technologies, status } = request.body;
122
145
  await addToSection(application, "projects", {
123
146
  name: name || "",
124
147
  url: url || "",
125
148
  description: description || "",
126
- technologies: parseCommaList(technologies),
149
+ technologies: parseCommaList(tags || technologies),
127
150
  status: status || "active",
128
151
  });
129
152
  response.redirect(application.cvEndpoint + "?saved=1#projects");
@@ -133,6 +156,25 @@ export const dashboardController = {
133
156
  }
134
157
  },
135
158
 
159
+ async editProject(request, response) {
160
+ const { application } = request.app.locals;
161
+ try {
162
+ const index = Number.parseInt(request.params.index, 10);
163
+ const { name, url, description, tags, status } = request.body;
164
+ await updateInSection(application, "projects", index, {
165
+ name: name || "",
166
+ url: url || "",
167
+ description: description || "",
168
+ technologies: parseCommaList(tags),
169
+ status: status || "active",
170
+ });
171
+ response.redirect(application.cvEndpoint + "?saved=1#projects");
172
+ } catch (error) {
173
+ console.error("[CV] Edit project error:", error);
174
+ response.redirect(application.cvEndpoint + "?error=1#projects");
175
+ }
176
+ },
177
+
136
178
  async deleteProject(request, response) {
137
179
  const { application } = request.app.locals;
138
180
  try {
@@ -164,6 +206,25 @@ export const dashboardController = {
164
206
  }
165
207
  },
166
208
 
209
+ async editEducation(request, response) {
210
+ const { application } = request.app.locals;
211
+ try {
212
+ const index = Number.parseInt(request.params.index, 10);
213
+ const { degree, institution, location, year, description } = request.body;
214
+ await updateInSection(application, "education", index, {
215
+ degree: degree || "",
216
+ institution: institution || "",
217
+ location: location || "",
218
+ year: year || "",
219
+ description: description || "",
220
+ });
221
+ response.redirect(application.cvEndpoint + "?saved=1#education");
222
+ } catch (error) {
223
+ console.error("[CV] Edit education error:", error);
224
+ response.redirect(application.cvEndpoint + "?error=1#education");
225
+ }
226
+ },
227
+
167
228
  async deleteEducation(request, response) {
168
229
  const { application } = request.app.locals;
169
230
  try {
@@ -192,6 +253,22 @@ export const dashboardController = {
192
253
  }
193
254
  },
194
255
 
256
+ async editLanguage(request, response) {
257
+ const { application } = request.app.locals;
258
+ try {
259
+ const index = Number.parseInt(request.params.index, 10);
260
+ const { name, level } = request.body;
261
+ await updateInSection(application, "languages", index, {
262
+ name: name || "",
263
+ level: level || "intermediate",
264
+ });
265
+ response.redirect(application.cvEndpoint + "?saved=1#languages");
266
+ } catch (error) {
267
+ console.error("[CV] Edit language error:", error);
268
+ response.redirect(application.cvEndpoint + "?error=1#languages");
269
+ }
270
+ },
271
+
195
272
  async deleteLanguage(request, response) {
196
273
  const { application } = request.app.locals;
197
274
  try {
package/lib/storage/cv.js CHANGED
@@ -83,6 +83,21 @@ export async function addToSection(application, section, item) {
83
83
  return saveCvData(application, data);
84
84
  }
85
85
 
86
+ /**
87
+ * Update an item in a CV array section by index
88
+ * @param {object} application - Application instance
89
+ * @param {string} section - Section name
90
+ * @param {number} index - Index to update
91
+ * @param {object} item - Updated item data
92
+ */
93
+ export async function updateInSection(application, section, index, item) {
94
+ const data = (await getCvData(application)) || getDefaultCvData();
95
+ if (Array.isArray(data[section]) && index >= 0 && index < data[section].length) {
96
+ data[section][index] = item;
97
+ }
98
+ return saveCvData(application, data);
99
+ }
100
+
86
101
  /**
87
102
  * Remove an item from a CV array section by index
88
103
  * @param {object} application - Application instance
package/locales/en.json CHANGED
@@ -10,6 +10,7 @@
10
10
  "title": "Experience",
11
11
  "description": "Work history and professional roles.",
12
12
  "add": "Add Experience",
13
+ "edit": "Edit",
13
14
  "jobTitle": "Job Title",
14
15
  "company": "Company",
15
16
  "location": "Location",
@@ -32,10 +33,11 @@
32
33
  "title": "Projects",
33
34
  "description": "Personal and professional projects.",
34
35
  "add": "Add Project",
36
+ "edit": "Edit",
35
37
  "name": "Name",
36
38
  "url": "URL",
37
39
  "descriptionField": "Description",
38
- "technologies": "Technologies (comma-separated)",
40
+ "tags": "Tags (comma-separated)",
39
41
  "status": "Status",
40
42
  "statusOptions": {
41
43
  "active": "Active",
@@ -55,6 +57,7 @@
55
57
  "title": "Education",
56
58
  "description": "Academic background and certifications.",
57
59
  "add": "Add Education",
60
+ "edit": "Edit",
58
61
  "degree": "Degree / Certificate",
59
62
  "institution": "Institution",
60
63
  "location": "Location",
@@ -65,6 +68,7 @@
65
68
  "title": "Languages",
66
69
  "description": "Languages you speak.",
67
70
  "add": "Add Language",
71
+ "edit": "Edit",
68
72
  "name": "Language",
69
73
  "level": "Level",
70
74
  "levelOptions": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rmdes/indiekit-endpoint-cv",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "CV/Resume editor endpoint for Indiekit. Manage work experience, projects, skills, education, and interests from the admin UI.",
5
5
  "keywords": [
6
6
  "indiekit",
@@ -65,6 +65,12 @@
65
65
  gap: var(--space-s, 0.75rem);
66
66
  }
67
67
 
68
+ .cv-item--has-edit {
69
+ margin-block-end: 0;
70
+ border-bottom-left-radius: 0;
71
+ border-bottom-right-radius: 0;
72
+ }
73
+
68
74
  .cv-item__info {
69
75
  flex: 1;
70
76
  min-width: 0;
@@ -96,6 +102,30 @@
96
102
  border-radius: 999px;
97
103
  }
98
104
 
105
+ .cv-item__actions {
106
+ display: flex;
107
+ gap: 0.25rem;
108
+ flex-shrink: 0;
109
+ }
110
+
111
+ .cv-edit-details {
112
+ margin-block-end: var(--space-xs, 0.5rem);
113
+ }
114
+
115
+ .cv-edit-details summary {
116
+ display: none;
117
+ }
118
+
119
+ .cv-edit-details .cv-form {
120
+ margin-block-start: 0;
121
+ border-top: none;
122
+ border-top-left-radius: 0;
123
+ border-top-right-radius: 0;
124
+ border-color: var(--color-primary, #0066cc);
125
+ border-style: solid;
126
+ border-width: 0 1px 1px 1px;
127
+ }
128
+
99
129
  .cv-form {
100
130
  background: var(--color-background, #fff);
101
131
  border: 1px dashed var(--color-outline-variant, #ddd);
@@ -151,6 +181,12 @@
151
181
  gap: var(--space-xs, 0.5rem);
152
182
  }
153
183
 
184
+ .cv-form__buttons {
185
+ display: flex;
186
+ gap: 0.5rem;
187
+ margin-block-start: var(--space-xs, 0.5rem);
188
+ }
189
+
154
190
  .cv-empty {
155
191
  color: var(--color-on-offset, #999);
156
192
  font: var(--font-caption, 0.875rem/1.4 sans-serif);
@@ -209,7 +245,7 @@
209
245
 
210
246
  {% if cv.experience.length %}
211
247
  {% for item in cv.experience %}
212
- <div class="cv-item">
248
+ <div class="cv-item cv-item--has-edit">
213
249
  <div class="cv-item__info">
214
250
  <div class="cv-item__title">{{ item.title }}</div>
215
251
  <div class="cv-item__sub">
@@ -226,10 +262,69 @@
226
262
  </div>
227
263
  {% endif %}
228
264
  </div>
229
- <form method="post" action="{{ cvEndpoint }}/experience/{{ loop.index0 }}/delete" style="margin:0">
230
- <button type="submit" class="button button--small button--secondary" onclick="return confirm('Delete this entry?')">Delete</button>
231
- </form>
265
+ <div class="cv-item__actions">
266
+ <button type="button" class="button button--small" onclick="var d=this.closest('.cv-item').nextElementSibling;d.open=!d.open">{{ __("cv.experience.edit") }}</button>
267
+ <form method="post" action="{{ cvEndpoint }}/experience/{{ loop.index0 }}/delete" style="margin:0">
268
+ <button type="submit" class="button button--small button--secondary" onclick="return confirm('Delete this entry?')">Delete</button>
269
+ </form>
270
+ </div>
232
271
  </div>
272
+ <details class="cv-edit-details">
273
+ <summary></summary>
274
+ <div class="cv-form">
275
+ <form method="post" action="{{ cvEndpoint }}/experience/{{ loop.index0 }}/edit">
276
+ <div class="field-row">
277
+ <div class="field">
278
+ <label class="field__label">{{ __("cv.experience.jobTitle") }}</label>
279
+ <input class="field__input" type="text" name="title" value="{{ item.title }}" required>
280
+ </div>
281
+ <div class="field">
282
+ <label class="field__label">{{ __("cv.experience.company") }}</label>
283
+ <input class="field__input" type="text" name="company" value="{{ item.company }}" required>
284
+ </div>
285
+ </div>
286
+ <div class="field-row">
287
+ <div class="field">
288
+ <label class="field__label">{{ __("cv.experience.location") }}</label>
289
+ <input class="field__input" type="text" name="location" value="{{ item.location }}">
290
+ </div>
291
+ <div class="field">
292
+ <label class="field__label">{{ __("cv.experience.type") }}</label>
293
+ <select class="field__input" name="type">
294
+ <option value="full-time" {% if item.type == "full-time" %}selected{% endif %}>Full-time</option>
295
+ <option value="part-time" {% if item.type == "part-time" %}selected{% endif %}>Part-time</option>
296
+ <option value="contract" {% if item.type == "contract" %}selected{% endif %}>Contract</option>
297
+ <option value="freelance" {% if item.type == "freelance" %}selected{% endif %}>Freelance</option>
298
+ <option value="volunteer" {% if item.type == "volunteer" %}selected{% endif %}>Volunteer</option>
299
+ <option value="internship" {% if item.type == "internship" %}selected{% endif %}>Internship</option>
300
+ </select>
301
+ </div>
302
+ </div>
303
+ <div class="field-row">
304
+ <div class="field">
305
+ <label class="field__label">{{ __("cv.experience.startDate") }}</label>
306
+ <input class="field__input" type="month" name="startDate" value="{{ item.startDate }}">
307
+ </div>
308
+ <div class="field">
309
+ <label class="field__label">{{ __("cv.experience.endDate") }}</label>
310
+ <input class="field__input" type="month" name="endDate" value="{{ item.endDate }}">
311
+ </div>
312
+ </div>
313
+ <div class="field">
314
+ <label class="field__label">{{ __("cv.experience.descriptionField") }}</label>
315
+ <textarea class="field__input" name="description" rows="2">{{ item.description }}</textarea>
316
+ </div>
317
+ <div class="field">
318
+ <label class="field__label">{{ __("cv.experience.highlights") }}</label>
319
+ <textarea class="field__input" name="highlights" rows="3" placeholder="One highlight per line">{{ item.highlights | join("\n") if item.highlights }}</textarea>
320
+ </div>
321
+ <div class="cv-form__buttons">
322
+ <button type="submit" class="button button--primary button--small">{{ __("cv.save") }}</button>
323
+ <button type="button" class="button button--small button--secondary" onclick="this.closest('details').open=false">Cancel</button>
324
+ </div>
325
+ </form>
326
+ </div>
327
+ </details>
233
328
  {% endfor %}
234
329
  {% else %}
235
330
  <p class="cv-empty">{{ __("cv.noData") }}</p>
@@ -300,7 +395,7 @@
300
395
 
301
396
  {% if cv.projects.length %}
302
397
  {% for item in cv.projects %}
303
- <div class="cv-item">
398
+ <div class="cv-item cv-item--has-edit">
304
399
  <div class="cv-item__info">
305
400
  <div class="cv-item__title">
306
401
  {% if item.url %}<a href="{{ item.url }}">{{ item.name }}</a>{% else %}{{ item.name }}{% endif %}
@@ -315,10 +410,53 @@
315
410
  </div>
316
411
  {% endif %}
317
412
  </div>
318
- <form method="post" action="{{ cvEndpoint }}/projects/{{ loop.index0 }}/delete" style="margin:0">
319
- <button type="submit" class="button button--small button--secondary" onclick="return confirm('Delete this entry?')">Delete</button>
320
- </form>
413
+ <div class="cv-item__actions">
414
+ <button type="button" class="button button--small" onclick="var d=this.closest('.cv-item').nextElementSibling;d.open=!d.open">{{ __("cv.projects.edit") }}</button>
415
+ <form method="post" action="{{ cvEndpoint }}/projects/{{ loop.index0 }}/delete" style="margin:0">
416
+ <button type="submit" class="button button--small button--secondary" onclick="return confirm('Delete this entry?')">Delete</button>
417
+ </form>
418
+ </div>
321
419
  </div>
420
+ <details class="cv-edit-details">
421
+ <summary></summary>
422
+ <div class="cv-form">
423
+ <form method="post" action="{{ cvEndpoint }}/projects/{{ loop.index0 }}/edit">
424
+ <div class="field-row">
425
+ <div class="field">
426
+ <label class="field__label">{{ __("cv.projects.name") }}</label>
427
+ <input class="field__input" type="text" name="name" value="{{ item.name }}" required>
428
+ </div>
429
+ <div class="field">
430
+ <label class="field__label">{{ __("cv.projects.url") }}</label>
431
+ <input class="field__input" type="url" name="url" value="{{ item.url }}" placeholder="https://...">
432
+ </div>
433
+ </div>
434
+ <div class="field">
435
+ <label class="field__label">{{ __("cv.projects.descriptionField") }}</label>
436
+ <textarea class="field__input" name="description" rows="2">{{ item.description }}</textarea>
437
+ </div>
438
+ <div class="field-row">
439
+ <div class="field">
440
+ <label class="field__label">{{ __("cv.projects.tags") }}</label>
441
+ <input class="field__input" type="text" name="tags" value="{{ item.technologies | join(', ') if item.technologies }}" placeholder="Docker, Node.js, Python">
442
+ </div>
443
+ <div class="field">
444
+ <label class="field__label">{{ __("cv.projects.status") }}</label>
445
+ <select class="field__input" name="status">
446
+ <option value="active" {% if item.status == "active" %}selected{% endif %}>Active</option>
447
+ <option value="maintained" {% if item.status == "maintained" %}selected{% endif %}>Maintained</option>
448
+ <option value="archived" {% if item.status == "archived" %}selected{% endif %}>Archived</option>
449
+ <option value="completed" {% if item.status == "completed" %}selected{% endif %}>Completed</option>
450
+ </select>
451
+ </div>
452
+ </div>
453
+ <div class="cv-form__buttons">
454
+ <button type="submit" class="button button--primary button--small">{{ __("cv.save") }}</button>
455
+ <button type="button" class="button button--small button--secondary" onclick="this.closest('details').open=false">Cancel</button>
456
+ </div>
457
+ </form>
458
+ </div>
459
+ </details>
322
460
  {% endfor %}
323
461
  {% else %}
324
462
  <p class="cv-empty">{{ __("cv.noData") }}</p>
@@ -343,8 +481,8 @@
343
481
  </div>
344
482
  <div class="field-row">
345
483
  <div class="field">
346
- <label class="field__label" for="proj-tech">{{ __("cv.projects.technologies") }}</label>
347
- <input class="field__input" type="text" id="proj-tech" name="technologies" placeholder="Docker, Node.js, Python">
484
+ <label class="field__label" for="proj-tags">{{ __("cv.projects.tags") }}</label>
485
+ <input class="field__input" type="text" id="proj-tags" name="tags" placeholder="Docker, Node.js, Python">
348
486
  </div>
349
487
  <div class="field">
350
488
  <label class="field__label" for="proj-status">{{ __("cv.projects.status") }}</label>
@@ -419,7 +557,7 @@
419
557
 
420
558
  {% if cv.education.length %}
421
559
  {% for item in cv.education %}
422
- <div class="cv-item">
560
+ <div class="cv-item cv-item--has-edit">
423
561
  <div class="cv-item__info">
424
562
  <div class="cv-item__title">{{ item.degree }}</div>
425
563
  <div class="cv-item__sub">
@@ -429,10 +567,48 @@
429
567
  <div class="cv-item__sub" style="margin-top:0.25rem">{{ item.description }}</div>
430
568
  {% endif %}
431
569
  </div>
432
- <form method="post" action="{{ cvEndpoint }}/education/{{ loop.index0 }}/delete" style="margin:0">
433
- <button type="submit" class="button button--small button--secondary" onclick="return confirm('Delete this entry?')">Delete</button>
434
- </form>
570
+ <div class="cv-item__actions">
571
+ <button type="button" class="button button--small" onclick="var d=this.closest('.cv-item').nextElementSibling;d.open=!d.open">{{ __("cv.education.edit") }}</button>
572
+ <form method="post" action="{{ cvEndpoint }}/education/{{ loop.index0 }}/delete" style="margin:0">
573
+ <button type="submit" class="button button--small button--secondary" onclick="return confirm('Delete this entry?')">Delete</button>
574
+ </form>
575
+ </div>
435
576
  </div>
577
+ <details class="cv-edit-details">
578
+ <summary></summary>
579
+ <div class="cv-form">
580
+ <form method="post" action="{{ cvEndpoint }}/education/{{ loop.index0 }}/edit">
581
+ <div class="field-row">
582
+ <div class="field">
583
+ <label class="field__label">{{ __("cv.education.degree") }}</label>
584
+ <input class="field__input" type="text" name="degree" value="{{ item.degree }}" required>
585
+ </div>
586
+ <div class="field">
587
+ <label class="field__label">{{ __("cv.education.institution") }}</label>
588
+ <input class="field__input" type="text" name="institution" value="{{ item.institution }}" required>
589
+ </div>
590
+ </div>
591
+ <div class="field-row">
592
+ <div class="field">
593
+ <label class="field__label">{{ __("cv.education.location") }}</label>
594
+ <input class="field__input" type="text" name="location" value="{{ item.location }}">
595
+ </div>
596
+ <div class="field">
597
+ <label class="field__label">{{ __("cv.education.year") }}</label>
598
+ <input class="field__input" type="text" name="year" value="{{ item.year }}" placeholder="2020-2024">
599
+ </div>
600
+ </div>
601
+ <div class="field">
602
+ <label class="field__label">{{ __("cv.education.descriptionField") }}</label>
603
+ <textarea class="field__input" name="description" rows="2">{{ item.description }}</textarea>
604
+ </div>
605
+ <div class="cv-form__buttons">
606
+ <button type="submit" class="button button--primary button--small">{{ __("cv.save") }}</button>
607
+ <button type="button" class="button button--small button--secondary" onclick="this.closest('details').open=false">Cancel</button>
608
+ </div>
609
+ </form>
610
+ </div>
611
+ </details>
436
612
  {% endfor %}
437
613
  {% else %}
438
614
  <p class="cv-empty">{{ __("cv.noData") }}</p>
@@ -482,15 +658,45 @@
482
658
 
483
659
  {% if cv.languages.length %}
484
660
  {% for item in cv.languages %}
485
- <div class="cv-item">
661
+ <div class="cv-item cv-item--has-edit">
486
662
  <div class="cv-item__info">
487
663
  <div class="cv-item__title">{{ item.name }}</div>
488
664
  <div class="cv-item__sub">{{ item.level }}</div>
489
665
  </div>
490
- <form method="post" action="{{ cvEndpoint }}/languages/{{ loop.index0 }}/delete" style="margin:0">
491
- <button type="submit" class="button button--small button--secondary" onclick="return confirm('Delete this entry?')">Delete</button>
492
- </form>
666
+ <div class="cv-item__actions">
667
+ <button type="button" class="button button--small" onclick="var d=this.closest('.cv-item').nextElementSibling;d.open=!d.open">{{ __("cv.languages.edit") }}</button>
668
+ <form method="post" action="{{ cvEndpoint }}/languages/{{ loop.index0 }}/delete" style="margin:0">
669
+ <button type="submit" class="button button--small button--secondary" onclick="return confirm('Delete this entry?')">Delete</button>
670
+ </form>
671
+ </div>
493
672
  </div>
673
+ <details class="cv-edit-details">
674
+ <summary></summary>
675
+ <div class="cv-form">
676
+ <form method="post" action="{{ cvEndpoint }}/languages/{{ loop.index0 }}/edit">
677
+ <div class="field-row">
678
+ <div class="field">
679
+ <label class="field__label">{{ __("cv.languages.name") }}</label>
680
+ <input class="field__input" type="text" name="name" value="{{ item.name }}" required>
681
+ </div>
682
+ <div class="field">
683
+ <label class="field__label">{{ __("cv.languages.level") }}</label>
684
+ <select class="field__input" name="level">
685
+ <option value="native" {% if item.level == "native" %}selected{% endif %}>Native</option>
686
+ <option value="fluent" {% if item.level == "fluent" %}selected{% endif %}>Fluent</option>
687
+ <option value="advanced" {% if item.level == "advanced" %}selected{% endif %}>Advanced</option>
688
+ <option value="intermediate" {% if item.level == "intermediate" %}selected{% endif %}>Intermediate</option>
689
+ <option value="basic" {% if item.level == "basic" %}selected{% endif %}>Basic</option>
690
+ </select>
691
+ </div>
692
+ </div>
693
+ <div class="cv-form__buttons">
694
+ <button type="submit" class="button button--primary button--small">{{ __("cv.save") }}</button>
695
+ <button type="button" class="button button--small button--secondary" onclick="this.closest('details').open=false">Cancel</button>
696
+ </div>
697
+ </form>
698
+ </div>
699
+ </details>
494
700
  {% endfor %}
495
701
  {% else %}
496
702
  <p class="cv-empty">{{ __("cv.noData") }}</p>