@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 +9 -20
- package/lib/controllers/dashboard.js +79 -2
- package/lib/storage/cv.js +15 -0
- package/locales/en.json +5 -1
- package/package.json +1 -1
- package/views/cv-dashboard.njk +224 -18
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
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
package/views/cv-dashboard.njk
CHANGED
|
@@ -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
|
-
<
|
|
230
|
-
<button type="
|
|
231
|
-
|
|
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
|
-
<
|
|
319
|
-
<button type="
|
|
320
|
-
|
|
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-
|
|
347
|
-
<input class="field__input" type="text" id="proj-
|
|
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
|
-
<
|
|
433
|
-
<button type="
|
|
434
|
-
|
|
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
|
-
<
|
|
491
|
-
<button type="
|
|
492
|
-
|
|
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>
|