diff --git a/BewerbungWebseite/Models/SiteContent/ContentLimits.cs b/BewerbungWebseite/Models/SiteContent/ContentLimits.cs new file mode 100644 index 0000000..1f95d4b --- /dev/null +++ b/BewerbungWebseite/Models/SiteContent/ContentLimits.cs @@ -0,0 +1,83 @@ +namespace BewerbungsSeite.Models.SiteContent; + +/// +/// Einzige Quelle der Wahrheit fuer die Laenge der CMS-Textfelder. Jede Zahl +/// steht hier genau einmal und wird von drei Stellen verwendet: +/// - den [StringLength]-Attributen auf den SiteContent-Models (harte +/// Grenze, die ein manipulierter Request nicht umgehen kann), +/// - den data-max-Attributen, die EditMarkup im Admin-Editor rendert, +/// - dem Live-Zaehler in wwwroot/js/page-editor.js, der data-max ausliest. +/// Ändert sich eine Grenze, reicht die Aenderung an genau dieser Zahl. +/// +/// Die Werte sind nicht pro einzelnem Feld gewaehlt, sondern pro Feld-Typ: +/// viele Felder teilen denselben Zweck (z. B. alle "kicker"-Felder) und +/// damit denselben verfuegbaren Platz im Layout. Herleitung siehe die +/// Analyse-Tabelle in der Aufgabenbeschreibung; kurz zusammengefasst: +/// - Kicker/Titel/Buttons/Icons sitzen einzeilig neben anderen Elementen +/// -> enge, harte Grenzen. +/// - Fliesstexte (Intro, Beschreibungen) haben viel Breite und brechen +/// dank CSS sauber um -> grosszuegiger Deckel, kein hartes Sperren beim +/// Tippen im Editor. +/// +public static class ContentLimits +{ + /// Kicker-Label ueber einem Section-Titel (alle 8 Sections). + public const int Kicker = 40; + + /// Grosser Section-/Hero-Titel bzw. dessen hervorgehobener Teil. + public const int Heading = 50; + + /// Titel einer Skill-/Ziel-Kachel. + public const int TileTitle = 50; + + /// + /// Text einer Skill-/Ziel-Kachel. Kacheln stehen im selben Flex-Row-Verbund + /// wie ihre Nachbarn und werden dadurch alle gleich hoch -- deshalb der + /// engste Deckel unter den Fliesstext-Feldern. + /// + public const int TileText = 180; + + /// Label einer Kurzfakten-Zeile (about.facts.label). + public const int FactLabel = 30; + + /// Wert einer Kurzfakten-Zeile (about.facts.value). + public const int FactValue = 80; + + /// CSS-Klasse eines Icons (kein Freitext). + public const int IconClass = 60; + + /// Titel einer Projekt-/Erfahrungs-/Dokumentkarte. + public const int CardTitle = 70; + + /// Kleiner Zwischentitel innerhalb einer Karte (z. B. "TECH:"). + public const int DetailHeading = 50; + + /// Kurze Meta-Zeile unter einem Kartentitel (Experience.meta). + public const int Meta = 80; + + /// Beschriftung eines Buttons oder Links. + public const int ButtonLabel = 40; + + /// + /// Fliesstext in einer breiten Spalte (Hero-Intro, About-Text, + /// Projekt-/Detail-/Erfahrungstext, Dokumentbeschreibung). Grosszuegiger + /// Deckel als Rueckfallgrenze -- im Editor nur Warnfarbe, kein Sperren. + /// + public const int ParagraphText = 500; + + /// Zweizeilige Einleitung ueber dem Kontaktformular. + public const int FormIntro = 200; + + /// Url, Datei- oder Bildpfad. Kein Layoutrisiko, nur Schutz vor Missbrauch. + public const int Url = 300; + + /// Alt-Text eines Bildes. Unsichtbar, nur Hygiene-Deckel. + public const int AltText = 200; + + /// + /// Deutsche Fehlermeldung fuer alle [StringLength]-Attribute unten (der + /// ASP.NET-Standardtext ist Englisch). {1} fuellt ASP.NET Core automatisch + /// mit der maximalen Laenge des jeweiligen Attributs. + /// + public const string TooLongMessage = "Text ist zu lang (maximal {1} Zeichen erlaubt)."; +} diff --git a/BewerbungWebseite/Models/SiteContent/PageContent.cs b/BewerbungWebseite/Models/SiteContent/PageContent.cs new file mode 100644 index 0000000..1e140f6 --- /dev/null +++ b/BewerbungWebseite/Models/SiteContent/PageContent.cs @@ -0,0 +1,41 @@ +using System.ComponentModel.DataAnnotations; + +namespace BewerbungsSeite.Models.SiteContent; + +/// +/// Wurzelobjekt des gesamten Seiteninhalts. Wird als JSON serialisiert und +/// in einer einzigen Zeile der Tabelle PageContent abgelegt. +/// Neue Bereiche kommen als zusaetzliche Property dazu; alte Datensaetze +/// kennen die Property noch nicht und erhalten beim Laden den Initialwert. +/// +public class PageContent +{ + public HeroSection Hero { get; set; } = new(); + public AboutSection About { get; set; } = new(); + public SkillsSection Skills { get; set; } = new(); + public ProjectsSection Projects { get; set; } = new(); + public GoalsSection Goals { get; set; } = new(); + public ExperienceSection Experience { get; set; } = new(); + public DocumentsSection Documents { get; set; } = new(); + public ContactSection Contact { get; set; } = new(); +} + +/// Ein Link mit Beschriftung, z. B. in der Hero-Section oder auf einer Projektkarte. +public class LinkItem +{ + [StringLength(ContentLimits.ButtonLabel, ErrorMessage = ContentLimits.TooLongMessage)] + public string Text { get; set; } = string.Empty; + + [StringLength(ContentLimits.Url, ErrorMessage = ContentLimits.TooLongMessage)] + public string Url { get; set; } = string.Empty; +} + +/// Ein Abschnitt aus Zwischentitel und Text, z. B. "TECH:" auf einer Projektkarte. +public class DetailBlock +{ + [StringLength(ContentLimits.DetailHeading, ErrorMessage = ContentLimits.TooLongMessage)] + public string Heading { get; set; } = string.Empty; + + [StringLength(ContentLimits.ParagraphText, ErrorMessage = ContentLimits.TooLongMessage)] + public string Text { get; set; } = string.Empty; +}