From 8e69dbde52d84340ffa86a38aa40a5c6d53ff2f6 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 17 Apr 2026 20:00:30 +0000 Subject: [PATCH 01/15] Custom sections: inline create + rename/delete on section header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Creation moves from Settings → Custom Sections to a hover-activated "+ Create custom section" placeholder that appears below every section. Clicking it opens the create dialog and inserts the new section right after the hovered one. Each custom section header gains trash and rename (title-only) buttons next to the visibility toggle, removing the round-trip through Settings for day-to-day management. Layout conversion after creation is dropped and the Custom Sections settings tab is removed; the inline controls cover every path. https://claude.ai/code/session_015sL8STYmL3AzbjbupUReug --- CHANGELOG.md | 10 ++ docs/guide/custom-sections.de.md | 19 ++- docs/guide/custom-sections.es.md | 19 ++- docs/guide/custom-sections.fr.md | 19 ++- docs/guide/custom-sections.it.md | 19 ++- docs/guide/custom-sections.md | 19 ++- docs/guide/custom-sections.nl.md | 19 ++- docs/guide/custom-sections.pt.md | 19 ++- docs/guide/custom-sections.zh.md | 19 ++- package-lock.json | 4 +- package.json | 2 +- public/index.html | 41 ++++--- public/shared/admin.js | 191 ++++++++++++++++++++++--------- public/shared/i18n/de.json | 6 +- public/shared/i18n/en.json | 6 +- public/shared/i18n/es.json | 6 +- public/shared/i18n/fr.json | 6 +- public/shared/i18n/it.json | 6 +- public/shared/i18n/nl.json | 6 +- public/shared/i18n/pt.json | 6 +- public/shared/i18n/zh.json | 6 +- public/shared/styles.css | 43 +++++++ version.json | 2 +- 23 files changed, 346 insertions(+), 147 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 854c28b0..2b21e90f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ 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.34.0] - 2026-04-17 + +### Added +- Inline custom section creation: hovering below any section reveals a dashed "+ Create custom section" placeholder; clicking it opens the creation dialog and inserts the new section directly after the hovered one. No more round-trip through Settings. +- Inline rename and delete controls on every custom section header (pencil + trash icons), alongside the existing visibility toggle. + +### Removed +- Custom Sections tab in Settings — the inline placeholder and header controls cover every path that tab used to provide. +- Ability to change a custom section's layout type after creation. Layout is chosen once on creation; if a different layout is needed, create a new section and move the items over. + ## [1.33.3] - 2026-04-17 ### Changed diff --git a/docs/guide/custom-sections.de.md b/docs/guide/custom-sections.de.md index 28370807..8e4bd1ce 100644 --- a/docs/guide/custom-sections.de.md +++ b/docs/guide/custom-sections.de.md @@ -4,12 +4,21 @@ Mit benutzerdefinierten Abschnitten können Sie beliebige Inhalte hinzufügen, d ## Einen benutzerdefinierten Abschnitt erstellen -1. Öffnen Sie **Einstellungen** → Tab **Benutzerdefinierte Abschnitte** -2. Klicken Sie auf **Benutzerdefinierten Abschnitt hinzufügen** +1. Bewegen Sie den Mauszeiger direkt unter einen vorhandenen Abschnitt in Ihrem Lebenslauf — ein gestrichelter Platzhalter **+ Eigenen Abschnitt erstellen** erscheint +2. Klicken Sie auf den Platzhalter, um den Erstellungsdialog zu öffnen 3. Geben Sie einen Abschnittsnamen ein 4. Wählen Sie einen Layout-Typ (siehe unten) -5. Wählen Sie optional ein **Abschnittssymbol** (Stern, Buch, Link, Globus, Herz, Auszeichnung, Aktentasche) — wird neben der Überschrift angezeigt -6. Speichern — der neue Abschnitt erscheint in Ihrem Lebenslauf +5. Speichern — der neue Abschnitt wird direkt unter dem Abschnitt eingefügt, über dem Sie den Platzhalter geöffnet haben + +## Einen benutzerdefinierten Abschnitt verwalten + +Jeder benutzerdefinierte Abschnitt hat drei Symbolschaltflächen in seiner Überschrift: + +- **Löschen** (Papierkorb-Symbol) — entfernt den Abschnitt und alle seine Elemente nach Bestätigung +- **Umbenennen** (Stift-Symbol) — öffnet einen kleinen Dialog, um den Titel des Abschnitts zu ändern +- **Sichtbarkeit** (Augen-Symbol) — schaltet ein, ob der Abschnitt im Lebenslauf angezeigt wird + +Der Layout-Typ eines Abschnitts wird bei der Erstellung gewählt und kann danach nicht mehr geändert werden — wenn Sie ein anderes Layout benötigen, erstellen Sie einen neuen Abschnitt und verschieben Sie die Elemente dorthin. ## Layout-Typen @@ -59,4 +68,4 @@ Für Raster-, Listen-, Karten- und Aufzählungs-Layouts hat jedes Element ein Ko Klicken Sie auf **Elemente verwalten** bei einem beliebigen benutzerdefinierten Abschnitt, um Elemente innerhalb dieses Abschnitts hinzuzufügen, zu bearbeiten, neu anzuordnen oder zu löschen. !!! note "Hinweis" - Es gibt keine feste Obergrenze für benutzerdefinierte Abschnitte. Erstellen Sie so viele, wie Sie benötigen. Sie können auch den Layout-Typ eines benutzerdefinierten Abschnitts nachträglich ändern — bearbeiten Sie einfach den Abschnitt und wählen Sie ein anderes Layout. + Es gibt keine feste Obergrenze für benutzerdefinierte Abschnitte. Erstellen Sie so viele, wie Sie benötigen. diff --git a/docs/guide/custom-sections.es.md b/docs/guide/custom-sections.es.md index 6aa0746b..dd2659dd 100644 --- a/docs/guide/custom-sections.es.md +++ b/docs/guide/custom-sections.es.md @@ -4,12 +4,21 @@ Las secciones personalizadas le permiten agregar cualquier contenido que no enca ## Crear una sección personalizada -1. Abra **Settings** → pestaña **Custom Sections** -2. Haga clic en **Add Custom Section** +1. Pase el ratón justo debajo de cualquier sección existente de su CV — aparecerá un marcador discontinuo **+ Crear sección personalizada** +2. Haga clic en el marcador para abrir el cuadro de diálogo de creación 3. Introduzca un nombre para la sección 4. Elija un tipo de diseño (ver más abajo) -5. Opcionalmente elija un **ícono de sección** (star, book, link, globe, heart, award, briefcase) — se muestra junto al encabezado -6. Guarde — la nueva sección aparecerá en su CV +5. Guarde — la nueva sección se insertará directamente debajo de la sección sobre la que pasó el ratón + +## Gestionar una sección personalizada + +Cada sección personalizada tiene tres botones con icono en su encabezado: + +- **Eliminar** (icono de papelera) — elimina la sección y todos sus elementos tras una confirmación +- **Renombrar** (icono de lápiz) — abre un pequeño diálogo para cambiar el título de la sección +- **Visibilidad** (icono de ojo) — activa o desactiva que la sección aparezca en el CV publicado + +El tipo de diseño de una sección se elige al crearla y no puede cambiarse después — si necesita un diseño distinto, cree una nueva sección y mueva los elementos allí. ## Tipos de diseño @@ -59,4 +68,4 @@ Para los diseños de cuadrícula, lista, tarjeta y viñetas, cada elemento tiene Haga clic en **Manage Items** en cualquier sección personalizada para agregar, editar, reordenar o eliminar elementos dentro de esa sección. !!! note - No hay un límite estricto de secciones personalizadas. Cree tantas como necesite. También puede cambiar el tipo de diseño de una sección personalizada después de crearla — simplemente edite la sección y seleccione un diseño diferente. + No hay un límite estricto de secciones personalizadas. Cree tantas como necesite. diff --git a/docs/guide/custom-sections.fr.md b/docs/guide/custom-sections.fr.md index 1c77c036..a248c0ab 100644 --- a/docs/guide/custom-sections.fr.md +++ b/docs/guide/custom-sections.fr.md @@ -4,12 +4,21 @@ Les sections personnalisées vous permettent d'ajouter tout contenu qui ne corre ## Créer une section personnalisée -1. Ouvrez **Paramètres** → onglet **Sections personnalisées** -2. Cliquez sur **Ajouter une section personnalisée** +1. Survolez juste en dessous d'une section existante de votre CV — un espace réservé en pointillés **+ Créer une section personnalisée** apparaît +2. Cliquez sur l'espace réservé pour ouvrir la boîte de dialogue de création 3. Entrez un nom de section 4. Choisissez un type de mise en page (voir ci-dessous) -5. Choisissez éventuellement une **icône de section** (étoile, livre, lien, globe, cœur, récompense, mallette) — affichée à côté du titre -6. Enregistrez — la nouvelle section apparaît dans votre CV +5. Enregistrez — la nouvelle section est insérée directement sous la section que vous avez survolée + +## Gérer une section personnalisée + +Chaque section personnalisée dispose de trois boutons d'icône dans son en-tête : + +- **Supprimer** (icône corbeille) — supprime la section et tous ses éléments après confirmation +- **Renommer** (icône crayon) — ouvre une petite boîte de dialogue pour modifier le titre de la section +- **Visibilité** (icône œil) — active ou désactive l'affichage de la section dans le CV en ligne + +Le type de mise en page d'une section est choisi à sa création et ne peut plus être modifié ensuite — si vous avez besoin d'une autre mise en page, créez une nouvelle section et déplacez-y les éléments. ## Types de mise en page @@ -59,4 +68,4 @@ Pour les mises en page grille, liste, cartes et puces, chaque élément dispose Cliquez sur **Gérer les éléments** sur n'importe quelle section personnalisée pour ajouter, modifier, réordonner ou supprimer des éléments au sein de cette section. !!! note "Remarque" - Il n'y a pas de limite stricte sur les sections personnalisées. Créez-en autant que nécessaire. Vous pouvez également changer le type de mise en page d'une section personnalisée après l'avoir créée — il suffit de modifier la section et de sélectionner une mise en page différente. + Il n'y a pas de limite stricte sur les sections personnalisées. Créez-en autant que nécessaire. diff --git a/docs/guide/custom-sections.it.md b/docs/guide/custom-sections.it.md index c32d4a7e..9722a84f 100644 --- a/docs/guide/custom-sections.it.md +++ b/docs/guide/custom-sections.it.md @@ -4,12 +4,21 @@ Le sezioni personalizzate consentono di aggiungere qualsiasi contenuto che non r ## Creazione di una sezione personalizzata -1. Aprite **Impostazioni** → scheda **Sezioni personalizzate** -2. Fate clic su **Aggiungi sezione personalizzata** +1. Passate il mouse appena sotto una sezione esistente del CV — comparirà un segnaposto tratteggiato **+ Crea sezione personalizzata** +2. Fate clic sul segnaposto per aprire la finestra di creazione 3. Inserite un nome per la sezione 4. Scegliete un tipo di layout (vedere sotto) -5. Opzionalmente scegliete un'**icona di sezione** (stella, libro, link, globo, cuore, premio, valigetta) — visualizzata accanto all'intestazione -6. Salvate — la nuova sezione appare nel CV +5. Salvate — la nuova sezione verrà inserita direttamente sotto la sezione su cui avete passato il mouse + +## Gestire una sezione personalizzata + +Ogni sezione personalizzata ha tre pulsanti icona nell'intestazione: + +- **Elimina** (icona cestino) — rimuove la sezione e tutti i suoi elementi dopo una conferma +- **Rinomina** (icona matita) — apre una piccola finestra per modificare il titolo della sezione +- **Visibilità** (icona occhio) — attiva o disattiva la visualizzazione della sezione nel CV pubblicato + +Il tipo di layout di una sezione viene scelto al momento della creazione e non può essere modificato in seguito — se vi serve un layout diverso, create una nuova sezione e spostate gli elementi lì. ## Tipi di layout @@ -59,4 +68,4 @@ Per i layout griglia, elenco, schede e elenco puntato, ogni elemento ha una case Fate clic su **Gestisci elementi** su qualsiasi sezione personalizzata per aggiungere, modificare, riordinare o eliminare elementi all'interno di quella sezione. !!! note "Nota" - Non esiste un limite massimo per le sezioni personalizzate. Potete crearne quante ne desiderate. Potete anche cambiare il tipo di layout di una sezione personalizzata dopo la creazione — basta modificare la sezione e selezionare un layout diverso. + Non esiste un limite massimo per le sezioni personalizzate. Potete crearne quante ne desiderate. diff --git a/docs/guide/custom-sections.md b/docs/guide/custom-sections.md index e1fdf1af..be97a4fa 100644 --- a/docs/guide/custom-sections.md +++ b/docs/guide/custom-sections.md @@ -4,12 +4,21 @@ Custom sections let you add any content that doesn't fit the built-in sections. ## Creating a Custom Section -1. Open **Settings** → **Custom Sections** tab -2. Click **Add Custom Section** +1. Hover just below any existing section in your CV — a dashed **+ Create custom section** placeholder appears +2. Click the placeholder to open the creation dialog 3. Enter a section name 4. Choose a layout type (see below) -5. Optionally choose a **section icon** (star, book, link, globe, heart, award, briefcase) — displayed next to the heading -6. Save — the new section appears in your CV +5. Save — the new section is inserted directly below the section you hovered over + +## Managing a Custom Section + +Each custom section has three icon buttons in its header: + +- **Delete** (trash icon) — removes the section and all of its items after confirmation +- **Rename** (pencil icon) — opens a small dialog to change the section's title +- **Visibility** (eye icon) — toggles whether the section is shown on the live CV + +The section's layout type is chosen when you create it and cannot be changed afterwards — if you need a different layout, create a new section and move the items over. ## Layout Types @@ -59,4 +68,4 @@ For grid, list, card, and bullet layouts, each item has a **"Hide title"** check Click **Manage Items** on any custom section to add, edit, reorder, or delete items within that section. !!! note - There's no hard limit on custom sections. Create as many as you need. You can also change a custom section's layout type after creating it — just edit the section and select a different layout. + There's no hard limit on custom sections. Create as many as you need. diff --git a/docs/guide/custom-sections.nl.md b/docs/guide/custom-sections.nl.md index 534ffbaa..f7c8a09a 100644 --- a/docs/guide/custom-sections.nl.md +++ b/docs/guide/custom-sections.nl.md @@ -4,12 +4,21 @@ Met aangepaste secties kunt u inhoud toevoegen die niet past bij de ingebouwde s ## Een aangepaste sectie aanmaken -1. Open **Instellingen** → tabblad **Aangepaste secties** -2. Klik op **Aangepaste sectie toevoegen** +1. Beweeg de muis net onder een bestaande sectie in uw CV — er verschijnt een gestippelde placeholder **+ Aangepaste sectie aanmaken** +2. Klik op de placeholder om het aanmaakdialoogvenster te openen 3. Voer een sectienaam in 4. Kies een lay-outtype (zie hieronder) -5. Kies optioneel een **sectiepictogram** (ster, boek, link, wereldbol, hart, onderscheiding, aktentas) — weergegeven naast de kop -6. Sla op — de nieuwe sectie verschijnt in uw CV +5. Sla op — de nieuwe sectie wordt direct onder de sectie die u aanwees ingevoegd + +## Een aangepaste sectie beheren + +Elke aangepaste sectie heeft drie pictogramknoppen in de kop: + +- **Verwijderen** (prullenbakpictogram) — verwijdert de sectie en al haar items na bevestiging +- **Hernoemen** (potloodpictogram) — opent een klein dialoogvenster om de titel van de sectie te wijzigen +- **Zichtbaarheid** (oogpictogram) — bepaalt of de sectie in de live CV wordt weergegeven + +Het lay-outtype van een sectie wordt bij het aanmaken gekozen en kan daarna niet meer worden gewijzigd — als u een andere lay-out nodig heeft, maak dan een nieuwe sectie aan en verplaats de items daarheen. ## Lay-outtypen @@ -59,4 +68,4 @@ Voor raster-, lijst-, kaart- en opsommingslay-outs heeft elk item een selectieva Klik op **Items beheren** bij een aangepaste sectie om items toe te voegen, te bewerken, te herschikken of te verwijderen binnen die sectie. !!! note "Opmerking" - Er is geen harde limiet voor het aantal aangepaste secties. Maak er zoveel aan als u nodig heeft. U kunt ook het lay-outtype van een aangepaste sectie wijzigen nadat u deze heeft aangemaakt — bewerk simpelweg de sectie en selecteer een andere lay-out. + Er is geen harde limiet voor het aantal aangepaste secties. Maak er zoveel aan als u nodig heeft. diff --git a/docs/guide/custom-sections.pt.md b/docs/guide/custom-sections.pt.md index ff6ffe98..edf380ae 100644 --- a/docs/guide/custom-sections.pt.md +++ b/docs/guide/custom-sections.pt.md @@ -4,12 +4,21 @@ Seções personalizadas permitem que você adicione qualquer conteúdo que não ## Criando uma Seção Personalizada -1. Abra **Configurações** → aba **Seções Personalizadas** -2. Clique em **Adicionar Seção Personalizada** +1. Passe o cursor logo abaixo de qualquer seção existente do seu CV — um marcador tracejado **+ Criar seção personalizada** aparece +2. Clique no marcador para abrir a caixa de diálogo de criação 3. Digite um nome para a seção 4. Escolha um tipo de layout (veja abaixo) -5. Opcionalmente, escolha um **ícone de seção** (estrela, livro, link, globo, coração, prêmio, maleta) — exibido ao lado do título -6. Salve — a nova seção aparece no seu CV +5. Salve — a nova seção será inserida logo abaixo da seção sobre a qual você passou o cursor + +## Gerenciando uma Seção Personalizada + +Cada seção personalizada possui três botões de ícone em seu cabeçalho: + +- **Excluir** (ícone de lixeira) — remove a seção e todos os seus itens após confirmação +- **Renomear** (ícone de lápis) — abre uma pequena caixa de diálogo para alterar o título da seção +- **Visibilidade** (ícone de olho) — ativa ou desativa a exibição da seção no CV publicado + +O tipo de layout de uma seção é escolhido na criação e não pode ser alterado depois — se precisar de um layout diferente, crie uma nova seção e mova os itens para lá. ## Tipos de Layout @@ -59,4 +68,4 @@ Para layouts de grade, lista, cartão e marcadores, cada item possui uma caixa d Clique em **Gerenciar Itens** em qualquer seção personalizada para adicionar, editar, reordenar ou excluir itens dentro dessa seção. !!! note "Nota" - Não há limite fixo para seções personalizadas. Crie quantas precisar. Você também pode alterar o tipo de layout de uma seção personalizada após criá-la — basta editar a seção e selecionar um layout diferente. + Não há limite fixo para seções personalizadas. Crie quantas precisar. diff --git a/docs/guide/custom-sections.zh.md b/docs/guide/custom-sections.zh.md index 784184c8..572422f1 100644 --- a/docs/guide/custom-sections.zh.md +++ b/docs/guide/custom-sections.zh.md @@ -4,12 +4,21 @@ ## 创建自定义板块 -1. 打开**设置** → **自定义板块**选项卡 -2. 点击**添加自定义板块** +1. 将鼠标悬停在简历中任意已有板块的下方 — 会出现一个虚线占位符 **+ 创建自定义板块** +2. 点击占位符以打开创建对话框 3. 输入板块名称 4. 选择布局类型(见下文) -5. 可选择一个**板块图标**(star、book、link、globe、heart、award、briefcase)— 显示在标题旁边 -6. 保存 — 新板块将出现在您的简历中 +5. 保存 — 新板块会直接插入到您悬停的板块下方 + +## 管理自定义板块 + +每个自定义板块的标题栏中都有三个图标按钮: + +- **删除**(垃圾桶图标)— 在确认后删除该板块及其所有条目 +- **重命名**(铅笔图标)— 打开一个小对话框用于修改板块标题 +- **可见性**(眼睛图标)— 切换该板块是否显示在已发布的简历中 + +板块的布局类型在创建时选定,之后无法更改 — 如果需要其他布局,请新建一个板块并将条目移入其中。 ## 布局类型 @@ -59,4 +68,4 @@ 点击任何自定义板块上的**管理条目**来添加、编辑、重新排序或删除该板块中的条目。 !!! note "注意" - 自定义板块没有数量限制。您可以根据需要创建任意数量。您也可以在创建后更改自定义板块的布局类型 — 只需编辑板块并选择不同的布局即可。 + 自定义板块没有数量限制。您可以根据需要创建任意数量。 diff --git a/package-lock.json b/package-lock.json index 566e1bf1..9ca0aec7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cv-manager", - "version": "1.33.3", + "version": "1.34.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cv-manager", - "version": "1.33.3", + "version": "1.34.0", "dependencies": { "archiver": "^7.0.1", "better-sqlite3": "^9.4.3", diff --git a/package.json b/package.json index 4e457ef7..c98de77e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cv-manager", - "version": "1.33.3", + "version": "1.34.0", "description": "Professional CV Management System", "main": "src/server.js", "scripts": { diff --git a/public/index.html b/public/index.html index c4fbc5f7..76fc2c22 100644 --- a/public/index.html +++ b/public/index.html @@ -385,11 +385,10 @@
@@ -215,6 +221,9 @@

Work Experience

+
@@ -235,6 +244,9 @@

Certifications

+
@@ -255,6 +267,9 @@

Education

+
@@ -275,6 +290,9 @@

Skills & Expertise

+
@@ -295,6 +313,9 @@

Featured Projects

+
diff --git a/public/shared/admin.css b/public/shared/admin.css index adf0cfbd..e504657d 100644 --- a/public/shared/admin.css +++ b/public/shared/admin.css @@ -70,6 +70,7 @@ .icon-btn:hover { background: var(--primary); color: var(--white); } .icon-btn.active { background: var(--primary); color: var(--white); } +.icon-btn:disabled, .icon-btn[disabled] { opacity: 0.4; cursor: not-allowed; pointer-events: none; } .icon-btn svg { width: 16px; height: 16px; } .icon-btn .material-symbols-outlined { font-size: 16px; } diff --git a/public/shared/admin.js b/public/shared/admin.js index f5245e5b..f306738b 100644 --- a/public/shared/admin.js +++ b/public/shared/admin.js @@ -674,6 +674,8 @@ function reorderSectionElements() { el.classList.remove('hidden-print'); } } + // Sync the inline print toggle button's state (active / disabled / icon) + syncSectionPrintButton(section.key); }); applySectionTitles(sectionOrder); @@ -811,6 +813,9 @@ function renderCustomSection(section) { +
@@ -1204,7 +1209,7 @@ async function toggleSection(section) { const newValue = !sectionVisibility[section]; await api(`/api/sections/${section}`, { method: 'PUT', body: { visible: newValue } }); sectionVisibility = await loadSectionsAdmin(); - + // For custom sections, also update the customSections data if (section.startsWith('custom_')) { const customSection = customSections.find(cs => cs.section_key === section); @@ -1212,10 +1217,45 @@ async function toggleSection(section) { customSection.visible = newValue; } } + // Keep the print-visibility button in sync: a hidden section is also hidden from print, + // and the inline print button is disabled while the section is hidden (matches Settings). + syncSectionPrintButton(section); toast('Section visibility updated'); autoSaveActiveDataset(); } +// Toggle Section Print Visibility (inline — mirrors the one in Settings) +async function toggleSectionPrint(section) { + const entry = sectionOrder.find(s => s.key === section); + const currentVisible = entry ? entry.visible : !!sectionVisibility[section]; + if (!currentVisible) return; // can't toggle print on a hidden section + const currentPrint = entry ? entry.print_visible !== false : true; + const newValue = !currentPrint; + await api(`/api/sections/${section}`, { method: 'PUT', body: { print_visible: newValue } }); + if (entry) entry.print_visible = newValue; + // Reflect in the DOM: `.hidden-print` hides the section in print when either flag is off. + const el = document.getElementById(`section-${section}`); + if (el) el.classList.toggle('hidden-print', !newValue); + syncSectionPrintButton(section); + toast('Section print visibility updated'); + autoSaveActiveDataset(); +} + +// Update the inline print button's active/disabled state and icon for a given section +function syncSectionPrintButton(section) { + const btn = document.getElementById(`toggle-print-${section}`); + if (!btn) return; + const entry = sectionOrder.find(s => s.key === section); + const visible = entry ? entry.visible : !!sectionVisibility[section]; + const printVisible = entry ? entry.print_visible !== false : true; + btn.classList.toggle('active', visible && printVisible); + btn.classList.toggle('disabled', !visible); + btn.disabled = !visible; + const iconName = printVisible ? 'print' : 'print_disabled'; + const iconEl = btn.querySelector('.material-symbols-outlined'); + if (iconEl) iconEl.textContent = iconName; +} + // Toggle Item Visibility async function toggleVisibility(endpoint, id, visible) { const data = await api(`/api/${endpoint}/${id}`); diff --git a/public/shared/i18n/de.json b/public/shared/i18n/de.json index f59f03fa..2e150ea5 100644 --- a/public/shared/i18n/de.json +++ b/public/shared/i18n/de.json @@ -42,6 +42,7 @@ "action.edit_profile": "Profil bearbeiten", "action.edit_about": "Über mich bearbeiten", "action.toggle_visibility": "Sichtbarkeit umschalten", + "action.toggle_print_visibility": "Drucksichtbarkeit umschalten", "action.add_experience": "Erfahrung hinzufügen", "action.add_certification": "Zertifizierung hinzufügen", "action.add_education": "Ausbildung hinzufügen", diff --git a/public/shared/i18n/en.json b/public/shared/i18n/en.json index e9bcb5bf..c741caeb 100644 --- a/public/shared/i18n/en.json +++ b/public/shared/i18n/en.json @@ -47,6 +47,7 @@ "action.edit_profile": "Edit Profile", "action.edit_about": "Edit About", "action.toggle_visibility": "Toggle Visibility", + "action.toggle_print_visibility": "Toggle Print Visibility", "action.add_experience": "Add Experience", "action.add_certification": "Add Certification", "action.add_education": "Add Education", diff --git a/public/shared/i18n/es.json b/public/shared/i18n/es.json index 07793c17..6ebc6000 100644 --- a/public/shared/i18n/es.json +++ b/public/shared/i18n/es.json @@ -42,6 +42,7 @@ "action.edit_profile": "Editar perfil", "action.edit_about": "Editar acerca de", "action.toggle_visibility": "Alternar visibilidad", + "action.toggle_print_visibility": "Alternar visibilidad en impresión", "action.add_experience": "Añadir experiencia", "action.add_certification": "Añadir certificación", "action.add_education": "Añadir educación", diff --git a/public/shared/i18n/fr.json b/public/shared/i18n/fr.json index 109d5476..c491e3bd 100644 --- a/public/shared/i18n/fr.json +++ b/public/shared/i18n/fr.json @@ -42,6 +42,7 @@ "action.edit_profile": "Modifier le profil", "action.edit_about": "Modifier la présentation", "action.toggle_visibility": "Basculer la visibilité", + "action.toggle_print_visibility": "Basculer la visibilité à l'impression", "action.add_experience": "Ajouter une expérience", "action.add_certification": "Ajouter une certification", "action.add_education": "Ajouter une formation", diff --git a/public/shared/i18n/it.json b/public/shared/i18n/it.json index 00cdc8df..2ff26b84 100644 --- a/public/shared/i18n/it.json +++ b/public/shared/i18n/it.json @@ -42,6 +42,7 @@ "action.edit_profile": "Modifica profilo", "action.edit_about": "Modifica informazioni", "action.toggle_visibility": "Cambia visibilità", + "action.toggle_print_visibility": "Cambia visibilità di stampa", "action.add_experience": "Aggiungi esperienza", "action.add_certification": "Aggiungi certificazione", "action.add_education": "Aggiungi formazione", diff --git a/public/shared/i18n/nl.json b/public/shared/i18n/nl.json index 97a988a3..2f014e4b 100644 --- a/public/shared/i18n/nl.json +++ b/public/shared/i18n/nl.json @@ -42,6 +42,7 @@ "action.edit_profile": "Profiel bewerken", "action.edit_about": "Over mij bewerken", "action.toggle_visibility": "Zichtbaarheid wijzigen", + "action.toggle_print_visibility": "Afdrukzichtbaarheid wijzigen", "action.add_experience": "Ervaring toevoegen", "action.add_certification": "Certificering toevoegen", "action.add_education": "Opleiding toevoegen", diff --git a/public/shared/i18n/pt.json b/public/shared/i18n/pt.json index 82cecf96..ec194f8e 100644 --- a/public/shared/i18n/pt.json +++ b/public/shared/i18n/pt.json @@ -42,6 +42,7 @@ "action.edit_profile": "Editar Perfil", "action.edit_about": "Editar Sobre", "action.toggle_visibility": "Alternar Visibilidade", + "action.toggle_print_visibility": "Alternar Visibilidade na Impressão", "action.add_experience": "Adicionar Experiência", "action.add_certification": "Adicionar Certificação", "action.add_education": "Adicionar Formação", diff --git a/public/shared/i18n/zh.json b/public/shared/i18n/zh.json index a166bb0d..e4d4115a 100644 --- a/public/shared/i18n/zh.json +++ b/public/shared/i18n/zh.json @@ -42,6 +42,7 @@ "action.edit_profile": "编辑个人资料", "action.edit_about": "编辑简介", "action.toggle_visibility": "切换可见性", + "action.toggle_print_visibility": "切换打印可见性", "action.add_experience": "添加工作经历", "action.add_certification": "添加资格认证", "action.add_education": "添加教育经历", diff --git a/src/server.js b/src/server.js index ef6e33df..7a50e23d 100644 --- a/src/server.js +++ b/src/server.js @@ -1889,7 +1889,21 @@ if (PUBLIC_ONLY) { }))); }); app.put('/api/sections/order', (req, res) => { const { sections } = req.body; if (!sections || !Array.isArray(sections)) return res.status(400).json({ error: 'Invalid sections data' }); const updateOrder = db.transaction(() => { sections.forEach(section => { const displayName = section.display_name || null; db.prepare('UPDATE section_visibility SET visible = ?, print_visible = ?, sort_order = ?, display_name = ? WHERE section_name = ?').run(section.visible ? 1 : 0, section.print_visible != false ? 1 : 0, section.sort_order, displayName, section.key); if (section.key.startsWith('custom_')) { db.prepare('UPDATE custom_sections SET visible = ?, sort_order = ? WHERE section_key = ?').run(section.visible ? 1 : 0, section.sort_order, section.key); } }); }); try { updateOrder(); res.json({ success: true }); } catch (err) { res.status(500).json({ error: err.message }); } }); - app.put('/api/sections/:name', (req, res) => { const sectionName = req.params.name; const visible = req.body.visible ? 1 : 0; db.prepare('UPDATE section_visibility SET visible = ? WHERE section_name = ?').run(visible, sectionName); if (sectionName.startsWith('custom_')) { db.prepare('UPDATE custom_sections SET visible = ? WHERE section_key = ?').run(visible, sectionName); } res.json({ success: true }); }); + app.put('/api/sections/:name', (req, res) => { + const sectionName = req.params.name; + if (req.body.visible !== undefined) { + const visible = req.body.visible ? 1 : 0; + db.prepare('UPDATE section_visibility SET visible = ? WHERE section_name = ?').run(visible, sectionName); + if (sectionName.startsWith('custom_')) { + db.prepare('UPDATE custom_sections SET visible = ? WHERE section_key = ?').run(visible, sectionName); + } + } + if (req.body.print_visible !== undefined) { + const printVisible = req.body.print_visible ? 1 : 0; + db.prepare('UPDATE section_visibility SET print_visible = ? WHERE section_name = ?').run(printVisible, sectionName); + } + res.json({ success: true }); + }); app.get('/api/experiences', (req, res) => { const experiences = db.prepare('SELECT * FROM experiences ORDER BY sort_order ASC, start_date DESC').all(); res.json(experiences.map(e => ({ ...e, highlights: e.highlights ? JSON.parse(e.highlights) : [], visible: !!e.visible }))); }); app.get('/api/experiences/:id', (req, res) => { const exp = db.prepare('SELECT * FROM experiences WHERE id = ?').get(req.params.id); if (!exp) return res.status(404).json({ error: 'Not found' }); res.json({ ...exp, highlights: exp.highlights ? JSON.parse(exp.highlights) : [], visible: !!exp.visible }); }); From 68d165f4fbf4658da76c13321dbe4cd59872c05d Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 17 Apr 2026 21:11:38 +0000 Subject: [PATCH 15/15] Revert "Inline print-visibility toggle next to the visibility button" This reverts commit e92f34c34cf8a3142745c13e8e96b9540a29e776. --- public/index.html | 21 ------------------- public/shared/admin.css | 1 - public/shared/admin.js | 42 +------------------------------------- public/shared/i18n/de.json | 1 - public/shared/i18n/en.json | 1 - public/shared/i18n/es.json | 1 - public/shared/i18n/fr.json | 1 - public/shared/i18n/it.json | 1 - public/shared/i18n/nl.json | 1 - public/shared/i18n/pt.json | 1 - public/shared/i18n/zh.json | 1 - src/server.js | 16 +-------------- 12 files changed, 2 insertions(+), 86 deletions(-) diff --git a/public/index.html b/public/index.html index 0673e068..76fc2c22 100644 --- a/public/index.html +++ b/public/index.html @@ -180,9 +180,6 @@

Professional Summary

-

@@ -199,9 +196,6 @@

Career Timeline

-
@@ -221,9 +215,6 @@

Work Experience

-
@@ -244,9 +235,6 @@

Certifications

-
@@ -267,9 +255,6 @@

Education

-
@@ -290,9 +275,6 @@

Skills & Expertise

-
@@ -313,9 +295,6 @@

Featured Projects

-
diff --git a/public/shared/admin.css b/public/shared/admin.css index e504657d..adf0cfbd 100644 --- a/public/shared/admin.css +++ b/public/shared/admin.css @@ -70,7 +70,6 @@ .icon-btn:hover { background: var(--primary); color: var(--white); } .icon-btn.active { background: var(--primary); color: var(--white); } -.icon-btn:disabled, .icon-btn[disabled] { opacity: 0.4; cursor: not-allowed; pointer-events: none; } .icon-btn svg { width: 16px; height: 16px; } .icon-btn .material-symbols-outlined { font-size: 16px; } diff --git a/public/shared/admin.js b/public/shared/admin.js index f306738b..f5245e5b 100644 --- a/public/shared/admin.js +++ b/public/shared/admin.js @@ -674,8 +674,6 @@ function reorderSectionElements() { el.classList.remove('hidden-print'); } } - // Sync the inline print toggle button's state (active / disabled / icon) - syncSectionPrintButton(section.key); }); applySectionTitles(sectionOrder); @@ -813,9 +811,6 @@ function renderCustomSection(section) { -
@@ -1209,7 +1204,7 @@ async function toggleSection(section) { const newValue = !sectionVisibility[section]; await api(`/api/sections/${section}`, { method: 'PUT', body: { visible: newValue } }); sectionVisibility = await loadSectionsAdmin(); - + // For custom sections, also update the customSections data if (section.startsWith('custom_')) { const customSection = customSections.find(cs => cs.section_key === section); @@ -1217,45 +1212,10 @@ async function toggleSection(section) { customSection.visible = newValue; } } - // Keep the print-visibility button in sync: a hidden section is also hidden from print, - // and the inline print button is disabled while the section is hidden (matches Settings). - syncSectionPrintButton(section); toast('Section visibility updated'); autoSaveActiveDataset(); } -// Toggle Section Print Visibility (inline — mirrors the one in Settings) -async function toggleSectionPrint(section) { - const entry = sectionOrder.find(s => s.key === section); - const currentVisible = entry ? entry.visible : !!sectionVisibility[section]; - if (!currentVisible) return; // can't toggle print on a hidden section - const currentPrint = entry ? entry.print_visible !== false : true; - const newValue = !currentPrint; - await api(`/api/sections/${section}`, { method: 'PUT', body: { print_visible: newValue } }); - if (entry) entry.print_visible = newValue; - // Reflect in the DOM: `.hidden-print` hides the section in print when either flag is off. - const el = document.getElementById(`section-${section}`); - if (el) el.classList.toggle('hidden-print', !newValue); - syncSectionPrintButton(section); - toast('Section print visibility updated'); - autoSaveActiveDataset(); -} - -// Update the inline print button's active/disabled state and icon for a given section -function syncSectionPrintButton(section) { - const btn = document.getElementById(`toggle-print-${section}`); - if (!btn) return; - const entry = sectionOrder.find(s => s.key === section); - const visible = entry ? entry.visible : !!sectionVisibility[section]; - const printVisible = entry ? entry.print_visible !== false : true; - btn.classList.toggle('active', visible && printVisible); - btn.classList.toggle('disabled', !visible); - btn.disabled = !visible; - const iconName = printVisible ? 'print' : 'print_disabled'; - const iconEl = btn.querySelector('.material-symbols-outlined'); - if (iconEl) iconEl.textContent = iconName; -} - // Toggle Item Visibility async function toggleVisibility(endpoint, id, visible) { const data = await api(`/api/${endpoint}/${id}`); diff --git a/public/shared/i18n/de.json b/public/shared/i18n/de.json index 2e150ea5..f59f03fa 100644 --- a/public/shared/i18n/de.json +++ b/public/shared/i18n/de.json @@ -42,7 +42,6 @@ "action.edit_profile": "Profil bearbeiten", "action.edit_about": "Über mich bearbeiten", "action.toggle_visibility": "Sichtbarkeit umschalten", - "action.toggle_print_visibility": "Drucksichtbarkeit umschalten", "action.add_experience": "Erfahrung hinzufügen", "action.add_certification": "Zertifizierung hinzufügen", "action.add_education": "Ausbildung hinzufügen", diff --git a/public/shared/i18n/en.json b/public/shared/i18n/en.json index c741caeb..e9bcb5bf 100644 --- a/public/shared/i18n/en.json +++ b/public/shared/i18n/en.json @@ -47,7 +47,6 @@ "action.edit_profile": "Edit Profile", "action.edit_about": "Edit About", "action.toggle_visibility": "Toggle Visibility", - "action.toggle_print_visibility": "Toggle Print Visibility", "action.add_experience": "Add Experience", "action.add_certification": "Add Certification", "action.add_education": "Add Education", diff --git a/public/shared/i18n/es.json b/public/shared/i18n/es.json index 6ebc6000..07793c17 100644 --- a/public/shared/i18n/es.json +++ b/public/shared/i18n/es.json @@ -42,7 +42,6 @@ "action.edit_profile": "Editar perfil", "action.edit_about": "Editar acerca de", "action.toggle_visibility": "Alternar visibilidad", - "action.toggle_print_visibility": "Alternar visibilidad en impresión", "action.add_experience": "Añadir experiencia", "action.add_certification": "Añadir certificación", "action.add_education": "Añadir educación", diff --git a/public/shared/i18n/fr.json b/public/shared/i18n/fr.json index c491e3bd..109d5476 100644 --- a/public/shared/i18n/fr.json +++ b/public/shared/i18n/fr.json @@ -42,7 +42,6 @@ "action.edit_profile": "Modifier le profil", "action.edit_about": "Modifier la présentation", "action.toggle_visibility": "Basculer la visibilité", - "action.toggle_print_visibility": "Basculer la visibilité à l'impression", "action.add_experience": "Ajouter une expérience", "action.add_certification": "Ajouter une certification", "action.add_education": "Ajouter une formation", diff --git a/public/shared/i18n/it.json b/public/shared/i18n/it.json index 2ff26b84..00cdc8df 100644 --- a/public/shared/i18n/it.json +++ b/public/shared/i18n/it.json @@ -42,7 +42,6 @@ "action.edit_profile": "Modifica profilo", "action.edit_about": "Modifica informazioni", "action.toggle_visibility": "Cambia visibilità", - "action.toggle_print_visibility": "Cambia visibilità di stampa", "action.add_experience": "Aggiungi esperienza", "action.add_certification": "Aggiungi certificazione", "action.add_education": "Aggiungi formazione", diff --git a/public/shared/i18n/nl.json b/public/shared/i18n/nl.json index 2f014e4b..97a988a3 100644 --- a/public/shared/i18n/nl.json +++ b/public/shared/i18n/nl.json @@ -42,7 +42,6 @@ "action.edit_profile": "Profiel bewerken", "action.edit_about": "Over mij bewerken", "action.toggle_visibility": "Zichtbaarheid wijzigen", - "action.toggle_print_visibility": "Afdrukzichtbaarheid wijzigen", "action.add_experience": "Ervaring toevoegen", "action.add_certification": "Certificering toevoegen", "action.add_education": "Opleiding toevoegen", diff --git a/public/shared/i18n/pt.json b/public/shared/i18n/pt.json index ec194f8e..82cecf96 100644 --- a/public/shared/i18n/pt.json +++ b/public/shared/i18n/pt.json @@ -42,7 +42,6 @@ "action.edit_profile": "Editar Perfil", "action.edit_about": "Editar Sobre", "action.toggle_visibility": "Alternar Visibilidade", - "action.toggle_print_visibility": "Alternar Visibilidade na Impressão", "action.add_experience": "Adicionar Experiência", "action.add_certification": "Adicionar Certificação", "action.add_education": "Adicionar Formação", diff --git a/public/shared/i18n/zh.json b/public/shared/i18n/zh.json index e4d4115a..a166bb0d 100644 --- a/public/shared/i18n/zh.json +++ b/public/shared/i18n/zh.json @@ -42,7 +42,6 @@ "action.edit_profile": "编辑个人资料", "action.edit_about": "编辑简介", "action.toggle_visibility": "切换可见性", - "action.toggle_print_visibility": "切换打印可见性", "action.add_experience": "添加工作经历", "action.add_certification": "添加资格认证", "action.add_education": "添加教育经历", diff --git a/src/server.js b/src/server.js index 7a50e23d..ef6e33df 100644 --- a/src/server.js +++ b/src/server.js @@ -1889,21 +1889,7 @@ if (PUBLIC_ONLY) { }))); }); app.put('/api/sections/order', (req, res) => { const { sections } = req.body; if (!sections || !Array.isArray(sections)) return res.status(400).json({ error: 'Invalid sections data' }); const updateOrder = db.transaction(() => { sections.forEach(section => { const displayName = section.display_name || null; db.prepare('UPDATE section_visibility SET visible = ?, print_visible = ?, sort_order = ?, display_name = ? WHERE section_name = ?').run(section.visible ? 1 : 0, section.print_visible != false ? 1 : 0, section.sort_order, displayName, section.key); if (section.key.startsWith('custom_')) { db.prepare('UPDATE custom_sections SET visible = ?, sort_order = ? WHERE section_key = ?').run(section.visible ? 1 : 0, section.sort_order, section.key); } }); }); try { updateOrder(); res.json({ success: true }); } catch (err) { res.status(500).json({ error: err.message }); } }); - app.put('/api/sections/:name', (req, res) => { - const sectionName = req.params.name; - if (req.body.visible !== undefined) { - const visible = req.body.visible ? 1 : 0; - db.prepare('UPDATE section_visibility SET visible = ? WHERE section_name = ?').run(visible, sectionName); - if (sectionName.startsWith('custom_')) { - db.prepare('UPDATE custom_sections SET visible = ? WHERE section_key = ?').run(visible, sectionName); - } - } - if (req.body.print_visible !== undefined) { - const printVisible = req.body.print_visible ? 1 : 0; - db.prepare('UPDATE section_visibility SET print_visible = ? WHERE section_name = ?').run(printVisible, sectionName); - } - res.json({ success: true }); - }); + app.put('/api/sections/:name', (req, res) => { const sectionName = req.params.name; const visible = req.body.visible ? 1 : 0; db.prepare('UPDATE section_visibility SET visible = ? WHERE section_name = ?').run(visible, sectionName); if (sectionName.startsWith('custom_')) { db.prepare('UPDATE custom_sections SET visible = ? WHERE section_key = ?').run(visible, sectionName); } res.json({ success: true }); }); app.get('/api/experiences', (req, res) => { const experiences = db.prepare('SELECT * FROM experiences ORDER BY sort_order ASC, start_date DESC').all(); res.json(experiences.map(e => ({ ...e, highlights: e.highlights ? JSON.parse(e.highlights) : [], visible: !!e.visible }))); }); app.get('/api/experiences/:id', (req, res) => { const exp = db.prepare('SELECT * FROM experiences WHERE id = ?').get(req.params.id); if (!exp) return res.status(404).json({ error: 'Not found' }); res.json({ ...exp, highlights: exp.highlights ? JSON.parse(exp.highlights) : [], visible: !!exp.visible }); });