feat: Rich-Text-Editor, Bild-Skalierung, PDF-Fix & Zeichenlimit
- Bild-Skalierung in Kartenansicht gefixt (object-position: top, 300px) - Admin-Slider für Zeichenlimit (4–32.000) als Modul-Setting - Textarea durch RichTextEditor (Quill.js) ersetzt - PDF: HTML-Parsing, Einzelperson-Filter, Autorisierung für alle User
This commit is contained in:
@@ -8,7 +8,10 @@ using Oqtane.Infrastructure;
|
||||
using Oqtane.Controllers;
|
||||
using SZUAbsolventenverein.Module.HallOfFame.Services;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Net;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
@@ -27,10 +30,10 @@ namespace SZUAbsolventenverein.Module.HallOfFame.Controllers
|
||||
_environment = environment;
|
||||
}
|
||||
|
||||
// GET: api/<controller>?moduleid=x&download=true/false
|
||||
// GET: api/<controller>?moduleid=x&download=true/false&id=y
|
||||
[HttpGet]
|
||||
[Authorize(Policy = PolicyNames.ViewModule)]
|
||||
public async Task<IActionResult> Get(string moduleid, bool download = false)
|
||||
public async Task<IActionResult> Get(string moduleid, bool download = false, int? id = null)
|
||||
{
|
||||
int ModuleId;
|
||||
if (int.TryParse(moduleid, out ModuleId) && IsAuthorizedEntityId(EntityNames.Module, ModuleId))
|
||||
@@ -38,6 +41,16 @@ namespace SZUAbsolventenverein.Module.HallOfFame.Controllers
|
||||
var entries = await _hallOfFameService.GetHallOfFamesAsync(ModuleId);
|
||||
var publishedEntries = entries.Where(e => e.Status == "Published").ToList();
|
||||
|
||||
// If a specific entry ID is provided, filter to just that entry
|
||||
if (id.HasValue)
|
||||
{
|
||||
publishedEntries = publishedEntries.Where(e => e.HallOfFameId == id.Value).ToList();
|
||||
if (!publishedEntries.Any())
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
var document = Document.Create(container =>
|
||||
{
|
||||
foreach (var entry in publishedEntries)
|
||||
@@ -116,11 +129,7 @@ namespace SZUAbsolventenverein.Module.HallOfFame.Controllers
|
||||
});
|
||||
|
||||
// ═══ BESCHREIBUNGSKARTE (unten) ═══
|
||||
var description = entry.Description ?? "";
|
||||
var sections = description.Split('\n')
|
||||
.Select(s => s.Trim())
|
||||
.Where(s => !string.IsNullOrEmpty(s))
|
||||
.ToArray();
|
||||
var sections = ConvertHtmlToLines(entry.Description ?? "");
|
||||
|
||||
column.Item().ExtendVertical().AlignBottom()
|
||||
.Border(5f).BorderColor("#20000000")
|
||||
@@ -134,7 +143,7 @@ namespace SZUAbsolventenverein.Module.HallOfFame.Controllers
|
||||
.PaddingHorizontal(32)
|
||||
.Column(descColumn =>
|
||||
{
|
||||
if (sections.Length > 0)
|
||||
if (sections.Count > 0)
|
||||
{
|
||||
// Überschrift
|
||||
descColumn.Item()
|
||||
@@ -184,5 +193,79 @@ namespace SZUAbsolventenverein.Module.HallOfFame.Controllers
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts HTML content (from Quill.js rich text editor) to a list of plain text lines
|
||||
/// suitable for rendering in QuestPDF.
|
||||
/// Handles: ol/ul lists → numbered/bulleted lines, p → paragraphs, br → newlines,
|
||||
/// strips all other tags and decodes HTML entities.
|
||||
/// </summary>
|
||||
private static List<string> ConvertHtmlToLines(string html)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(html))
|
||||
return new List<string>();
|
||||
|
||||
var lines = new List<string>();
|
||||
|
||||
// Process ordered lists: <ol>...</ol>
|
||||
html = Regex.Replace(html, @"<ol[^>]*>(.*?)</ol>", match =>
|
||||
{
|
||||
var listContent = match.Groups[1].Value;
|
||||
var items = Regex.Matches(listContent, @"<li[^>]*>(.*?)</li>", RegexOptions.Singleline);
|
||||
int counter = 1;
|
||||
var result = "";
|
||||
foreach (Match item in items)
|
||||
{
|
||||
var text = StripHtmlTags(item.Groups[1].Value).Trim();
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
result += $"\n{counter}. {text}";
|
||||
counter++;
|
||||
}
|
||||
return result;
|
||||
}, RegexOptions.Singleline | RegexOptions.IgnoreCase);
|
||||
|
||||
// Process unordered lists: <ul>...</ul>
|
||||
html = Regex.Replace(html, @"<ul[^>]*>(.*?)</ul>", match =>
|
||||
{
|
||||
var listContent = match.Groups[1].Value;
|
||||
var items = Regex.Matches(listContent, @"<li[^>]*>(.*?)</li>", RegexOptions.Singleline);
|
||||
var result = "";
|
||||
foreach (Match item in items)
|
||||
{
|
||||
var text = StripHtmlTags(item.Groups[1].Value).Trim();
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
result += $"\n• {text}";
|
||||
}
|
||||
return result;
|
||||
}, RegexOptions.Singleline | RegexOptions.IgnoreCase);
|
||||
|
||||
// Replace <br>, <br/>, <br /> with newline
|
||||
html = Regex.Replace(html, @"<br\s*/?>", "\n", RegexOptions.IgnoreCase);
|
||||
|
||||
// Replace </p>, </div>, </h1>-</h6> with newline
|
||||
html = Regex.Replace(html, @"</(?:p|div|h[1-6])>", "\n", RegexOptions.IgnoreCase);
|
||||
|
||||
// Strip all remaining HTML tags
|
||||
html = StripHtmlTags(html);
|
||||
|
||||
// Decode HTML entities ( & etc.)
|
||||
html = WebUtility.HtmlDecode(html);
|
||||
|
||||
// Split into lines, trim, filter empty
|
||||
var rawLines = html.Split('\n')
|
||||
.Select(s => s.Trim())
|
||||
.Where(s => !string.IsNullOrEmpty(s))
|
||||
.ToList();
|
||||
|
||||
return rawLines;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Strips all HTML tags from a string, leaving only the text content.
|
||||
/// </summary>
|
||||
private static string StripHtmlTags(string html)
|
||||
{
|
||||
return Regex.Replace(html, @"<[^>]+>", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user