fix: preserve blank CVs and reliable editor undo

This commit is contained in:
cesnimda
2026-10-27 07:37:03 +01:00
parent 5d4b8f23ce
commit 663023a088
22 changed files with 950 additions and 101 deletions
+19 -9
View File
@@ -6,7 +6,10 @@ using JobTrackerApi.Models;
namespace JobTrackerApi.Services;
public sealed record ThemedCvRenderResult(string ThemeId, string SuggestedFileName, string Html);
public sealed record ThemedCvRenderResult(string ThemeId, string SuggestedFileName, string Html)
{
public bool HasContent { get; init; } = true;
}
public interface IThemedCvRenderer
{
@@ -45,11 +48,11 @@ public sealed class ThemedCvRenderer : IThemedCvRenderer
var css = CvDocumentFonts.Embed(headingFont, bodyFont, theme.UtilityFont)
+ BuildCss(theme, settings, accent, headerInk, ink, muted, paper, headingColor, CvDocumentFonts.Stack(headingFont), CvDocumentFonts.Stack(bodyFont), headingStyle, density, pageDims, layout, twoColumn);
var header = RenderHeader(model, theme, showIcons, twoColumn, headerStyle);
var header = RenderHeader(model, theme, showIcons, twoColumn, headerStyle, settings.IncludeProfileContact == false);
var body = layout switch
{
"sidebar-left" => $@"<div class=""cols"">{Sidebar(model, sidebarHtml, theme, showIcons, sidebarSections, headerStyle)}<section class=""main"">{mainHtml}</section></div>",
"sidebar-right" => $@"<div class=""cols""><section class=""main"">{mainHtml}</section>{Sidebar(model, sidebarHtml, theme, showIcons, sidebarSections, headerStyle)}</div>",
"sidebar-left" => $@"<div class=""cols"">{Sidebar(model, sidebarHtml, theme, showIcons, sidebarSections, headerStyle, settings.IncludeProfileContact == false)}<section class=""main"">{mainHtml}</section></div>",
"sidebar-right" => $@"<div class=""cols""><section class=""main"">{mainHtml}</section>{Sidebar(model, sidebarHtml, theme, showIcons, sidebarSections, headerStyle, settings.IncludeProfileContact == false)}</div>",
_ => $@"{header}<section class=""main"">{mainHtml}</section>",
};
var html = $@"<!DOCTYPE html>
@@ -65,7 +68,12 @@ public sealed class ThemedCvRenderer : IThemedCvRenderer
</html>";
var fileName = Slug($"{model.FullName}-{theme.Id}") + ".pdf";
return new ThemedCvRenderResult(theme.Id, fileName, html);
return new ThemedCvRenderResult(theme.Id, fileName, html)
{
HasContent = model.Sections.Any(section => !section.IsEmpty)
|| !string.IsNullOrWhiteSpace(model.FullName) || !string.IsNullOrWhiteSpace(model.Headline)
|| model.Contact.Count > 0,
};
}
private static (string sidebar, string main) SplitColumns(CvRenderModel model, CvTheme theme, IReadOnlyCollection<string> sidebarKeys, CvVariantSettings settings)
@@ -82,20 +90,22 @@ public sealed class ThemedCvRenderer : IThemedCvRenderer
return (sidebar.ToString(), RenderSections(mainSections, theme, settings));
}
private static string Sidebar(CvRenderModel model, string sectionsHtml, CvTheme theme, bool showIcons, IReadOnlyCollection<string> sidebarSections, string headerStyle)
private static string Sidebar(CvRenderModel model, string sectionsHtml, CvTheme theme, bool showIcons, IReadOnlyCollection<string> sidebarSections, string headerStyle, bool hideEmptyHeader)
{
var header = RenderHeader(model, theme, showIcons, twoColumn: true, headerStyle);
var header = RenderHeader(model, theme, showIcons, twoColumn: true, headerStyle, hideEmptyHeader);
var contact = sidebarSections.Contains("contact", StringComparer.OrdinalIgnoreCase)
? RenderContactBlock(model.Contact, showIcons, sidebar: true)
: string.Empty;
return $@"<aside class=""sidebar"">{header}{contact}{sectionsHtml}</aside>";
}
private static string RenderHeader(CvRenderModel model, CvTheme theme, bool showIcons, bool twoColumn, string headerStyle)
private static string RenderHeader(CvRenderModel model, CvTheme theme, bool showIcons, bool twoColumn, string headerStyle, bool hideEmptyHeader)
{
if (hideEmptyHeader && string.IsNullOrWhiteSpace(model.FullName) && string.IsNullOrWhiteSpace(model.Headline)
&& model.Contact.Count == 0 && string.IsNullOrWhiteSpace(model.PhotoDataUrl)) return string.Empty;
var photo = RenderPhoto(model.PhotoDataUrl, theme.PhotoShape);
var kicker = headerStyle == "kicker" ? @"<div class=""kicker"">Curriculum Vitae</div>" : string.Empty;
var name = $@"<h1 class=""name"">{Enc(model.FullName)}</h1>";
var name = hideEmptyHeader && string.IsNullOrWhiteSpace(model.FullName) ? string.Empty : $@"<h1 class=""name"">{Enc(model.FullName)}</h1>";
var headline = string.IsNullOrWhiteSpace(model.Headline) ? string.Empty : $@"<div class=""headline"">{Enc(model.Headline)}</div>";
var contact = twoColumn ? string.Empty : RenderContactBlock(model.Contact, showIcons, sidebar: false);
var headerClass = twoColumn ? "hero" : $"header header-{headerStyle}";