feat(cv): qualify templates and reliable local import processing
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace JobTrackerApi.Models;
|
||||
|
||||
@@ -6,6 +7,10 @@ namespace JobTrackerApi.Models;
|
||||
public sealed class CoverLetterDocumentSettings
|
||||
{
|
||||
public int SchemaVersion { get; set; } = 1;
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public int? DesignVersion { get; set; }
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? HeadingFont { get; set; }
|
||||
public string Language { get; set; } = "en";
|
||||
public string SenderName { get; set; } = "";
|
||||
public string SenderTitle { get; set; } = "";
|
||||
@@ -38,16 +43,18 @@ public static class CoverLetterDocumentSettingsJson
|
||||
public static CoverLetterDocumentSettings Normalize(CoverLetterDocumentSettings? value)
|
||||
{
|
||||
value ??= new();
|
||||
var theme = CvThemeCatalog.Resolve(value.ThemeId);
|
||||
var theme = CvThemeCatalog.Resolve(value.ThemeId, value.DesignVersion);
|
||||
var style = CvVariantSettingsJson.Normalize(new()
|
||||
{
|
||||
AccentColor = value.AccentColor, BodyFont = value.BodyFont,
|
||||
AccentColor = value.AccentColor, BodyFont = value.BodyFont, HeadingFont = value.HeadingFont,
|
||||
BaseFontSizePt = value.BaseFontSizePt, LineHeight = value.LineHeight, PageMarginMm = value.PageMarginMm,
|
||||
});
|
||||
static string Text(string? text, int limit) => (text ?? "")[..Math.Min(text?.Length ?? 0, limit)];
|
||||
return new()
|
||||
{
|
||||
SchemaVersion = 1, ThemeId = theme.Id,
|
||||
DesignVersion = value.DesignVersion is 1 or 2 ? value.DesignVersion : null,
|
||||
HeadingFont = value.DesignVersion == 2 ? style.HeadingFont ?? theme.HeadingFont : null,
|
||||
Language = value.Language?.Trim().ToLowerInvariant() is "nb" or "nb-no" ? "nb-NO" : "en",
|
||||
SenderName = Text(value.SenderName, 300), SenderTitle = Text(value.SenderTitle, 300),
|
||||
SenderEmail = Text(value.SenderEmail, 300), SenderPhone = Text(value.SenderPhone, 100), SenderLocation = Text(value.SenderLocation, 300),
|
||||
|
||||
@@ -38,6 +38,8 @@ public sealed class CvTheme
|
||||
public double PageMarginMm { get; init; } = 16;
|
||||
public double SectionGapMm { get; init; } = 6;
|
||||
public double EntryGapMm { get; init; } = 4.5;
|
||||
public double NameGapMm { get; init; }
|
||||
public double HeaderContentGapMm { get; init; }
|
||||
|
||||
// plain | band | centered | kicker
|
||||
public string HeaderStyle { get; init; } = "plain";
|
||||
@@ -159,6 +161,45 @@ public static class CvThemeCatalog
|
||||
},
|
||||
};
|
||||
|
||||
// Versioned data keeps existing saved lenses and frozen output on their original design.
|
||||
public static readonly IReadOnlyList<CvTheme> RevisedThemes = Themes.Select(Revised).ToArray();
|
||||
|
||||
private static CvTheme Revised(CvTheme source)
|
||||
{
|
||||
var (nameSize, sectionGap, entryGap) = source.Id switch
|
||||
{
|
||||
"modern" => (25d, 5.8, 4.0),
|
||||
"minimal" => (25d, 7.5, 4.8),
|
||||
"executive" => (28d, 6.5, 4.6),
|
||||
"technical" => (24d, 4.8, 3.2),
|
||||
"ats-classic" => (23d, 5.0, 3.4),
|
||||
"nordic" => (23d, 6.0, 4.2),
|
||||
"elegant" => (27d, 7.5, 5.0),
|
||||
"creative" => (20d, 5.5, 3.8),
|
||||
_ => (25.5, 5.2, 3.6),
|
||||
};
|
||||
return new CvTheme
|
||||
{
|
||||
Id = source.Id, Name = source.Name, Category = source.Category, Description = source.Description,
|
||||
Layout = source.Layout, SidebarWidthMm = 68,
|
||||
Accent = source.Accent, Ink = source.Ink, Muted = source.Muted, Line = source.Line,
|
||||
Paper = source.Paper, SidebarBg = source.SidebarBg, SidebarInk = source.SidebarInk, HeadingColor = source.HeadingColor,
|
||||
HeadingFont = source.HeadingFont, BodyFont = source.BodyFont, UtilityFont = source.UtilityFont,
|
||||
NameSizePt = nameSize, HeadingSizePt = source.HeadingSizePt, BodySizePt = source.BodySizePt, LineHeight = source.LineHeight,
|
||||
PageMarginMm = source.PageMarginMm, SectionGapMm = sectionGap, EntryGapMm = entryGap,
|
||||
NameGapMm = source.Id is "modern" or "ats-classic" or "code" ? 3 : 0,
|
||||
HeaderContentGapMm = source.Id is "modern" or "ats-classic" or "code" ? 5 : 0,
|
||||
HeaderStyle = source.HeaderStyle, HeadingStyle = source.HeadingStyle, PhotoShape = source.PhotoShape,
|
||||
DefaultIcons = source.DefaultIcons, AtsFriendly = source.AtsFriendly, Premium = source.Premium,
|
||||
NumberSections = source.NumberSections, DefaultSkillsStyle = source.DefaultSkillsStyle,
|
||||
SupportedSettings = new(source.SupportedSettings), SidebarSections = new(source.SidebarSections),
|
||||
};
|
||||
}
|
||||
|
||||
public static CvTheme Resolve(string? id, int? designVersion) => designVersion == 2
|
||||
? RevisedThemes.FirstOrDefault(theme => theme.Id == (id ?? string.Empty).Trim().ToLowerInvariant()) ?? RevisedThemes[0]
|
||||
: Resolve(id);
|
||||
|
||||
public static CvTheme Resolve(string? id)
|
||||
{
|
||||
var key = (id ?? string.Empty).Trim().ToLowerInvariant();
|
||||
|
||||
@@ -9,6 +9,8 @@ namespace JobTrackerApi.Models;
|
||||
public sealed class CvVariantSettings
|
||||
{
|
||||
public string ThemeId { get; set; } = "modern";
|
||||
// Missing or version 1 retains the original template output.
|
||||
public int? DesignVersion { get; set; }
|
||||
|
||||
// Theme overrides (null => use the theme's own value).
|
||||
public string? AccentColor { get; set; }
|
||||
@@ -132,6 +134,7 @@ public static class CvVariantSettingsJson
|
||||
{
|
||||
s ??= new CvVariantSettings();
|
||||
s.ThemeId = string.IsNullOrWhiteSpace(s.ThemeId) ? "modern" : s.ThemeId.Trim().ToLowerInvariant();
|
||||
s.DesignVersion = s.DesignVersion is 1 or 2 ? s.DesignVersion : null;
|
||||
s.AccentColor = NormalizeColor(s.AccentColor);
|
||||
s.TextColor = NormalizeColor(s.TextColor);
|
||||
s.MutedColor = NormalizeColor(s.MutedColor);
|
||||
|
||||
Reference in New Issue
Block a user