diff --git a/CHANGELOG.md b/CHANGELOG.md index b7beee73..854c28b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,26 @@ All notable changes to CV Manager will be documented in this file. Format follows [Keep a Changelog](https://keepachangelog.com/), versioning follows [Semantic Versioning](https://semver.org/). +## [1.33.3] - 2026-04-17 + +### Changed +- Section reorder overlay: when a drag is started from a section-heading handle, the floating pill now snaps under the cursor (centered on the pointer) instead of preserving the pointer→pill offset. The handle can be far from the overlay's center, which previously required dragging the cursor a long way before the pill caught up — painful on small trackpads. + +## [1.33.2] - 2026-04-17 + +### Fixed +- Section reorder overlay: the new order is now also synced into the active dataset snapshot, matching how the Settings → Sections Save button behaves. Without this, the live `section_visibility` table was updated but the active dataset kept the old order, and a page reload restored it — making the reorder appear not to persist. + +## [1.33.1] - 2026-04-17 + +### Fixed +- Section reorder overlay: the drop-position indicator now tracks the floating pill's midpoint instead of the cursor, so grabbing a pill near its top or bottom edge no longer puts the placeholder one slot off from where the pill is actually shown. + +## [1.33.0] - 2026-04-17 + +### Added +- In-place section reorder overlay. A drag handle now appears next to every section heading; activating it opens a floating overlay with each section's title over a dimmed, blurred CV. Drag pills to reorder with animated transitions, then click OK to save or Cancel to revert. Hidden sections appear dimmed so their position can still be adjusted. The Settings → Sections & Headlines panel is unchanged and remains available. + ## [1.32.0] - 2026-04-17 ### Added diff --git a/package-lock.json b/package-lock.json index 6d494a69..566e1bf1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cv-manager", - "version": "1.32.0", + "version": "1.33.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cv-manager", - "version": "1.32.0", + "version": "1.33.3", "dependencies": { "archiver": "^7.0.1", "better-sqlite3": "^9.4.3", diff --git a/package.json b/package.json index 96f69f1d..4e457ef7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cv-manager", - "version": "1.32.0", + "version": "1.33.3", "description": "Professional CV Management System", "main": "src/server.js", "scripts": { diff --git a/public/index.html b/public/index.html index 4bb6bbed..c4fbc5f7 100644 --- a/public/index.html +++ b/public/index.html @@ -168,6 +168,9 @@

Loading...

+

Professional Summary

@@ -184,6 +187,9 @@

Professional Summary

+

Career Timeline

@@ -200,6 +206,9 @@

Career Timeline

+

Work Experience

@@ -217,6 +226,9 @@

Work Experience

+

Certifications

@@ -234,6 +246,9 @@

Certifications

+

Education

@@ -251,6 +266,9 @@

Education

+

Skills & Expertise

@@ -268,6 +286,9 @@

Skills & Expertise

+

Featured Projects

@@ -726,6 +747,18 @@

ATS-Friendly Document

+ + + diff --git a/public/shared/admin.css b/public/shared/admin.css index 83ca8f7e..adf0cfbd 100644 --- a/public/shared/admin.css +++ b/public/shared/admin.css @@ -2739,3 +2739,221 @@ } .ats-pdf-scale-row input[type="range"] { width: 100px; } } + +/* ========================================================= + Section Reorder Overlay + ========================================================= */ + +.section-reorder-handle { + position: absolute; + top: 20px; + left: -14px; + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + border: none; + background: var(--white); + color: var(--gray-500); + border-radius: 50%; + cursor: grab; + box-shadow: var(--shadow-md); + opacity: 0; + transform: translateX(6px) scale(0.85); + transition: opacity .18s ease, transform .18s ease, color .18s ease, background .18s ease; + z-index: 2; + padding: 0; +} + +.section:hover .section-reorder-handle, +.section-reorder-handle:focus-visible { + opacity: 1; + transform: translateX(0) scale(1); +} + +.section-reorder-handle:hover { + color: var(--primary); + background: var(--light); +} + +.section-reorder-handle:active { + cursor: grabbing; + transform: scale(0.95); +} + +.section-reorder-handle .material-symbols-outlined { + font-size: 18px; + pointer-events: none; +} + +body.reorder-active .section-reorder-handle { opacity: 0; pointer-events: none; } + +.reorder-overlay { + position: fixed; + inset: 0; + display: none; + align-items: center; + justify-content: center; + z-index: 2500; + background: rgba(0, 26, 77, 0); + backdrop-filter: blur(0); + -webkit-backdrop-filter: blur(0); + transition: background .28s ease, backdrop-filter .28s ease, -webkit-backdrop-filter .28s ease; + padding: 24px; +} + +.reorder-overlay.active { + display: flex; + background: rgba(0, 26, 77, 0.55); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); +} + +.reorder-dialog { + display: flex; + flex-direction: column; + gap: 18px; + width: 100%; + max-width: 440px; + max-height: 90vh; + opacity: 0; + transition: opacity .28s ease .05s; +} + +.reorder-overlay.active .reorder-dialog { + opacity: 1; +} + +.reorder-hint { + text-align: center; + color: var(--white); + font-size: 13px; + font-weight: 500; + opacity: 0.85; + text-shadow: 0 1px 6px rgba(0, 0, 0, 0.25); +} + +.reorder-list { + display: flex; + flex-direction: column; + gap: 10px; + overflow-y: auto; + padding: 4px 2px; +} + +.reorder-pill { + display: flex; + align-items: center; + gap: 12px; + padding: 14px 18px; + background: var(--white); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-md); + color: var(--primary); + font-size: 16px; + font-weight: 600; + cursor: grab; + user-select: none; + touch-action: none; + will-change: transform; + animation: reorderPillIn .32s cubic-bezier(.2,.8,.2,1) both; + transition: box-shadow .2s ease, opacity .2s ease; +} + +.reorder-pill:hover { + box-shadow: var(--shadow-lg); +} + +.reorder-pill:active { + cursor: grabbing; +} + +.reorder-pill.dragging { + cursor: grabbing; + box-shadow: 0 18px 40px rgba(0, 0, 0, 0.25), 0 4px 10px rgba(0, 0, 0, 0.15); + transform: scale(1.04); + animation: none; + transition: none; + background: var(--white); +} + +.reorder-pill--hidden { + opacity: 0.55; +} + +.reorder-pill-grip { + font-size: 20px !important; + color: var(--gray-400); + flex-shrink: 0; +} + +.reorder-pill-label { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.reorder-pill-hidden-icon { + font-size: 18px !important; + color: var(--gray-400); + flex-shrink: 0; +} + +.reorder-placeholder { + border: 2px dashed rgba(255, 255, 255, 0.45); + border-radius: var(--radius-lg); + background: rgba(255, 255, 255, 0.08); + transition: height .2s ease; +} + +.reorder-footer { + display: flex; + justify-content: flex-end; + gap: 10px; + padding-top: 4px; +} + +.reorder-footer .btn { + min-width: 96px; +} + +.reorder-footer .btn-ghost { + background: rgba(255, 255, 255, 0.14); + color: var(--white); + border: 1px solid rgba(255, 255, 255, 0.3); +} + +.reorder-footer .btn-ghost:hover { + background: rgba(255, 255, 255, 0.24); +} + +@keyframes reorderPillIn { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: none; } +} + +.reorder-pill--hidden { + animation-name: reorderPillInHidden; +} + +@keyframes reorderPillInHidden { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 0.55; transform: none; } +} + +@media (max-width: 600px) { + .section-reorder-handle { + left: 6px; + top: 16px; + } + .reorder-dialog { max-width: 100%; } + .reorder-pill { padding: 12px 14px; font-size: 15px; } +} + +@media print { + .reorder-overlay, + .section-reorder-handle { display: none !important; } +} diff --git a/public/shared/admin.js b/public/shared/admin.js index 880922e9..30e716a3 100644 --- a/public/shared/admin.js +++ b/public/shared/admin.js @@ -756,6 +756,9 @@ function renderCustomSection(section) { return `
+

${escapeHtml(section.name)}

@@ -4859,3 +4862,351 @@ async function downloadAtsPdf() { btn.disabled = false; } } + +// =========================== +// Section Reorder Overlay +// =========================== + +let reorderState = null; + +function reorderSectionDisplayName(section) { + const isCustom = section.name && section.name !== section.default_name; + return isCustom ? section.name : getTranslatedSectionName(section.key, section.name || section.default_name); +} + +function renderReorderPills() { + const list = document.getElementById('reorderList'); + if (!list || !reorderState) return; + list.innerHTML = reorderState.order.map((section, index) => { + const hidden = section.visible === false; + const label = reorderSectionDisplayName(section); + const hiddenBadge = hidden + ? '' + : ''; + return ` +
+ + ${escapeHtml(label)} + ${hiddenBadge} +
+ `; + }).join(''); + attachReorderPillListeners(); +} + +function attachReorderPillListeners() { + document.querySelectorAll('#reorderList .reorder-pill').forEach(pill => { + pill.addEventListener('pointerdown', onReorderPillPointerDown); + }); +} + +function onReorderPillPointerDown(e) { + if (e.button !== undefined && e.button !== 0) return; + const pill = e.currentTarget; + const key = pill.dataset.key; + beginReorderDrag(key, e); +} + +function openReorderOverlay(grabbedKey, pointerEvent) { + if (!Array.isArray(sectionOrder) || sectionOrder.length === 0) return; + const overlay = document.getElementById('reorderOverlay'); + if (!overlay) return; + + // Snapshot so Cancel can revert + const snapshot = sectionOrder.map(s => ({ ...s })); + reorderState = { + order: sectionOrder.map(s => ({ ...s })), + snapshot, + dragging: null, + isOpen: true + }; + + renderReorderPills(); + + overlay.classList.add('active'); + overlay.setAttribute('aria-hidden', 'false'); + document.body.classList.add('reorder-active'); + + // Wire footer buttons once + const okBtn = document.getElementById('reorderOkBtn'); + const cancelBtn = document.getElementById('reorderCancelBtn'); + if (okBtn && !okBtn.dataset.bound) { + okBtn.addEventListener('click', confirmReorder); + okBtn.dataset.bound = '1'; + } + if (cancelBtn && !cancelBtn.dataset.bound) { + cancelBtn.addEventListener('click', cancelReorder); + cancelBtn.dataset.bound = '1'; + } + + // Esc to cancel, backdrop click to cancel + document.addEventListener('keydown', onReorderKeyDown); + overlay.addEventListener('pointerdown', onReorderOverlayPointerDown); + + // If a handle triggered the open, kick off a drag on that pill immediately. + // Suppress the pill's entrance animation so getBoundingClientRect matches final layout. + // Snap the pill under the cursor so the user doesn't have to chase it from + // the section heading handle (potentially far from the overlay center). + if (grabbedKey && pointerEvent) { + const pill = document.querySelector(`#reorderList .reorder-pill[data-key="${grabbedKey}"]`); + if (pill) pill.style.animation = 'none'; + beginReorderDrag(grabbedKey, pointerEvent, { snapPillToCursor: true }); + } +} + +function onReorderOverlayPointerDown(e) { + // Clicking the backdrop (the overlay itself, not the dialog) cancels + if (e.target && e.target.id === 'reorderOverlay') { + cancelReorder(); + } +} + +function onReorderKeyDown(e) { + if (e.key === 'Escape') { + cancelReorder(); + } +} + +function beginReorderDrag(key, pointerEvent, opts = {}) { + if (!reorderState) return; + const pill = document.querySelector(`#reorderList .reorder-pill[data-key="${key}"]`); + if (!pill) return; + + // End any previous drag cleanly + if (reorderState.dragging) endReorderDrag(); + + const rect = pill.getBoundingClientRect(); + const pointerX = pointerEvent.clientX ?? (rect.left + rect.width / 2); + const pointerY = pointerEvent.clientY ?? (rect.top + rect.height / 2); + + // If the pointer is outside the pill (e.g. drag initiated from the + // section-heading handle on the left of the page), center the pill on + // the cursor instead of preserving the pointer→pill offset. Otherwise + // the pill floats far from the cursor and is hard to reach on a small + // trackpad. Caller can also force this via opts.snapPillToCursor. + const pointerOutsidePill = + pointerX < rect.left || pointerX > rect.right || + pointerY < rect.top || pointerY > rect.bottom; + const snap = opts.snapPillToCursor || pointerOutsidePill; + + // Placeholder keeps the slot while the pill floats + const placeholder = document.createElement('div'); + placeholder.className = 'reorder-placeholder'; + placeholder.style.height = `${rect.height}px`; + pill.parentNode.insertBefore(placeholder, pill); + + pill.classList.add('dragging'); + pill.style.width = `${rect.width}px`; + pill.style.height = `${rect.height}px`; + pill.style.position = 'fixed'; + pill.style.left = `${rect.left}px`; + pill.style.top = `${rect.top}px`; + pill.style.zIndex = '10'; + pill.style.pointerEvents = 'none'; + + reorderState.dragging = { + key, + pill, + placeholder, + offsetX: snap ? rect.width / 2 : pointerX - rect.left, + offsetY: snap ? rect.height / 2 : pointerY - rect.top, + pointerId: pointerEvent.pointerId + }; + + updateDraggingPillPosition(pointerX, pointerY); + + try { + if (pointerEvent.pointerId !== undefined) { + document.body.setPointerCapture?.(pointerEvent.pointerId); + } + } catch (_) { /* ignore */ } + + document.addEventListener('pointermove', onReorderPointerMove); + document.addEventListener('pointerup', onReorderPointerUp); + document.addEventListener('pointercancel', onReorderPointerUp); +} + +function updateDraggingPillPosition(clientX, clientY) { + const d = reorderState?.dragging; + if (!d) return; + d.pill.style.left = `${clientX - d.offsetX}px`; + d.pill.style.top = `${clientY - d.offsetY}px`; +} + +function onReorderPointerMove(e) { + const d = reorderState?.dragging; + if (!d) return; + e.preventDefault(); + updateDraggingPillPosition(e.clientX, e.clientY); + + // Find the placeholder's target index based on pointer Y vs. other pills + const list = document.getElementById('reorderList'); + if (!list) return; + const siblings = Array.from(list.children).filter(el => + (el.classList.contains('reorder-pill') && el !== d.pill) || + el.classList.contains('reorder-placeholder') + ); + + // Target index follows the floating pill's midpoint, not the cursor's Y + // (the pointer typically grabs the pill off-center). + const pillRect = d.pill.getBoundingClientRect(); + const pillMidY = pillRect.top + pillRect.height / 2; + + let insertBefore = null; + for (const sib of siblings) { + if (sib === d.placeholder) continue; + const rect = sib.getBoundingClientRect(); + if (pillMidY < rect.top + rect.height / 2) { + insertBefore = sib; + break; + } + } + + // FLIP animation: measure before mutation + const pills = Array.from(list.querySelectorAll('.reorder-pill')).filter(p => p !== d.pill); + const firstRects = new Map(pills.map(p => [p, p.getBoundingClientRect()])); + + if (insertBefore) { + if (d.placeholder.nextSibling !== insertBefore) { + list.insertBefore(d.placeholder, insertBefore); + } + } else { + if (list.lastElementChild !== d.placeholder) { + list.appendChild(d.placeholder); + } + } + + // Measure after, animate the delta + pills.forEach(p => { + const first = firstRects.get(p); + const last = p.getBoundingClientRect(); + const dy = first.top - last.top; + if (dy) { + p.style.transition = 'none'; + p.style.transform = `translateY(${dy}px)`; + requestAnimationFrame(() => { + p.style.transition = 'transform .22s cubic-bezier(.2,.8,.2,1)'; + p.style.transform = ''; + }); + } + }); +} + +function onReorderPointerUp(e) { + const d = reorderState?.dragging; + if (!d) return; + + // Commit new position: replace placeholder with pill + const list = document.getElementById('reorderList'); + if (list && d.placeholder.parentNode === list) { + list.insertBefore(d.pill, d.placeholder); + } + d.placeholder.remove(); + + // Clear inline floating styles so the pill sits back in flow + d.pill.classList.remove('dragging'); + d.pill.style.position = ''; + d.pill.style.left = ''; + d.pill.style.top = ''; + d.pill.style.width = ''; + d.pill.style.height = ''; + d.pill.style.zIndex = ''; + d.pill.style.pointerEvents = ''; + + try { + if (d.pointerId !== undefined) { + document.body.releasePointerCapture?.(d.pointerId); + } + } catch (_) { /* ignore */ } + + endReorderDrag(); + + // Rebuild reorderState.order from DOM order + if (list && reorderState) { + const keys = Array.from(list.querySelectorAll('.reorder-pill')).map(p => p.dataset.key); + const lookup = new Map(reorderState.order.map(s => [s.key, s])); + reorderState.order = keys.map(k => lookup.get(k)).filter(Boolean); + // Refresh data-index + animation-delay staleness by updating indices + Array.from(list.querySelectorAll('.reorder-pill')).forEach((p, i) => { + p.dataset.index = String(i); + }); + } +} + +function endReorderDrag() { + document.removeEventListener('pointermove', onReorderPointerMove); + document.removeEventListener('pointerup', onReorderPointerUp); + document.removeEventListener('pointercancel', onReorderPointerUp); + if (reorderState) reorderState.dragging = null; +} + +function closeReorderOverlay() { + const overlay = document.getElementById('reorderOverlay'); + if (!overlay) return; + overlay.classList.remove('active'); + overlay.setAttribute('aria-hidden', 'true'); + document.body.classList.remove('reorder-active'); + document.removeEventListener('keydown', onReorderKeyDown); + overlay.removeEventListener('pointerdown', onReorderOverlayPointerDown); + endReorderDrag(); + reorderState = null; +} + +function cancelReorder() { + closeReorderOverlay(); +} + +async function confirmReorder() { + if (!reorderState) { + closeReorderOverlay(); + return; + } + const newOrder = reorderState.order; + + // No change — just close + const snap = reorderState.snapshot; + const unchanged = newOrder.length === snap.length && + newOrder.every((s, i) => s.key === snap[i].key); + if (unchanged) { + closeReorderOverlay(); + return; + } + + const sections = newOrder.map((s, index) => ({ + key: s.key, + visible: s.visible, + print_visible: s.print_visible !== false, + sort_order: index, + display_name: (s.name && s.name !== s.default_name) ? s.name : null + })); + + try { + await api('/api/sections/order', { method: 'PUT', body: { sections } }); + // Update globals so subsequent renders see the new order + sectionOrder = newOrder.map(s => ({ ...s })); + settingsSectionOrder = newOrder.map(s => ({ ...s })); + reorderSectionElements(); + // Persist into the active dataset snapshot; otherwise a page reload + // restores the dataset's saved order and the change appears lost. + autoSaveActiveDataset(); + toast(t('toast.settings_saved'), 'success'); + } catch (err) { + toast(t('toast.settings_failed'), 'error'); + } finally { + closeReorderOverlay(); + } +} + +// Delegated handle listener: press-and-drag on any .section-reorder-handle +document.addEventListener('pointerdown', (e) => { + const handle = e.target.closest && e.target.closest('.section-reorder-handle'); + if (!handle) return; + if (e.button !== undefined && e.button !== 0) return; + const key = handle.dataset.sectionKey; + if (!key) return; + e.preventDefault(); + openReorderOverlay(key, e); +}); diff --git a/public/shared/i18n/de.json b/public/shared/i18n/de.json index 0ee71c93..b620998c 100644 --- a/public/shared/i18n/de.json +++ b/public/shared/i18n/de.json @@ -49,14 +49,17 @@ "action.add_project": "Projekt hinzufügen", "action.move_up": "Nach oben", "action.move_down": "Nach unten", + "action.reorder_sections": "Abschnitte neu anordnen", "btn.save": "Speichern", "btn.cancel": "Abbrechen", "btn.delete": "Löschen", "btn.close": "Schließen", "btn.save_changes": "Änderungen speichern", "btn.done": "Fertig", + "btn.ok": "OK", "btn.add_item": "Eintrag hinzufügen", "btn.delete_section": "Abschnitt löschen", + "reorder.hint": "Abschnitte ziehen, um sie neu anzuordnen. Klicken Sie auf OK zum Speichern.", "modal.edit": "Bearbeiten", "modal.edit_experience": "Erfahrung bearbeiten", "modal.add_experience": "Erfahrung hinzufügen", diff --git a/public/shared/i18n/en.json b/public/shared/i18n/en.json index 0e3c11ac..149d3c6e 100644 --- a/public/shared/i18n/en.json +++ b/public/shared/i18n/en.json @@ -54,6 +54,7 @@ "action.add_project": "Add Project", "action.move_up": "Move Up", "action.move_down": "Move Down", + "action.reorder_sections": "Reorder sections", "btn.save": "Save", "btn.cancel": "Cancel", @@ -61,9 +62,12 @@ "btn.close": "Close", "btn.save_changes": "Save Changes", "btn.done": "Done", + "btn.ok": "OK", "btn.add_item": "Add Item", "btn.delete_section": "Delete Section", + "reorder.hint": "Drag sections to reorder. Click OK to save.", + "modal.edit": "Edit", "modal.edit_experience": "Edit Experience", "modal.add_experience": "Add Experience", diff --git a/public/shared/i18n/es.json b/public/shared/i18n/es.json index e6f025ee..d7eb951a 100644 --- a/public/shared/i18n/es.json +++ b/public/shared/i18n/es.json @@ -49,14 +49,17 @@ "action.add_project": "Añadir proyecto", "action.move_up": "Mover arriba", "action.move_down": "Mover abajo", + "action.reorder_sections": "Reordenar secciones", "btn.save": "Guardar", "btn.cancel": "Cancelar", "btn.delete": "Eliminar", "btn.close": "Cerrar", "btn.save_changes": "Guardar cambios", "btn.done": "Hecho", + "btn.ok": "OK", "btn.add_item": "Añadir elemento", "btn.delete_section": "Eliminar sección", + "reorder.hint": "Arrastre las secciones para reordenarlas. Haga clic en OK para guardar.", "modal.edit": "Editar", "modal.edit_experience": "Editar experiencia", "modal.add_experience": "Añadir experiencia", diff --git a/public/shared/i18n/fr.json b/public/shared/i18n/fr.json index ada99de0..f656422d 100644 --- a/public/shared/i18n/fr.json +++ b/public/shared/i18n/fr.json @@ -49,14 +49,17 @@ "action.add_project": "Ajouter un projet", "action.move_up": "Déplacer vers le haut", "action.move_down": "Déplacer vers le bas", + "action.reorder_sections": "Réorganiser les sections", "btn.save": "Enregistrer", "btn.cancel": "Annuler", "btn.delete": "Supprimer", "btn.close": "Fermer", "btn.save_changes": "Enregistrer les modifications", "btn.done": "Terminé", + "btn.ok": "OK", "btn.add_item": "Ajouter un élément", "btn.delete_section": "Supprimer la section", + "reorder.hint": "Glissez les sections pour les réorganiser. Cliquez sur OK pour enregistrer.", "modal.edit": "Modifier", "modal.edit_experience": "Modifier l'expérience", "modal.add_experience": "Ajouter une expérience", diff --git a/public/shared/i18n/it.json b/public/shared/i18n/it.json index 33928ff4..ee74c61d 100644 --- a/public/shared/i18n/it.json +++ b/public/shared/i18n/it.json @@ -49,14 +49,17 @@ "action.add_project": "Aggiungi progetto", "action.move_up": "Sposta su", "action.move_down": "Sposta giù", + "action.reorder_sections": "Riordina sezioni", "btn.save": "Salva", "btn.cancel": "Annulla", "btn.delete": "Elimina", "btn.close": "Chiudi", "btn.save_changes": "Salva modifiche", "btn.done": "Fatto", + "btn.ok": "OK", "btn.add_item": "Aggiungi elemento", "btn.delete_section": "Elimina sezione", + "reorder.hint": "Trascina le sezioni per riordinarle. Fai clic su OK per salvare.", "modal.edit": "Modifica", "modal.edit_experience": "Modifica esperienza", "modal.add_experience": "Aggiungi esperienza", diff --git a/public/shared/i18n/nl.json b/public/shared/i18n/nl.json index 584db0ef..78b7151a 100644 --- a/public/shared/i18n/nl.json +++ b/public/shared/i18n/nl.json @@ -49,14 +49,17 @@ "action.add_project": "Project toevoegen", "action.move_up": "Omhoog", "action.move_down": "Omlaag", + "action.reorder_sections": "Secties herschikken", "btn.save": "Opslaan", "btn.cancel": "Annuleren", "btn.delete": "Verwijderen", "btn.close": "Sluiten", "btn.save_changes": "Wijzigingen opslaan", "btn.done": "Gereed", + "btn.ok": "OK", "btn.add_item": "Item toevoegen", "btn.delete_section": "Sectie verwijderen", + "reorder.hint": "Sleep secties om ze te herschikken. Klik op OK om op te slaan.", "modal.edit": "Bewerken", "modal.edit_experience": "Ervaring bewerken", "modal.add_experience": "Ervaring toevoegen", diff --git a/public/shared/i18n/pt.json b/public/shared/i18n/pt.json index 57325914..c2b421b6 100644 --- a/public/shared/i18n/pt.json +++ b/public/shared/i18n/pt.json @@ -49,14 +49,17 @@ "action.add_project": "Adicionar Projeto", "action.move_up": "Mover para cima", "action.move_down": "Mover para baixo", + "action.reorder_sections": "Reordenar secções", "btn.save": "Guardar", "btn.cancel": "Cancelar", "btn.delete": "Eliminar", "btn.close": "Fechar", "btn.save_changes": "Guardar Alterações", "btn.done": "Concluído", + "btn.ok": "OK", "btn.add_item": "Adicionar Item", "btn.delete_section": "Eliminar Secção", + "reorder.hint": "Arraste as secções para reordenar. Clique em OK para guardar.", "modal.edit": "Editar", "modal.edit_experience": "Editar Experiência", "modal.add_experience": "Adicionar Experiência", diff --git a/public/shared/i18n/zh.json b/public/shared/i18n/zh.json index 765f20b1..2fb5926e 100644 --- a/public/shared/i18n/zh.json +++ b/public/shared/i18n/zh.json @@ -49,14 +49,17 @@ "action.add_project": "添加项目", "action.move_up": "上移", "action.move_down": "下移", + "action.reorder_sections": "重新排序板块", "btn.save": "保存", "btn.cancel": "取消", "btn.delete": "删除", "btn.close": "关闭", "btn.save_changes": "保存更改", "btn.done": "完成", + "btn.ok": "确定", "btn.add_item": "添加条目", "btn.delete_section": "删除板块", + "reorder.hint": "拖动板块以重新排序。点击确定保存。", "modal.edit": "编辑", "modal.edit_experience": "编辑工作经历", "modal.add_experience": "添加工作经历", diff --git a/version.json b/version.json index b491dfa2..2b107cd7 100644 --- a/version.json +++ b/version.json @@ -1,4 +1,4 @@ { - "version": "1.32.0", + "version": "1.33.3", "changelog": "https://github.com/vincentmakes/cv-manager/blob/main/CHANGELOG.md" }