Formatted everything; completed upload capabilities

This commit is contained in:
schnulller
2022-06-04 02:42:01 +02:00
parent 743c88a4e5
commit 37b794ea05
61 changed files with 677 additions and 558 deletions

View File

@@ -1,6 +1,12 @@
{ {
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach",
"processName": "HaWeb",
},
{ {
// Use IntelliSense to find out which attributes exist for C# debugging // Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes // Use hover for the description of the existing attributes
@@ -24,12 +30,7 @@
}, },
"sourceFileMap": { "sourceFileMap": {
"/Views": "${workspaceFolder}/Views" "/Views": "${workspaceFolder}/Views"
} },
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach"
} }
] ]
} }

View File

@@ -4,14 +4,12 @@ using HaDocument.Interfaces;
using HaXMLReader.Interfaces; using HaXMLReader.Interfaces;
using Microsoft.FeatureManagement.Mvc; using Microsoft.FeatureManagement.Mvc;
public class AdminController : Controller public class AdminController : Controller {
{
// DI // DI
private ILibrary _lib; private ILibrary _lib;
private IReaderService _readerService; private IReaderService _readerService;
public AdminController(ILibrary lib, IReaderService readerService) public AdminController(ILibrary lib, IReaderService readerService) {
{
_lib = lib; _lib = lib;
_readerService = readerService; _readerService = readerService;
} }
@@ -19,8 +17,7 @@ public class AdminController : Controller
[Route("Admin")] [Route("Admin")]
[FeatureGate(Features.AdminService)] [FeatureGate(Features.AdminService)]
public IActionResult Index() public IActionResult Index() {
{
return Redirect("/Admin/Upload"); return Redirect("/Admin/Upload");
} }
} }

View File

@@ -7,8 +7,7 @@ using HaDocument.Models;
namespace HaWeb.Controllers; namespace HaWeb.Controllers;
public class Briefecontroller : Controller public class Briefecontroller : Controller {
{
[BindProperty(SupportsGet = true)] [BindProperty(SupportsGet = true)]
public string? id { get; set; } public string? id { get; set; }
@@ -16,16 +15,14 @@ public class Briefecontroller : Controller
private ILibrary _lib; private ILibrary _lib;
private IReaderService _readerService; private IReaderService _readerService;
public Briefecontroller(ILibrary lib, IReaderService readerService) public Briefecontroller(ILibrary lib, IReaderService readerService) {
{
_lib = lib; _lib = lib;
_readerService = readerService; _readerService = readerService;
} }
[Route("Briefe")] [Route("Briefe")]
[Route("Briefe/{id?}")] [Route("Briefe/{id?}")]
public IActionResult Index(string? id) public IActionResult Index(string? id) {
{
// Setup settings and variables // Setup settings and variables
var url = "/Briefe/"; var url = "/Briefe/";
var defaultID = "1"; var defaultID = "1";
@@ -79,19 +76,16 @@ public class Briefecontroller : Controller
return View(model); return View(model);
} }
private IActionResult error404() private IActionResult error404() {
{
Response.StatusCode = 404; Response.StatusCode = 404;
return Redirect("/Error404"); return Redirect("/Error404");
} }
private BriefeMetaViewModel generateMetaViewModel(Meta meta, bool hasMarginals) private BriefeMetaViewModel generateMetaViewModel(Meta meta, bool hasMarginals) {
{
var senders = meta.Senders.Select(x => _lib.Persons[x].Name) ?? new List<string>(); var senders = meta.Senders.Select(x => _lib.Persons[x].Name) ?? new List<string>();
var recivers = meta.Receivers.Select(x => _lib.Persons[x].Name) ?? new List<string>(); var recivers = meta.Receivers.Select(x => _lib.Persons[x].Name) ?? new List<string>();
var zhstring = meta.ZH != null ? HaWeb.HTMLHelpers.LetterHelpers.CreateZHString(meta) : null; var zhstring = meta.ZH != null ? HaWeb.HTMLHelpers.LetterHelpers.CreateZHString(meta) : null;
return new BriefeMetaViewModel(meta, hasMarginals, false) return new BriefeMetaViewModel(meta, hasMarginals, false) {
{
ParsedZHString = zhstring, ParsedZHString = zhstring,
ParsedSenders = HTMLHelpers.StringHelpers.GetEnumerationString(senders), ParsedSenders = HTMLHelpers.StringHelpers.GetEnumerationString(senders),
ParsedReceivers = HTMLHelpers.StringHelpers.GetEnumerationString(recivers) ParsedReceivers = HTMLHelpers.StringHelpers.GetEnumerationString(recivers)

View File

@@ -5,40 +5,32 @@ using HaWeb.Models;
namespace HaWeb.Controllers; namespace HaWeb.Controllers;
[Route("Edition/[action]")] [Route("Edition/[action]")]
public class EditionController : Controller public class EditionController : Controller {
{
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error() public IActionResult Error() {
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
} }
public IActionResult Kontakt() public IActionResult Kontakt() {
{
return View(); return View();
} }
public IActionResult Mitwirkende() public IActionResult Mitwirkende() {
{
return View(); return View();
} }
public IActionResult Richtlinien() public IActionResult Richtlinien() {
{
return View(); return View();
} }
public IActionResult Werkausgabe() public IActionResult Werkausgabe() {
{
return View(); return View();
} }
public IActionResult Editionsgeschichte() public IActionResult Editionsgeschichte() {
{
return View(); return View();
} }
public IActionResult Datenschutzerklaerung() public IActionResult Datenschutzerklaerung() {
{
return View(); return View();
} }
} }

View File

@@ -5,8 +5,7 @@ using HaWeb.Models;
namespace HaWeb.Controllers; namespace HaWeb.Controllers;
public class ErrorController : Controller public class ErrorController : Controller {
{
[Route("Error404/")] [Route("Error404/")]
public IActionResult ErrorNotFound() { public IActionResult ErrorNotFound() {
return View(); return View();

View File

@@ -4,18 +4,15 @@ using HaWeb.Models;
namespace HaWeb.Controllers; namespace HaWeb.Controllers;
public class HomeController : Controller public class HomeController : Controller {
{
[Route("")] [Route("")]
public IActionResult Index() public IActionResult Index() {
{
return Redirect("/Suche"); return Redirect("/Suche");
} }
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error() public IActionResult Error() {
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
} }
} }

View File

@@ -6,8 +6,7 @@ using HaWeb.Models;
namespace HaWeb.Controllers; namespace HaWeb.Controllers;
public class LegacyContoller : Controller public class LegacyContoller : Controller {
{
[Route("Supplementa/")] [Route("Supplementa/")]
[Route("Supplementa/Register")] [Route("Supplementa/Register")]
[Route("Supplementa/Register/{id?}")] [Route("Supplementa/Register/{id?}")]

View File

@@ -12,8 +12,7 @@ using System.Collections.Concurrent;
namespace HaWeb.Controllers; namespace HaWeb.Controllers;
[Route("Register/[action]/{id?}")] [Route("Register/[action]/{id?}")]
public class RegisterController : Controller public class RegisterController : Controller {
{
[BindProperty(SupportsGet = true)] [BindProperty(SupportsGet = true)]
public string? search { get; set; } public string? search { get; set; }
@@ -24,14 +23,12 @@ public class RegisterController : Controller
private ILibrary _lib; private ILibrary _lib;
private IReaderService _readerService; private IReaderService _readerService;
public RegisterController(ILibrary lib, IReaderService readerService) public RegisterController(ILibrary lib, IReaderService readerService) {
{
_lib = lib; _lib = lib;
_readerService = readerService; _readerService = readerService;
} }
public IActionResult Register(string? id) public IActionResult Register(string? id) {
{
// Setup settings and variables // Setup settings and variables
var url = "/Register/Register/"; var url = "/Register/Register/";
var category = "neuzeit"; var category = "neuzeit";
@@ -52,15 +49,12 @@ public class RegisterController : Controller
// Parsing // Parsing
var res = new List<CommentModel>(); var res = new List<CommentModel>();
foreach (var comm in comments) foreach (var comm in comments) {
{
var parsedComment = HTMLHelpers.CommentHelpers.CreateHTML(_lib, _readerService, comm, category, Settings.ParsingState.CommentType.Comment); var parsedComment = HTMLHelpers.CommentHelpers.CreateHTML(_lib, _readerService, comm, category, Settings.ParsingState.CommentType.Comment);
List<string>? parsedSubComments = null; List<string>? parsedSubComments = null;
if (comm.Kommentare != null) if (comm.Kommentare != null) {
{
parsedSubComments = new List<string>(); parsedSubComments = new List<string>();
foreach (var subcomm in comm.Kommentare.OrderBy(x => x.Value.Order)) foreach (var subcomm in comm.Kommentare.OrderBy(x => x.Value.Order)) {
{
parsedSubComments.Add(HTMLHelpers.CommentHelpers.CreateHTML(_lib, _readerService, subcomm.Value, category, Settings.ParsingState.CommentType.Subcomment)); parsedSubComments.Add(HTMLHelpers.CommentHelpers.CreateHTML(_lib, _readerService, subcomm.Value, category, Settings.ParsingState.CommentType.Subcomment));
} }
} }
@@ -68,8 +62,7 @@ public class RegisterController : Controller
} }
// Model instantiation // Model instantiation
var model = new RegisterViewModel(category, this.id, res, title) var model = new RegisterViewModel(category, this.id, res, title) {
{
AvailableCategories = availableCategories, AvailableCategories = availableCategories,
}; };
@@ -77,8 +70,7 @@ public class RegisterController : Controller
return View("Index", model); return View("Index", model);
} }
public IActionResult Bibelstellen(string? id) public IActionResult Bibelstellen(string? id) {
{
// Setup settings and variables // Setup settings and variables
var url = "/Register/Bibelstellen/"; var url = "/Register/Bibelstellen/";
var category = "bibel"; var category = "bibel";
@@ -99,15 +91,12 @@ public class RegisterController : Controller
// Parsing // Parsing
var res = new List<CommentModel>(); var res = new List<CommentModel>();
foreach (var comm in comments) foreach (var comm in comments) {
{
var parsedComment = HTMLHelpers.CommentHelpers.CreateHTML(_lib, _readerService, comm, category, Settings.ParsingState.CommentType.Comment); var parsedComment = HTMLHelpers.CommentHelpers.CreateHTML(_lib, _readerService, comm, category, Settings.ParsingState.CommentType.Comment);
List<string>? parsedSubComments = null; List<string>? parsedSubComments = null;
if (comm.Kommentare != null) if (comm.Kommentare != null) {
{
parsedSubComments = new List<string>(); parsedSubComments = new List<string>();
foreach (var subcomm in comm.Kommentare.OrderBy(x => x.Value.Lemma.Length).ThenBy(x => x.Value.Lemma)) foreach (var subcomm in comm.Kommentare.OrderBy(x => x.Value.Lemma.Length).ThenBy(x => x.Value.Lemma)) {
{
parsedSubComments.Add(HTMLHelpers.CommentHelpers.CreateHTML(_lib, _readerService, subcomm.Value, category, Settings.ParsingState.CommentType.Subcomment)); parsedSubComments.Add(HTMLHelpers.CommentHelpers.CreateHTML(_lib, _readerService, subcomm.Value, category, Settings.ParsingState.CommentType.Subcomment));
} }
} }
@@ -115,8 +104,7 @@ public class RegisterController : Controller
} }
// Model instantiation // Model instantiation
var model = new RegisterViewModel(category, this.id, res, title) var model = new RegisterViewModel(category, this.id, res, title) {
{
AvailableCategories = availableCategories, AvailableCategories = availableCategories,
}; };
@@ -124,8 +112,7 @@ public class RegisterController : Controller
return View("Index", model); return View("Index", model);
} }
public IActionResult Forschung(string? id) public IActionResult Forschung(string? id) {
{
// Setup settings and variables // Setup settings and variables
var url = "/Register/Forschung/"; var url = "/Register/Forschung/";
var category = "forschung"; var category = "forschung";
@@ -144,8 +131,7 @@ public class RegisterController : Controller
IOrderedEnumerable<Comment>? comments = null; IOrderedEnumerable<Comment>? comments = null;
if (this.id == "EDITIONEN") { if (this.id == "EDITIONEN") {
comments = _lib.CommentsByCategory[this.id.ToLower()].OrderBy(x => x.Index); comments = _lib.CommentsByCategory[this.id.ToLower()].OrderBy(x => x.Index);
} } else {
else {
comments = _lib.CommentsByCategoryLetter[category][this.id].OrderBy(x => x.Index); comments = _lib.CommentsByCategoryLetter[category][this.id].OrderBy(x => x.Index);
} }
var availableCategories = _lib.CommentsByCategoryLetter[category].Select(x => (x.Key.ToUpper(), url + x.Key.ToUpper())).OrderBy(x => x.Item1).ToList(); var availableCategories = _lib.CommentsByCategoryLetter[category].Select(x => (x.Key.ToUpper(), url + x.Key.ToUpper())).OrderBy(x => x.Item1).ToList();
@@ -154,15 +140,12 @@ public class RegisterController : Controller
// Parsing // Parsing
var res = new List<CommentModel>(); var res = new List<CommentModel>();
foreach (var comm in comments) foreach (var comm in comments) {
{
var parsedComment = HTMLHelpers.CommentHelpers.CreateHTML(_lib, _readerService, comm, category, Settings.ParsingState.CommentType.Comment); var parsedComment = HTMLHelpers.CommentHelpers.CreateHTML(_lib, _readerService, comm, category, Settings.ParsingState.CommentType.Comment);
List<string>? parsedSubComments = null; List<string>? parsedSubComments = null;
if (comm.Kommentare != null) if (comm.Kommentare != null) {
{
parsedSubComments = new List<string>(); parsedSubComments = new List<string>();
foreach (var subcomm in comm.Kommentare.OrderBy(x => x.Value.Order)) foreach (var subcomm in comm.Kommentare.OrderBy(x => x.Value.Order)) {
{
parsedSubComments.Add(HTMLHelpers.CommentHelpers.CreateHTML(_lib, _readerService, subcomm.Value, category, Settings.ParsingState.CommentType.Subcomment)); parsedSubComments.Add(HTMLHelpers.CommentHelpers.CreateHTML(_lib, _readerService, subcomm.Value, category, Settings.ParsingState.CommentType.Subcomment));
} }
} }
@@ -170,8 +153,7 @@ public class RegisterController : Controller
} }
// Model instantiation // Model instantiation
var model = new RegisterViewModel(category, this.id, res, title) var model = new RegisterViewModel(category, this.id, res, title) {
{
AvailableCategories = availableCategories, AvailableCategories = availableCategories,
AvailableSideCategories = AvailableSideCategories AvailableSideCategories = AvailableSideCategories
}; };
@@ -180,22 +162,18 @@ public class RegisterController : Controller
return View("Index", model); return View("Index", model);
} }
private void normalizeID(string? id, string defaultid) private void normalizeID(string? id, string defaultid) {
{
this.id = this.id.ToUpper(); this.id = this.id.ToUpper();
} }
private bool validationCheck(HashSet<string> permitted) private bool validationCheck(HashSet<string> permitted) {
{ if (!permitted.Contains(id)) {
if (!permitted.Contains(id))
{
return false; return false;
} }
return true; return true;
} }
private IActionResult error404() private IActionResult error404() {
{
Response.StatusCode = 404; Response.StatusCode = 404;
return Redirect("/Error404"); return Redirect("/Error404");
} }

View File

@@ -4,18 +4,15 @@ using HaWeb.Models;
namespace HaWeb.Controllers; namespace HaWeb.Controllers;
public class SucheController : Controller public class SucheController : Controller {
{
[Route("Suche")] [Route("Suche")]
public IActionResult Index() public IActionResult Index() {
{
return View(); return View();
} }
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error() public IActionResult Error() {
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
} }
} }

View File

@@ -4,14 +4,12 @@ using HaDocument.Interfaces;
using HaXMLReader.Interfaces; using HaXMLReader.Interfaces;
using Microsoft.FeatureManagement.Mvc; using Microsoft.FeatureManagement.Mvc;
public class UpdateController : Controller public class UpdateController : Controller {
{
// DI // DI
private ILibrary _lib; private ILibrary _lib;
private IReaderService _readerService; private IReaderService _readerService;
public UpdateController(ILibrary lib, IReaderService readerService) public UpdateController(ILibrary lib, IReaderService readerService) {
{
_lib = lib; _lib = lib;
_readerService = readerService; _readerService = readerService;
} }
@@ -19,8 +17,7 @@ public class UpdateController : Controller
[Route("Admin/Update")] [Route("Admin/Update")]
[FeatureGate(Features.UpdateService)] [FeatureGate(Features.UpdateService)]
public IActionResult Index() public IActionResult Index() {
{
return View("../Admin/Upload/Index"); return View("../Admin/Upload/Index");
} }
} }

View File

@@ -21,8 +21,7 @@ using System.Xml.Linq;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Text.Json; using System.Text.Json;
public class UploadController : Controller public class UploadController : Controller {
{
// DI // DI
private ILibrary _lib; private ILibrary _lib;
private IReaderService _readerService; private IReaderService _readerService;
@@ -35,8 +34,7 @@ public class UploadController : Controller
private static readonly FormOptions _defaultFormOptions = new FormOptions(); private static readonly FormOptions _defaultFormOptions = new FormOptions();
public UploadController(ILibrary lib, IReaderService readerService, IXMLService xmlService, IConfiguration config) public UploadController(ILibrary lib, IReaderService readerService, IXMLService xmlService, IConfiguration config) {
{
_lib = lib; _lib = lib;
_readerService = readerService; _readerService = readerService;
_xmlService = xmlService; _xmlService = xmlService;
@@ -49,27 +47,28 @@ public class UploadController : Controller
} }
[HttpGet] [HttpGet]
[Route("Admin/Upload")] [Route("Admin/Upload/{id?}")]
[FeatureGate(Features.UploadService)] [FeatureGate(Features.UploadService)]
[GenerateAntiforgeryTokenCookie] [GenerateAntiforgeryTokenCookie]
public IActionResult Index() public IActionResult Index(string? id) {
{
var model = new UploadViewModel(); var model = new UploadViewModel();
model.AvailableRoots = _xmlService.GetRoots().Select(x => (x.Type, "")).ToList(); model.AvailableRoots = _xmlService.GetRoots();
model.UsedFiles = _xmlService.GetUsed();
var hello = "mama";
return View("../Admin/Upload/Index", model); return View("../Admin/Upload/Index", model);
} }
//// UPLOAD //// //// UPLOAD ////
[HttpPost] [HttpPost]
[Route("Admin/Upload")] [Route("Admin/Upload")]
[DisableFormValueModelBinding] [DisableFormValueModelBinding]
[ValidateAntiForgeryToken] [ValidateAntiForgeryToken]
public async Task<IActionResult> Post() { public async Task<IActionResult> Post() {
//// 1. Stage: Check Request format and request spec List<XMLRootDocument>? docs = null;
//// 1. Stage: Check Request format and request spec
// Checks the Content-Type Field (must be multipart + Boundary) // Checks the Content-Type Field (must be multipart + Boundary)
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType)) if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType)) {
{
ModelState.AddModelError("Error", $"Wrong / No Content Type on the Request"); ModelState.AddModelError("Error", $"Wrong / No Content Type on the Request");
return BadRequest(ModelState); return BadRequest(ModelState);
} }
@@ -80,80 +79,63 @@ public class UploadController : Controller
MultipartSection? section = null; MultipartSection? section = null;
try { try {
section = await reader.ReadNextSectionAsync(); section = await reader.ReadNextSectionAsync();
} } catch (Exception ex) {
catch (Exception ex) {
ModelState.AddModelError("Error", "The Request is bad: " + ex.Message); ModelState.AddModelError("Error", "The Request is bad: " + ex.Message);
} }
while (section != null) while (section != null) {
{
// Multipart document content disposition header read for a section: // Multipart document content disposition header read for a section:
// Starts with boundary, contains field name, content-dispo, filename, content-type // Starts with boundary, contains field name, content-dispo, filename, content-type
var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition); var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition);
if (hasContentDispositionHeader && contentDisposition != null) if (hasContentDispositionHeader && contentDisposition != null) {
{
// Checks if it is a section with content-disposition, name, filename // Checks if it is a section with content-disposition, name, filename
if (!MultipartRequestHelper.HasFileContentDisposition(contentDisposition)) if (!MultipartRequestHelper.HasFileContentDisposition(contentDisposition)) {
{
ModelState.AddModelError("Error", $"Wrong Content-Dispostion Headers in Multipart Document"); ModelState.AddModelError("Error", $"Wrong Content-Dispostion Headers in Multipart Document");
return BadRequest(ModelState); return BadRequest(ModelState);
} }
//// 2. Stage: Check File. Sanity checks on the file on a byte level, extension checking, is it empty etc. //// 2. Stage: Check File. Sanity checks on the file on a byte level, extension checking, is it empty etc.
var streamedFileContent = await XMLFileHelpers.ProcessStreamedFile( var streamedFileContent = await XMLFileHelpers.ProcessStreamedFile(
section, contentDisposition, ModelState, section, contentDisposition, ModelState,
_permittedExtensions, _fileSizeLimit); _permittedExtensions, _fileSizeLimit);
if (!ModelState.IsValid || streamedFileContent == null) if (!ModelState.IsValid || streamedFileContent == null)
return BadRequest(ModelState); return BadRequest(ModelState);
//// 3. Stage: Valid XML checking using a simple XDocument.Load() //// 3. Stage: Valid XML checking using a simple XDocument.Load()
var xdocument = await XDocumentFileHelper.ProcessStreamedFile(streamedFileContent, ModelState); var xdocument = await XDocumentFileHelper.ProcessStreamedFile(streamedFileContent, ModelState);
if (!ModelState.IsValid || xdocument == null) if (!ModelState.IsValid || xdocument == null)
return UnprocessableEntity(ModelState); return UnprocessableEntity(ModelState);
//// 4. Stage: Is it a Hamann-Document? What kind? //// 4. Stage: Is it a Hamann-Document? What kind?
var docs = _xmlService.ProbeHamannFile(xdocument, ModelState); var retdocs = await _xmlService.ProbeHamannFile(xdocument, ModelState);
if (!ModelState.IsValid || docs == null || !docs.Any()) if (!ModelState.IsValid || retdocs == null || !retdocs.Any())
return UnprocessableEntity(ModelState); return UnprocessableEntity(ModelState);
//// 5. Stage: Saving the File(s) //// 5. Stage: Saving the File(s)
foreach (var doc in docs) { foreach (var doc in retdocs) {
var type = doc.Prefix; await _xmlService.UpdateAvailableFiles(doc, _targetFilePath, ModelState);
var directory = Path.Combine(_targetFilePath, type); if (!ModelState.IsValid) return StatusCode(500, ModelState);
if (!Directory.Exists(directory)) if (docs == null) docs = new List<XMLRootDocument>();
Directory.CreateDirectory(directory); docs.Add(doc);
var path = Path.Combine(directory, doc.FileName);
try {
using (var targetStream = System.IO.File.Create(path))
await doc.Save(targetStream, ModelState);
if (!ModelState.IsValid) return StatusCode(500, ModelState);
}
catch (Exception ex) {
ModelState.AddModelError("Error", "Speichern der Datei fehlgeschlagen: " + ex.Message);
return StatusCode(500, ModelState);
}
} }
// 6. State: Returning Ok, and redirecting
JsonSerializerOptions options = new() {
ReferenceHandler = ReferenceHandler.Preserve
};
string json = JsonSerializer.Serialize(docs);
return Created(nameof(UploadController), json);
} }
try try {
{
section = await reader.ReadNextSectionAsync(); section = await reader.ReadNextSectionAsync();
} } catch (Exception ex) {
catch (Exception ex)
{
ModelState.AddModelError("Error", "The Request is bad: " + ex.Message); ModelState.AddModelError("Error", "The Request is bad: " + ex.Message);
} }
} }
//// Success! Return Last Created File View // 6. Stage: Success! Returning Ok, and redirecting
return Created(nameof(UploadController), null); JsonSerializerOptions options = new() {
ReferenceHandler = ReferenceHandler.Preserve,
Converters = {
new IdentificationStringJSONConverter()
}
};
string json = JsonSerializer.Serialize(docs);
return Created(nameof(UploadController), json);
} }
} }

View File

@@ -3,12 +3,10 @@ using System;
using System.IO; using System.IO;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
public static class MultipartRequestHelper public static class MultipartRequestHelper {
{
// Content-Type: multipart/form-data; boundary="----WebKitFormBoundarymx2fSWqWSd0OxQqq" // Content-Type: multipart/form-data; boundary="----WebKitFormBoundarymx2fSWqWSd0OxQqq"
// The spec at https://tools.ietf.org/html/rfc2046#section-5.1 states that 70 characters is a reasonable limit. // The spec at https://tools.ietf.org/html/rfc2046#section-5.1 states that 70 characters is a reasonable limit.
public static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit) public static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit) {
{
var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary).Value; var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary).Value;
if (string.IsNullOrWhiteSpace(boundary)) if (string.IsNullOrWhiteSpace(boundary))
@@ -23,8 +21,7 @@ public static class MultipartRequestHelper
public static bool IsMultipartContentType(string? contentType) public static bool IsMultipartContentType(string? contentType)
=> !string.IsNullOrEmpty(contentType) && contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0; => !string.IsNullOrEmpty(contentType) && contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0;
public static bool HasFormDataContentDisposition(ContentDispositionHeaderValue contentDisposition) public static bool HasFormDataContentDisposition(ContentDispositionHeaderValue contentDisposition) {
{
// Content-Disposition: form-data; name="key"; // Content-Disposition: form-data; name="key";
return contentDisposition != null return contentDisposition != null
&& contentDisposition.DispositionType.Equals("form-data") && contentDisposition.DispositionType.Equals("form-data")
@@ -32,8 +29,7 @@ public static class MultipartRequestHelper
&& string.IsNullOrEmpty(contentDisposition.FileNameStar.Value); && string.IsNullOrEmpty(contentDisposition.FileNameStar.Value);
} }
public static bool HasFileContentDisposition(ContentDispositionHeaderValue? contentDisposition) public static bool HasFileContentDisposition(ContentDispositionHeaderValue? contentDisposition) {
{
return contentDisposition != null return contentDisposition != null
&& contentDisposition.DispositionType.Equals("form-data") && contentDisposition.DispositionType.Equals("form-data")
&& (!string.IsNullOrEmpty(contentDisposition.FileName.Value) && (!string.IsNullOrEmpty(contentDisposition.FileName.Value)

View File

@@ -6,8 +6,7 @@ using System.Xml;
public static class XDocumentFileHelper { public static class XDocumentFileHelper {
private readonly static XmlReaderSettings _Settings = new XmlReaderSettings() private readonly static XmlReaderSettings _Settings = new XmlReaderSettings() {
{
CloseInput = true, CloseInput = true,
CheckCharacters = false, CheckCharacters = false,
ConformanceLevel = ConformanceLevel.Fragment, ConformanceLevel = ConformanceLevel.Fragment,
@@ -17,19 +16,14 @@ public static class XDocumentFileHelper {
}; };
public static async Task<XDocument?> ProcessStreamedFile(byte[] bytes, ModelStateDictionary modelState) { public static async Task<XDocument?> ProcessStreamedFile(byte[] bytes, ModelStateDictionary modelState) {
try try {
{ using (var stream = new MemoryStream(bytes)) {
using (var stream = new MemoryStream(bytes)) using (var xmlreader = XmlReader.Create(stream, _Settings)) {
{
using (var xmlreader = XmlReader.Create(stream, _Settings))
{
return XDocument.Load(xmlreader, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); return XDocument.Load(xmlreader, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
} }
} }
} } catch (Exception ex) {
catch (Exception ex)
{
modelState.AddModelError("Error", $"Kein gültiges XML-Dokument geladen. Error: {ex.Message}"); modelState.AddModelError("Error", $"Kein gültiges XML-Dokument geladen. Error: {ex.Message}");
} }

View File

@@ -12,8 +12,7 @@ using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.WebUtilities; using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
public static class XMLFileHelpers public static class XMLFileHelpers {
{
// File Signatures Database (https://www.filesignatures.net/) // File Signatures Database (https://www.filesignatures.net/)
private static readonly Dictionary<string, List<byte[]>> _fileSignature = new Dictionary<string, List<byte[]>> private static readonly Dictionary<string, List<byte[]>> _fileSignature = new Dictionary<string, List<byte[]>>
{ {
@@ -142,19 +141,15 @@ public static class XMLFileHelpers
public static async Task<byte[]?> ProcessStreamedFile( public static async Task<byte[]?> ProcessStreamedFile(
MultipartSection section, ContentDispositionHeaderValue contentDisposition, MultipartSection section, ContentDispositionHeaderValue contentDisposition,
ModelStateDictionary modelState, string[] permittedExtensions, long sizeLimit) ModelStateDictionary modelState, string[] permittedExtensions, long sizeLimit) {
{ try {
try using (var memoryStream = new MemoryStream()) {
{
using (var memoryStream = new MemoryStream())
{
await section.Body.CopyToAsync(memoryStream); await section.Body.CopyToAsync(memoryStream);
// Check if the file is empty or exceeds the size limit. // Check if the file is empty or exceeds the size limit.
if (memoryStream.Length == 0) if (memoryStream.Length == 0)
modelState.AddModelError("Error", "The file is empty."); modelState.AddModelError("Error", "The file is empty.");
else if (memoryStream.Length > sizeLimit) else if (memoryStream.Length > sizeLimit) {
{
var megabyteSizeLimit = sizeLimit / 1048576; var megabyteSizeLimit = sizeLimit / 1048576;
modelState.AddModelError("Error", $"The file exceeds {megabyteSizeLimit:N1} MB."); modelState.AddModelError("Error", $"The file exceeds {megabyteSizeLimit:N1} MB.");
} }
@@ -168,17 +163,14 @@ public static class XMLFileHelpers
// Return the File as a byte array // Return the File as a byte array
else return memoryStream.ToArray(); else return memoryStream.ToArray();
} }
} } catch (Exception ex) {
catch (Exception ex)
{
modelState.AddModelError("Error", $"The upload failed. Error: {ex.Message}"); modelState.AddModelError("Error", $"The upload failed. Error: {ex.Message}");
} }
return null; return null;
} }
private static bool IsValidFileExtensionAndSignature(string fileName, Stream data, string[] permittedExtensions) private static bool IsValidFileExtensionAndSignature(string fileName, Stream data, string[] permittedExtensions) {
{
if (string.IsNullOrEmpty(fileName) || data == null || data.Length == 0) if (string.IsNullOrEmpty(fileName) || data == null || data.Length == 0)
return false; return false;
@@ -189,8 +181,7 @@ public static class XMLFileHelpers
data.Position = 0; data.Position = 0;
using (var reader = new BinaryReader(data)) using (var reader = new BinaryReader(data)) {
{
var signatures = _fileSignature[ext]; var signatures = _fileSignature[ext];
var headerBytes = reader.ReadBytes(signatures.Max(m => m.Length)); var headerBytes = reader.ReadBytes(signatures.Max(m => m.Length));
return signatures.Any(signature => return signatures.Any(signature =>

View File

@@ -4,10 +4,8 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
public class GenerateAntiforgeryTokenCookieAttribute : ResultFilterAttribute public class GenerateAntiforgeryTokenCookieAttribute : ResultFilterAttribute {
{ public override void OnResultExecuting(ResultExecutingContext context) {
public override void OnResultExecuting(ResultExecutingContext context)
{
var antiforgery = context.HttpContext.RequestServices.GetService<IAntiforgery>(); var antiforgery = context.HttpContext.RequestServices.GetService<IAntiforgery>();
// Send the request token as a JavaScript-readable cookie // Send the request token as a JavaScript-readable cookie
@@ -19,8 +17,7 @@ public class GenerateAntiforgeryTokenCookieAttribute : ResultFilterAttribute
new CookieOptions() { HttpOnly = false }); new CookieOptions() { HttpOnly = false });
} }
public override void OnResultExecuted(ResultExecutedContext context) public override void OnResultExecuted(ResultExecutedContext context) {
{
} }
} }

View File

@@ -4,17 +4,14 @@ using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter {
{ public void OnResourceExecuting(ResourceExecutingContext context) {
public void OnResourceExecuting(ResourceExecutingContext context)
{
var factories = context.ValueProviderFactories; var factories = context.ValueProviderFactories;
factories.RemoveType<FormValueProviderFactory>(); factories.RemoveType<FormValueProviderFactory>();
factories.RemoveType<FormFileValueProviderFactory>(); factories.RemoveType<FormFileValueProviderFactory>();
factories.RemoveType<JQueryFormValueProviderFactory>(); factories.RemoveType<JQueryFormValueProviderFactory>();
} }
public void OnResourceExecuted(ResourceExecutedContext context) public void OnResourceExecuted(ResourceExecutedContext context) {
{
} }
} }

View File

@@ -7,16 +7,14 @@ using HaWeb.Settings.ParsingRules;
using HaWeb.Settings.ParsingState; using HaWeb.Settings.ParsingState;
using System.Text; using System.Text;
public static class CommentHelpers public static class CommentHelpers {
{
private static readonly string DEFAULTELEMENT = HaWeb.Settings.HTML.DEFAULTELEMENT; private static readonly string DEFAULTELEMENT = HaWeb.Settings.HTML.DEFAULTELEMENT;
private static readonly string BACKLINKSCLASS = HaWeb.Settings.CSSClasses.BACKLINKSCLASS; private static readonly string BACKLINKSCLASS = HaWeb.Settings.CSSClasses.BACKLINKSCLASS;
private static readonly string LETLINKCLASS = HaWeb.Settings.CSSClasses.LETLINKCLASS; private static readonly string LETLINKCLASS = HaWeb.Settings.CSSClasses.LETLINKCLASS;
private static readonly string COMMENTHEADCLASS = HaWeb.Settings.CSSClasses.COMMENTHEADCLASS; private static readonly string COMMENTHEADCLASS = HaWeb.Settings.CSSClasses.COMMENTHEADCLASS;
private static readonly string BACKLINKSHKBCLASS = HaWeb.Settings.CSSClasses.BACKLINKSHKBCLASS; private static readonly string BACKLINKSHKBCLASS = HaWeb.Settings.CSSClasses.BACKLINKSHKBCLASS;
public static string CreateHTML(ILibrary lib, IReaderService readerService, Comment comment, string category, CommentType type) public static string CreateHTML(ILibrary lib, IReaderService readerService, Comment comment, string category, CommentType type) {
{
var sb = new StringBuilder(); var sb = new StringBuilder();
var rd = readerService.RequestStringReader(comment.Lemma); var rd = readerService.RequestStringReader(comment.Lemma);
var commentState = new CommentState(category, type); var commentState = new CommentState(category, type);
@@ -28,17 +26,13 @@ public static class CommentHelpers
.Where(x => lib.Metas.ContainsKey(x.Letter)) .Where(x => lib.Metas.ContainsKey(x.Letter))
.OrderBy(x => lib.Metas[x.Letter].Sort) .OrderBy(x => lib.Metas[x.Letter].Sort)
.ThenBy(x => lib.Metas[x.Letter].Order) : null; .ThenBy(x => lib.Metas[x.Letter].Order) : null;
if (backlinks != null) if (backlinks != null) {
{
sb.Append(HTMLHelpers.TagHelpers.CreateElement(DEFAULTELEMENT, BACKLINKSCLASS)); sb.Append(HTMLHelpers.TagHelpers.CreateElement(DEFAULTELEMENT, BACKLINKSCLASS));
var arrow = false; var arrow = false;
foreach (var blk in backlinks) foreach (var blk in backlinks) {
{
var let = lib.Metas.ContainsKey(blk.Letter) ? lib.Metas[blk.Letter] : null; var let = lib.Metas.ContainsKey(blk.Letter) ? lib.Metas[blk.Letter] : null;
if (let != null) if (let != null) {
{ if (!arrow) {
if (!arrow)
{
sb.Append(HTMLHelpers.TagHelpers.CreateElement(DEFAULTELEMENT, BACKLINKSHKBCLASS)); sb.Append(HTMLHelpers.TagHelpers.CreateElement(DEFAULTELEMENT, BACKLINKSHKBCLASS));
sb.Append("HKB&nbsp;"); sb.Append("HKB&nbsp;");
sb.Append(HTMLHelpers.TagHelpers.CreateEndElement(DEFAULTELEMENT)); sb.Append(HTMLHelpers.TagHelpers.CreateEndElement(DEFAULTELEMENT));

View File

@@ -14,36 +14,27 @@ public static class ConversionHelpers {
{'M', 1000} {'M', 1000}
}; };
public static int RomanToInteger(string roman) public static int RomanToInteger(string roman) {
{
var ro = roman.ToUpper(); var ro = roman.ToUpper();
int number = 0; int number = 0;
for (int i = 0; i < roman.Length; i++) for (int i = 0; i < roman.Length; i++) {
{ if (RomanMap.ContainsKey(ro[i]) && (i + 1 >= ro.Length || RomanMap.ContainsKey(ro[i + 1]))) {
if (RomanMap.ContainsKey(ro[i]) && (i + 1 >= ro.Length || RomanMap.ContainsKey(ro[i + 1]))) if (i + 1 < ro.Length && RomanMap[ro[i]] < RomanMap[ro[i + 1]]) {
{
if (i + 1 < ro.Length && RomanMap[ro[i]] < RomanMap[ro[i + 1]])
{
number -= RomanMap[ro[i]]; number -= RomanMap[ro[i]];
} } else {
else
{
number += RomanMap[ro[i]]; number += RomanMap[ro[i]];
} }
} } else return 0;
else return 0;
} }
return number; return number;
} }
public static int RomanOrNumberToInt(string number) public static int RomanOrNumberToInt(string number) {
{
var a = 0; var a = 0;
if (Int32.TryParse(number, out a)) return a; if (Int32.TryParse(number, out a)) return a;
else return RomanToInteger(number); else return RomanToInteger(number);
} }
public static string ToRoman(int number) public static string ToRoman(int number) {
{
if ((number < 0) || (number > 3999)) return string.Empty; if ((number < 0) || (number > 3999)) return string.Empty;
if (number < 1) return string.Empty; if (number < 1) return string.Empty;
if (number >= 1000) return "M" + ToRoman(number - 1000); if (number >= 1000) return "M" + ToRoman(number - 1000);

View File

@@ -11,10 +11,8 @@ using System.Xml.Linq;
using HaWeb.Settings.ParsingState; using HaWeb.Settings.ParsingState;
using HaWeb.Settings.ParsingRules; using HaWeb.Settings.ParsingRules;
public static class LetterHelpers public static class LetterHelpers {
{ public static LetterState CreateLetter(ILibrary lib, IReaderService readerService, Meta meta, Letter letter, IEnumerable<Marginal>? marginals, IEnumerable<Hand>? hands, IEnumerable<Editreason>? edits) {
public static LetterState CreateLetter(ILibrary lib, IReaderService readerService, Meta meta, Letter letter, IEnumerable<Marginal>? marginals, IEnumerable<Hand>? hands, IEnumerable<Editreason>? edits)
{
var rd = readerService.RequestStringReader(letter.Element); var rd = readerService.RequestStringReader(letter.Element);
var letterState = new LetterState(lib, readerService, meta, marginals, hands, edits); var letterState = new LetterState(lib, readerService, meta, marginals, hands, edits);
new HaWeb.HTMLParser.XMLHelper<LetterState>(letterState, rd, letterState.sb_lettertext, LetterRules.OTagRules, LetterRules.STagRules, LetterRules.CTagRules, LetterRules.TextRules, LetterRules.WhitespaceRules); new HaWeb.HTMLParser.XMLHelper<LetterState>(letterState, rd, letterState.sb_lettertext, LetterRules.OTagRules, LetterRules.STagRules, LetterRules.CTagRules, LetterRules.TextRules, LetterRules.WhitespaceRules);
@@ -23,8 +21,7 @@ public static class LetterHelpers
return letterState; return letterState;
} }
public static TraditionState CreateTraditions(ILibrary lib, IReaderService readerService, IEnumerable<Marginal>? marginals, Tradition tradition, IEnumerable<Hand>? hands, IEnumerable<Editreason>? edits) public static TraditionState CreateTraditions(ILibrary lib, IReaderService readerService, IEnumerable<Marginal>? marginals, Tradition tradition, IEnumerable<Hand>? hands, IEnumerable<Editreason>? edits) {
{
var rd = readerService.RequestStringReader(tradition.Element); var rd = readerService.RequestStringReader(tradition.Element);
var traditionState = new TraditionState(lib, rd, readerService, marginals, hands, edits); var traditionState = new TraditionState(lib, rd, readerService, marginals, hands, edits);
new HaWeb.HTMLParser.XMLHelper<TraditionState>(traditionState, rd, traditionState.sb_tradition, TraditionRules.OTagRules, TraditionRules.STagRules, TraditionRules.CTagRules, TraditionRules.TextRules, TraditionRules.WhitespaceRules); new HaWeb.HTMLParser.XMLHelper<TraditionState>(traditionState, rd, traditionState.sb_tradition, TraditionRules.OTagRules, TraditionRules.STagRules, TraditionRules.CTagRules, TraditionRules.TextRules, TraditionRules.WhitespaceRules);
@@ -33,13 +30,11 @@ public static class LetterHelpers
return traditionState; return traditionState;
} }
public static List<(string, string, string, string, string, string)> CreateEdits(ILibrary lib, IReaderService readerService, IEnumerable<Editreason> editreasons) public static List<(string, string, string, string, string, string)> CreateEdits(ILibrary lib, IReaderService readerService, IEnumerable<Editreason> editreasons) {
{
editreasons = editreasons.OrderBy(x => HaWeb.HTMLHelpers.ConversionHelpers.RomanOrNumberToInt(x.StartPage)).ThenBy(x => HaWeb.HTMLHelpers.ConversionHelpers.RomanOrNumberToInt(x.StartLine)); editreasons = editreasons.OrderBy(x => HaWeb.HTMLHelpers.ConversionHelpers.RomanOrNumberToInt(x.StartPage)).ThenBy(x => HaWeb.HTMLHelpers.ConversionHelpers.RomanOrNumberToInt(x.StartLine));
var editstrings = new List<(string, string, string, string, string, string)>(); var editstrings = new List<(string, string, string, string, string, string)>();
var editsState = new EditState(); var editsState = new EditState();
foreach (var edit in editreasons) foreach (var edit in editreasons) {
{
var currstring = edit.StartPage + "/" + edit.StartLine; var currstring = edit.StartPage + "/" + edit.StartLine;
var endstring = ""; var endstring = "";
var refstring = ""; var refstring = "";
@@ -49,8 +44,7 @@ public static class LetterHelpers
endstring += edit.EndLine; endstring += edit.EndLine;
editsState.sb_edits.Append(HaWeb.HTMLHelpers.TagHelpers.CreateElement("div", "edit")); editsState.sb_edits.Append(HaWeb.HTMLHelpers.TagHelpers.CreateElement("div", "edit"));
if (!String.IsNullOrWhiteSpace(edit.Reference)) if (!String.IsNullOrWhiteSpace(edit.Reference)) {
{
var sb2 = new StringBuilder(); var sb2 = new StringBuilder();
sb2.Append(HaWeb.HTMLHelpers.TagHelpers.CreateElement("span", "reference")); sb2.Append(HaWeb.HTMLHelpers.TagHelpers.CreateElement("span", "reference"));
var rd = readerService.RequestStringReader(edit.Reference); var rd = readerService.RequestStringReader(edit.Reference);
@@ -58,8 +52,7 @@ public static class LetterHelpers
rd.Read(); rd.Read();
sb2.Append(HaWeb.HTMLHelpers.TagHelpers.CreateEndElement("span")); sb2.Append(HaWeb.HTMLHelpers.TagHelpers.CreateEndElement("span"));
// Old: (edit.StartPage != edit.EndPage || edit.StartLine != edit.EndLine) && // Old: (edit.StartPage != edit.EndPage || edit.StartLine != edit.EndLine) &&
if (XElement.Parse(sb2.ToString()).Value.ToString().Length >= 20) if (XElement.Parse(sb2.ToString()).Value.ToString().Length >= 20) {
{
var text = XElement.Parse(sb2.ToString()).Value.ToString(); var text = XElement.Parse(sb2.ToString()).Value.ToString();
text = text.ToString().Split(' ').Take(1).First() + " [&#x2026;] " + text.ToString().Split(' ').TakeLast(1).First(); text = text.ToString().Split(' ').Take(1).First() + " [&#x2026;] " + text.ToString().Split(' ').TakeLast(1).First();
var sb3 = new StringBuilder(); var sb3 = new StringBuilder();
@@ -67,12 +60,10 @@ public static class LetterHelpers
sb3.Append(text); sb3.Append(text);
sb3.Append(HaWeb.HTMLHelpers.TagHelpers.CreateEndElement("span")); sb3.Append(HaWeb.HTMLHelpers.TagHelpers.CreateEndElement("span"));
refstring = sb3.ToString(); refstring = sb3.ToString();
} } else
else
refstring = sb2.ToString(); refstring = sb2.ToString();
} }
if (!String.IsNullOrWhiteSpace(edit.Element)) if (!String.IsNullOrWhiteSpace(edit.Element)) {
{
editsState.sb_edits.Append(HaWeb.HTMLHelpers.TagHelpers.CreateElement("span", "corrections")); editsState.sb_edits.Append(HaWeb.HTMLHelpers.TagHelpers.CreateElement("span", "corrections"));
var rd = readerService.RequestStringReader(edit.Element); var rd = readerService.RequestStringReader(edit.Element);
new HaWeb.HTMLParser.XMLHelper<EditState>(editsState, rd, editsState.sb_edits, EditRules.OTagRules, EditRules.STagRules, EditRules.CTagRules, EditRules.TextRules, EditRules.WhitespaceRules); new HaWeb.HTMLParser.XMLHelper<EditState>(editsState, rd, editsState.sb_edits, EditRules.OTagRules, EditRules.STagRules, EditRules.CTagRules, EditRules.TextRules, EditRules.WhitespaceRules);
@@ -86,11 +77,9 @@ public static class LetterHelpers
return editstrings; return editstrings;
} }
public static List<(string, string, string, string, string)> CreateHands(ILibrary lib, ImmutableList<Hand> hands) public static List<(string, string, string, string, string)> CreateHands(ILibrary lib, ImmutableList<Hand> hands) {
{
var handstrings = new List<(string, string, string, string, string)>(); var handstrings = new List<(string, string, string, string, string)>();
foreach (var hand in hands.OrderBy(x => x.StartPage.Length).ThenBy(x => x.StartPage).ThenBy(x => x.StartLine.Length).ThenBy(x => x.StartLine)) foreach (var hand in hands.OrderBy(x => x.StartPage.Length).ThenBy(x => x.StartPage).ThenBy(x => x.StartLine.Length).ThenBy(x => x.StartLine)) {
{
var currstring = hand.StartPage + "/" + hand.StartLine; var currstring = hand.StartPage + "/" + hand.StartLine;
var endstring = ""; var endstring = "";
var personstring = ""; var personstring = "";
@@ -100,8 +89,7 @@ public static class LetterHelpers
if (hand.StartLine != hand.EndLine) if (hand.StartLine != hand.EndLine)
endstring += hand.EndLine; endstring += hand.EndLine;
var persons = lib.HandPersons.Where(x => x.Key == hand.Person); var persons = lib.HandPersons.Where(x => x.Key == hand.Person);
if (persons.Any()) if (persons.Any()) {
{
personstring += " " + persons.FirstOrDefault().Value.Name; personstring += " " + persons.FirstOrDefault().Value.Name;
handstrings.Add((currstring, endstring, personstring, hand.StartPage, hand.StartLine)); handstrings.Add((currstring, endstring, personstring, hand.StartPage, hand.StartLine));
} }

View File

@@ -37,21 +37,20 @@ public class LinkHelper {
if (tag.Name == "wwwlink" || tag.Name == "intlink" || tag.Name == "link") { if (tag.Name == "wwwlink" || tag.Name == "intlink" || tag.Name == "link") {
if (tag.EndTag && _followlinksinthis) { if (tag.EndTag && _followlinksinthis) {
_sb.Append(HTMLHelpers.TagHelpers.CreateEndElement("a")); _sb.Append(HTMLHelpers.TagHelpers.CreateEndElement("a"));
} } else {
else {
if (tag.Name == "wwwlink" && tag.Values.ContainsKey("address") && _followlinksinthis) if (tag.Name == "wwwlink" && tag.Values.ContainsKey("address") && _followlinksinthis)
_sb.Append(HTMLHelpers.TagHelpers.CreateCustomElement("a", _sb.Append(HTMLHelpers.TagHelpers.CreateCustomElement("a",
new HaWeb.HTMLHelpers.TagHelpers.Attribute() { Name = "class", Value = WWWLINKCLASS }, new HaWeb.HTMLHelpers.TagHelpers.Attribute() { Name = "class", Value = WWWLINKCLASS },
new HaWeb.HTMLHelpers.TagHelpers.Attribute() { Name = "href", Value = tag["address"]}, new HaWeb.HTMLHelpers.TagHelpers.Attribute() { Name = "href", Value = tag["address"] },
new HaWeb.HTMLHelpers.TagHelpers.Attribute() { Name = "target", Value = "_blank"}, new HaWeb.HTMLHelpers.TagHelpers.Attribute() { Name = "target", Value = "_blank" },
new HaWeb.HTMLHelpers.TagHelpers.Attribute() { Name = "rel", Value = "noopener noreferrer"})); new HaWeb.HTMLHelpers.TagHelpers.Attribute() { Name = "rel", Value = "noopener noreferrer" }));
if (tag.Name == "intlink" && tag.Values.ContainsKey("letter") && _lib.Metas.ContainsKey(tag["letter"])) { if (tag.Name == "intlink" && tag.Values.ContainsKey("letter") && _lib.Metas.ContainsKey(tag["letter"])) {
var letter = _lib.Metas[tag["letter"]]; var letter = _lib.Metas[tag["letter"]];
_sb.Append(HTMLHelpers.TagHelpers.CreateElement("a", LETLINKCLASS, "/Briefe/" + letter.Autopsic + "#" + tag["page"] + "-" + tag["line"])); _sb.Append(HTMLHelpers.TagHelpers.CreateElement("a", LETLINKCLASS, "/Briefe/" + letter.Autopsic + "#" + tag["page"] + "-" + tag["line"]));
if (!tag.Values.ContainsKey("linktext") || tag.Values["linktext"] == "true") { if (!tag.Values.ContainsKey("linktext") || tag.Values["linktext"] == "true") {
var linkstring = ""; var linkstring = "";
var ZHstring = ""; var ZHstring = "";
var pglnstring= ""; var pglnstring = "";
linkstring += "HKB&nbsp;" + letter.Autopsic; linkstring += "HKB&nbsp;" + letter.Autopsic;
if (tag.Values.ContainsKey("page")) { if (tag.Values.ContainsKey("page")) {
pglnstring += tag["page"]; pglnstring += tag["page"];

View File

@@ -2,11 +2,9 @@ using System.Web;
namespace HaWeb.HTMLHelpers; namespace HaWeb.HTMLHelpers;
public static class StringHelpers { public static class StringHelpers {
public static string GetEnumerationString(IEnumerable<string> strlist) public static string GetEnumerationString(IEnumerable<string> strlist) {
{
var res = ""; var res = "";
foreach (var str in strlist) foreach (var str in strlist) {
{
if (str != strlist.First()) if (str != strlist.First())
if (str == strlist.Last()) if (str == strlist.Last())
res += " und " + str; res += " und " + str;

View File

@@ -3,16 +3,13 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
public static class TagHelpers public static class TagHelpers {
{ public struct Attribute {
public struct Attribute
{
public string Name; public string Name;
public string Value; public string Value;
} }
public static string CreateElement(string elementname, string classes = "", string ids = "") public static string CreateElement(string elementname, string classes = "", string ids = "") {
{
string res = "<" + elementname; string res = "<" + elementname;
if (!String.IsNullOrWhiteSpace(classes)) if (!String.IsNullOrWhiteSpace(classes))
if (elementname == "button") if (elementname == "button")
@@ -27,13 +24,10 @@ public static class TagHelpers
return res + ">"; return res + ">";
} }
public static string CreateCustomElement(string elementname, params Attribute[] attributes) public static string CreateCustomElement(string elementname, params Attribute[] attributes) {
{
string res = "<" + elementname; string res = "<" + elementname;
if (!(attributes.Length == 0)) if (!(attributes.Length == 0)) {
{ foreach (var attrib in attributes) {
foreach (var attrib in attributes)
{
res += CreateAttribute(attrib); res += CreateAttribute(attrib);
} }
} }
@@ -47,8 +41,7 @@ public static class TagHelpers
public static string CreateAttribute(Attribute attr) public static string CreateAttribute(Attribute attr)
=> " " + attr.Name + "=\"" + attr.Value + "\" "; => " " + attr.Name + "=\"" + attr.Value + "\" ";
public static string CreateEmptyElement(string elementname, string classes = "", string ids = "") public static string CreateEmptyElement(string elementname, string classes = "", string ids = "") {
{
string res = "<" + elementname; string res = "<" + elementname;
if (!String.IsNullOrWhiteSpace(classes)) if (!String.IsNullOrWhiteSpace(classes))
res += CreateAttribute(new Attribute() { Name = "class", Value = classes }); res += CreateAttribute(new Attribute() { Name = "class", Value = classes });

View File

@@ -1,8 +1,7 @@
namespace HaWeb.Models; namespace HaWeb.Models;
using HaDocument.Models; using HaDocument.Models;
using System.Web; using System.Web;
public class BriefeMetaViewModel public class BriefeMetaViewModel {
{
public Meta Meta { get; private set; } public Meta Meta { get; private set; }
public bool HasMarginals { get; private set; } public bool HasMarginals { get; private set; }
public bool ShowZHData { get; private set; } public bool ShowZHData { get; private set; }
@@ -12,11 +11,9 @@ public class BriefeMetaViewModel
private string? _ParsedReceivers; private string? _ParsedReceivers;
private string? _ParsedZHString; private string? _ParsedZHString;
public string? ParsedSenders public string? ParsedSenders {
{
get => _ParsedSenders; get => _ParsedSenders;
set set {
{
if (value != null) if (value != null)
_ParsedSenders = HttpUtility.HtmlEncode(value); _ParsedSenders = HttpUtility.HtmlEncode(value);
else else
@@ -24,11 +21,9 @@ public class BriefeMetaViewModel
} }
} }
public string? ParsedReceivers public string? ParsedReceivers {
{
get => _ParsedReceivers; get => _ParsedReceivers;
set set {
{
if (value != null) if (value != null)
_ParsedReceivers = HttpUtility.HtmlEncode(value); _ParsedReceivers = HttpUtility.HtmlEncode(value);
else else
@@ -37,11 +32,9 @@ public class BriefeMetaViewModel
} }
} }
public string? ParsedZHString public string? ParsedZHString {
{
get => _ParsedZHString; get => _ParsedZHString;
set set {
{
if (value != null) if (value != null)
_ParsedZHString = HttpUtility.HtmlEncode(value); _ParsedZHString = HttpUtility.HtmlEncode(value);
else else
@@ -54,8 +47,7 @@ public class BriefeMetaViewModel
public (BriefeMetaViewModel, string)? Prev { get; set; } public (BriefeMetaViewModel, string)? Prev { get; set; }
public BriefeMetaViewModel(Meta meta, bool hasMarginals, bool showZHData) public BriefeMetaViewModel(Meta meta, bool hasMarginals, bool showZHData) {
{
Meta = meta; Meta = meta;
HasMarginals = hasMarginals; HasMarginals = hasMarginals;
ShowZHData = showZHData; ShowZHData = showZHData;

View File

@@ -1,8 +1,7 @@
namespace HaWeb.Models; namespace HaWeb.Models;
using System.Web; using System.Web;
public class BriefeViewModel public class BriefeViewModel {
{
public string Id { get; private set; } public string Id { get; private set; }
public string Index { get; private set; } public string Index { get; private set; }
public BriefeMetaViewModel MetaData { get; private set; } public BriefeMetaViewModel MetaData { get; private set; }
@@ -17,11 +16,9 @@ public class BriefeViewModel
public bool MinWidthTrad { get; set; } = false; public bool MinWidthTrad { get; set; } = false;
// From, Until, Reference, Edit, sartpage, startline // From, Until, Reference, Edit, sartpage, startline
public List<(string, string, string, string, string, string)>? ParsedEdits public List<(string, string, string, string, string, string)>? ParsedEdits {
{
get => _ParsedEdits; get => _ParsedEdits;
set set {
{
if (value != null) if (value != null)
_ParsedEdits = value.Select(x => ( _ParsedEdits = value.Select(x => (
HttpUtility.HtmlEncode(x.Item1), HttpUtility.HtmlEncode(x.Item1),
@@ -37,11 +34,9 @@ public class BriefeViewModel
} }
// From, Until, Person, startpage, startline // From, Until, Person, startpage, startline
public List<(string, string, string, string, string)>? ParsedHands public List<(string, string, string, string, string)>? ParsedHands {
{
get => _ParsedHands; get => _ParsedHands;
set set {
{
if (value != null) if (value != null)
_ParsedHands = value.Select(x => ( _ParsedHands = value.Select(x => (
HttpUtility.HtmlEncode(x.Item1), HttpUtility.HtmlEncode(x.Item1),
@@ -56,11 +51,9 @@ public class BriefeViewModel
} }
// Page, Line, Element // Page, Line, Element
public List<(string, string, string)>? ParsedMarginals public List<(string, string, string)>? ParsedMarginals {
{
get => _ParsedMarginals; get => _ParsedMarginals;
set set {
{
if (value != null) if (value != null)
_ParsedMarginals = value.Select(x => ( _ParsedMarginals = value.Select(x => (
HttpUtility.HtmlEncode(x.Item1), HttpUtility.HtmlEncode(x.Item1),
@@ -72,8 +65,7 @@ public class BriefeViewModel
} }
} }
public BriefeViewModel(string id, string index, BriefeMetaViewModel meta) public BriefeViewModel(string id, string index, BriefeMetaViewModel meta) {
{
Id = id; Id = id;
Index = index; Index = index;
MetaData = meta; MetaData = meta;

View File

@@ -5,13 +5,11 @@ using System.Text;
using HaXMLReader.Interfaces; using HaXMLReader.Interfaces;
using HaXMLReader.EvArgs; using HaXMLReader.EvArgs;
public class CommentModel public class CommentModel {
{
public string ParsedComment { get; private set; } public string ParsedComment { get; private set; }
public List<string>? ParsedSubComments { get; private set; } public List<string>? ParsedSubComments { get; private set; }
public CommentModel(string parsedComment, List<string>? parsedSubComments) public CommentModel(string parsedComment, List<string>? parsedSubComments) {
{
this.ParsedComment = parsedComment; this.ParsedComment = parsedComment;
this.ParsedSubComments = parsedSubComments; this.ParsedSubComments = parsedSubComments;
} }

View File

@@ -1,7 +1,6 @@
namespace HaWeb.Models; namespace HaWeb.Models;
public class ErrorViewModel public class ErrorViewModel {
{
public string? RequestId { get; set; } public string? RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);

View File

@@ -1,8 +1,7 @@
namespace HaWeb.Models; namespace HaWeb.Models;
using System.Web; using System.Web;
public class RegisterViewModel public class RegisterViewModel {
{
public string Category { get; private set; } public string Category { get; private set; }
public string Id { get; private set; } public string Id { get; private set; }
public string Title { get; private set; } public string Title { get; private set; }
@@ -15,11 +14,9 @@ public class RegisterViewModel
public List<CommentModel> ParsedComments { get; private set; } public List<CommentModel> ParsedComments { get; private set; }
// Title, URL // Title, URL
public List<(string, string)>? AvailableCategories public List<(string, string)>? AvailableCategories {
{
get => _AvailableCategories; get => _AvailableCategories;
set set {
{
if (value != null) if (value != null)
_AvailableCategories = value.Select(x => ( _AvailableCategories = value.Select(x => (
HttpUtility.HtmlEncode(x.Item1), HttpUtility.HtmlEncode(x.Item1),
@@ -31,11 +28,9 @@ public class RegisterViewModel
} }
// Title, URL // Title, URL
public List<(string, string)>? AvailableSideCategories public List<(string, string)>? AvailableSideCategories {
{
get => _AvailableSideCategories; get => _AvailableSideCategories;
set set {
{
if (value != null) if (value != null)
_AvailableSideCategories = value.Select(x => ( _AvailableSideCategories = value.Select(x => (
HttpUtility.HtmlEncode(x.Item1), HttpUtility.HtmlEncode(x.Item1),
@@ -46,8 +41,7 @@ public class RegisterViewModel
} }
} }
public RegisterViewModel(string category, string id, List<CommentModel> parsedComments, string title) public RegisterViewModel(string category, string id, List<CommentModel> parsedComments, string title) {
{
this.Category = HttpUtility.HtmlAttributeEncode(category); this.Category = HttpUtility.HtmlAttributeEncode(category);
this.Id = HttpUtility.HtmlAttributeEncode(id); this.Id = HttpUtility.HtmlAttributeEncode(id);
this.ParsedComments = parsedComments; this.ParsedComments = parsedComments;

View File

@@ -26,10 +26,8 @@ public class DocumentResult {
} }
} }
public class LetterComparer : IComparer<DocumentSearchResult> public class LetterComparer : IComparer<DocumentSearchResult> {
{ public int Compare(DocumentSearchResult first, DocumentSearchResult second) {
public int Compare(DocumentSearchResult first, DocumentSearchResult second)
{
var cmp = new DefaultComparer(); var cmp = new DefaultComparer();
return cmp.Compare(first.MetaData, second.MetaData); return cmp.Compare(first.MetaData, second.MetaData);
} }

View File

@@ -1,5 +1,8 @@
namespace HaWeb.Models; namespace HaWeb.Models;
using HaWeb.XMLParser;
public class UploadViewModel { public class UploadViewModel {
public List<(string, string)>? AvailableRoots { get; set; } public List<IXMLRoot>? AvailableRoots { get; set; }
public List<XMLRootDocument>? AvailableFiles { get; set; }
public Dictionary<string, List<XMLRootDocument>>? UsedFiles { get; set; }
} }

View File

@@ -3,9 +3,7 @@ using HaXMLReader.Interfaces;
using HaDocument.Interfaces; using HaDocument.Interfaces;
using HaWeb.XMLParser; using HaWeb.XMLParser;
using Microsoft.FeatureManagement; using Microsoft.FeatureManagement;
using System.Runtime.InteropServices;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.FileProviders;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
@@ -14,12 +12,23 @@ var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews(); builder.Services.AddControllersWithViews();
builder.Services.AddHttpContextAccessor(); builder.Services.AddHttpContextAccessor();
// // To list physical files from a path provided by configuration: // // To get files from a path provided by configuration:
// var physicalProvider = new PhysicalFileProvider(Configuration.GetValue<string>("StoredFilesPath")); string? filepath = null;
// // To list physical files in the temporary files folder, use: if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
// //var physicalProvider = new PhysicalFileProvider(Path.GetTempPath()); filepath = builder.Configuration.GetValue<string>("StoredFilePathWindows");
// services.AddSingleton<IFileProvider>(physicalProvider); }
else {
filepath = builder.Configuration.GetValue<string>("StoredFilePathLinux");
}
if (filepath == null) {
throw new Exception("You need to set a specific Filepath, either StoredFilePathWindows or StoredFilePathLinux");
}
var physicalProvider = new PhysicalFileProvider(filepath);
builder.Services.AddSingleton<IFileProvider>(physicalProvider);
builder.Services.AddSingleton<ILibrary>(HaDocument.Document.Create(new Options())); builder.Services.AddSingleton<ILibrary>(HaDocument.Document.Create(new Options()));
builder.Services.AddTransient<IReaderService, ReaderService>(); builder.Services.AddTransient<IReaderService, ReaderService>();
builder.Services.AddSingleton<IXMLService, XMLService>(); builder.Services.AddSingleton<IXMLService, XMLService>();

View File

@@ -42,3 +42,5 @@ TODO Kommentare und min-size von ha-lettertetx
TODO Word-wrap before align, tabs TODO Word-wrap before align, tabs
TODO pills are not mobile friendly (hover / click) TODO pills are not mobile friendly (hover / click)
TODO Evtl alignment von center / right an der letzten oder nächsten zeile TODO Evtl alignment von center / right an der letzten oder nächsten zeile
TODO Abhärten des Konstruktors von XMLRootDokument für von außerhalb platzierte Dokumente
TODO XML-Check im Client

View File

@@ -63,7 +63,7 @@ public static class CSSClasses {
// Zeilen: // Zeilen:
public const string ZHLINECLASS = "ha-zhline"; public const string ZHLINECLASS = "ha-zhline";
public const string FIRSTLINECLASS ="ha-firstline"; public const string FIRSTLINECLASS = "ha-firstline";
public const string ZHBREAKCLASS = "ha-zhbreak"; public const string ZHBREAKCLASS = "ha-zhbreak";
public const string LINELINECLASS = "ha-hr"; public const string LINELINECLASS = "ha-hr";
public const string LINEINDENTCLASS = "ha-indent-"; // TODO: GEN public const string LINEINDENTCLASS = "ha-indent-"; // TODO: GEN

View File

@@ -1,7 +1,6 @@
namespace HaWeb; namespace HaWeb;
public static class Features public static class Features {
{
public const string AdminService = "AdminService"; public const string AdminService = "AdminService";
public const string UploadService = "UploadService"; public const string UploadService = "UploadService";
public const string UpdateService = "UpdateService"; public const string UpdateService = "UpdateService";

View File

@@ -7,8 +7,7 @@ using TextFuncList = List<(Func<HaXMLReader.EvArgs.Text, HaWeb.HTMLParser.XMLHel
using WhitespaceFuncList = List<(Func<HaXMLReader.EvArgs.Whitespace, HaWeb.HTMLParser.XMLHelper<HaWeb.Settings.ParsingState.LetterState>, bool>, Action<System.Text.StringBuilder, HaXMLReader.EvArgs.Whitespace, HaWeb.HTMLParser.XMLHelper<HaWeb.Settings.ParsingState.LetterState>>)>; using WhitespaceFuncList = List<(Func<HaXMLReader.EvArgs.Whitespace, HaWeb.HTMLParser.XMLHelper<HaWeb.Settings.ParsingState.LetterState>, bool>, Action<System.Text.StringBuilder, HaXMLReader.EvArgs.Whitespace, HaWeb.HTMLParser.XMLHelper<HaWeb.Settings.ParsingState.LetterState>>)>;
// TODO: stringbuilder als Rückgabeparameter des XMHelpers ist eigentlich auch Part vom State // TODO: stringbuilder als Rückgabeparameter des XMHelpers ist eigentlich auch Part vom State
public class LetterRules public class LetterRules {
{
private static readonly string DEFAULTELEMENT = HaWeb.Settings.HTML.DEFAULTELEMENT; private static readonly string DEFAULTELEMENT = HaWeb.Settings.HTML.DEFAULTELEMENT;
private static readonly string LEMMACLASS = HaWeb.Settings.CSSClasses.LEMMACLASS; private static readonly string LEMMACLASS = HaWeb.Settings.CSSClasses.LEMMACLASS;

View File

@@ -6,8 +6,7 @@ using TagFuncList = List<(Func<HaXMLReader.EvArgs.Tag, HaWeb.HTMLParser.XMLHelpe
using TextFuncList = List<(Func<HaXMLReader.EvArgs.Text, HaWeb.HTMLParser.XMLHelper<HaWeb.Settings.ParsingState.TraditionState>, bool>, Action<System.Text.StringBuilder, HaXMLReader.EvArgs.Text, HaWeb.HTMLParser.XMLHelper<HaWeb.Settings.ParsingState.TraditionState>>)>; using TextFuncList = List<(Func<HaXMLReader.EvArgs.Text, HaWeb.HTMLParser.XMLHelper<HaWeb.Settings.ParsingState.TraditionState>, bool>, Action<System.Text.StringBuilder, HaXMLReader.EvArgs.Text, HaWeb.HTMLParser.XMLHelper<HaWeb.Settings.ParsingState.TraditionState>>)>;
using WhitespaceFuncList = List<(Func<HaXMLReader.EvArgs.Whitespace, HaWeb.HTMLParser.XMLHelper<HaWeb.Settings.ParsingState.TraditionState>, bool>, Action<System.Text.StringBuilder, HaXMLReader.EvArgs.Whitespace, HaWeb.HTMLParser.XMLHelper<HaWeb.Settings.ParsingState.TraditionState>>)>; using WhitespaceFuncList = List<(Func<HaXMLReader.EvArgs.Whitespace, HaWeb.HTMLParser.XMLHelper<HaWeb.Settings.ParsingState.TraditionState>, bool>, Action<System.Text.StringBuilder, HaXMLReader.EvArgs.Whitespace, HaWeb.HTMLParser.XMLHelper<HaWeb.Settings.ParsingState.TraditionState>>)>;
public static class TraditionRules public static class TraditionRules {
{
private static readonly string DEFAULTELEMENT = HaWeb.Settings.HTML.DEFAULTELEMENT; private static readonly string DEFAULTELEMENT = HaWeb.Settings.HTML.DEFAULTELEMENT;
private static readonly string LEMMACLASS = HaWeb.Settings.CSSClasses.LEMMACLASS; private static readonly string LEMMACLASS = HaWeb.Settings.CSSClasses.LEMMACLASS;

View File

@@ -10,7 +10,7 @@ using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
public class LetterState : HaWeb.HTMLParser.IState { public class LetterState : HaWeb.HTMLParser.IState {
// Input // Input
internal ILibrary Lib; internal ILibrary Lib;
internal IReaderService ReaderService; internal IReaderService ReaderService;
internal Meta Meta; internal Meta Meta;

View File

@@ -34,8 +34,7 @@ public class TraditionState : HaWeb.HTMLParser.IState {
internal IReader rd_tradition; internal IReader rd_tradition;
public TraditionState(ILibrary lib, IReader reader, IReaderService readerService, IEnumerable<Marginal>? marginals, IEnumerable<Hand>? hands, IEnumerable<Editreason>? edits) public TraditionState(ILibrary lib, IReader reader, IReaderService readerService, IEnumerable<Marginal>? marginals, IEnumerable<Hand>? hands, IEnumerable<Editreason>? edits) {
{
Lib = lib; Lib = lib;
rd_tradition = reader; rd_tradition = reader;
Marginals = marginals; Marginals = marginals;

View File

@@ -5,7 +5,7 @@ using HaWeb.XMLParser;
public class DescriptionsRoot : HaWeb.XMLParser.IXMLRoot { public class DescriptionsRoot : HaWeb.XMLParser.IXMLRoot {
public string Type { get; } = "Metadaten"; public string Type { get; } = "Metadaten";
public string Prefix { get; } = "metadaten"; public string Prefix { get; } = "metadaten";
public string[] XPathContainer { get; } = {".//data/descriptions", ".//descriptions" }; public string[] XPathContainer { get; } = { ".//data/descriptions", ".//descriptions" };
public Predicate<XElement> IsCollectedObject { get; } = (elem) => { public Predicate<XElement> IsCollectedObject { get; } = (elem) => {
if (elem.Name == "letterDesc") return true; if (elem.Name == "letterDesc") return true;

View File

@@ -1,57 +1,79 @@
@model UploadViewModel; @model UploadViewModel;
Hello from Upload Index!
<form id="uploadForm" action="Upload" method="post" <div class="ha-adminuploadfields">
enctype="multipart/form-data" onsubmit="AJAXSubmit(this);return false;"> @foreach (var item in Model.AvailableRoots.OrderBy(x => x.Type)) {
<dl> <div class="ha-uploadfield">
<dt> <div class="ha-uploadfieldname">@item.Type</div>
<label for="file">File</label> @if (Model.UsedFiles != null && Model.UsedFiles.ContainsKey(item.Prefix)) {
</dt> @foreach(var file in Model.UsedFiles[item.Prefix]) {
<dd> <span>@file.File.Name</span>
<input id="file" type="file" name="file" /> }
</dd> }
</dl>
<input class="btn" type="submit" value="Upload" />
<div style="margin-top:15px">
<output form="uploadForm" name="result"></output>
</div> </div>
</form> }
<form class="ha-uploadform" id="uploadForm" action="Upload" method="post" enctype="multipart/form-data">
@foreach (var item in Model.AvailableRoots.OrderBy(x => x.Item1)) <label class="filelabel" id="dropzone">
{ <input class="hidden" id="file" type="file" accept=".xml" name="file" />
<div>@item.Item1</div> Upload2 es
} </label>
<div class="ha-uploadmessage">
<output form="uploadForm" name="result"></output>
</div>
</form>
</div>
@section Scripts { @section Scripts {
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"
asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
asp-fallback-test="window.jQuery"
crossorigin="anonymous"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=">
</script>
<script> <script>
"use strict"; "use strict";
async function AJAXSubmit (oFormElement) { const dropHandler = function (formelement, ev, dropzone) {
const formData = new FormData(oFormElement); ev.preventDefault();
try { if (ev.dataTransfer.items) {
const response = await fetch(oFormElement.action, { if (ev.dataTransfer.items[0].kind === 'file') {
var file = ev.dataTransfer.items[0].getAsFile();
AJAXSubmit(formelement, file);
} else {
var file = ev.dataTransfer.files[0];
AJAXSubmit(formelement, file);
}
}
}
const dragOverHandler = function (ev, dropzone) {
ev.preventDefault();
}
const dragLeaveHander = function (ev, dropzone) {
ev.preventDefault();
}
const dragEnterHandler = function (ev, dropzone) {
ev.preventDefault();
}
const AJAXSubmit = async function (oFormElement, file = null) {
var fd = new FormData();
if (file !== null) fd.append("file", file);
else fd = new FormData(oFormElement);
oFormElement.elements.namedItem("result").value = "Wait";
await fetch(oFormElement.action, {
method: 'POST', method: 'POST',
headers: { headers: {
'RequestVerificationToken': getCookie('RequestVerificationToken') 'RequestVerificationToken': getCookie('RequestVerificationToken')
}, },
body: formData body: fd
}); })
.then(response => response.json())
oFormElement.elements.namedItem("result").value = .then(json => {
'Result: ' + response.status + ' ' + response.statusText; if ("Error" in json) {
} catch (error) { oFormElement.elements.namedItem("result").value = json.Error;
console.error('Error:', error); } else {
} oFormElement.elements.namedItem("result").value = "Erfolg!";
}
})
.catch ((e) => console.error('Error:', error))
} }
function getCookie(name) { function getCookie(name) {
@@ -59,5 +81,18 @@ Hello from Upload Index!
var parts = value.split("; " + name + "="); var parts = value.split("; " + name + "=");
if (parts.length == 2) return parts.pop().split(";").shift(); if (parts.length == 2) return parts.pop().split(";").shift();
} }
window.addEventListener("load", function () {
var submitelement = document.getElementById("file");
var formelement = document.getElementById("uploadForm");
var dropzone = document.getElementById("dropzone");
submitelement.addEventListener("change", () => AJAXSubmit(formelement));
dropzone.addEventListener("drop", (ev) => dropHandler(formelement, ev, dropzone));
dropzone.addEventListener("dragover", (ev) => dragOverHandler(ev, dropzone));
dropzone.addEventListener("dragleave", (ev) => dragLeaveHander(ev, dropzone));
dropzone.addEventListener("dragenter", (ev) => dragEnterHandler(ev, dropzone));
});
</script> </script>
} }

View File

@@ -7,46 +7,44 @@
if (Model.MinWidthTrad) if (Model.MinWidthTrad)
minwidthtrads = "ha-minwidth"; minwidthtrads = "ha-minwidth";
} }
<div class="ha-twilighttogglebar"> <div class="ha-letterheader">
<div class="ha-letterheader"> @await Html.PartialAsync("/Views/Shared/_LetterHead.cshtml", Model.MetaData)
@await Html.PartialAsync("/Views/Shared/_LetterHead.cshtml", Model.MetaData) <div class="ha-letterheadernav">
<div class="ha-letterheadernav"> <div class="ha-lettertabs">
<div class="ha-lettertabs"> @if (Model.ParsedText != null && !String.IsNullOrWhiteSpace(Model.ParsedText))
@if (Model.ParsedText != null && !String.IsNullOrWhiteSpace(Model.ParsedText))
{
<a class="" id="ha-lettertextbtn">Brieftext</a>
@if (Model.ParsedMarginals != null)
{
<a class="ha-marginalsbtn " id="ha-marginalsbtn">Stellenkommentar</a>
}
}
<a class="" id="ha-additionsbtn">Überlieferung & Textkritik</a>
<a class="">PDF</a>
</div>
@if (Model.MetaData.Next != null || Model.MetaData.Prev != null)
{ {
<div class="ha-lettermetalinks"> <a class="" id="ha-lettertextbtn">Brieftext</a>
@if (Model.MetaData.Prev != null) @if (Model.ParsedMarginals != null)
{ {
<a href="@Model.MetaData.Prev.Value.Item2"> <a class="ha-marginalsbtn " id="ha-marginalsbtn">Stellenkommentar</a>
@Model.MetaData.Prev.Value.Item1.Meta.Autopsic ◀ }
</a>
}
<div class="ha-hkb">
HKB
</div>
@if (Model.MetaData.Next != null)
{
<a href="@Model.MetaData.Next.Value.Item2">
▶ @Model.MetaData.Next.Value.Item1.Meta.Autopsic
</a>
}
</div>
} }
<a class="" id="ha-additionsbtn">Überlieferung & Textkritik</a>
<a class="">PDF</a>
</div> </div>
@if (Model.MetaData.Next != null || Model.MetaData.Prev != null)
{
<div class="ha-lettermetalinks">
@if (Model.MetaData.Prev != null)
{
<a href="@Model.MetaData.Prev.Value.Item2">
@Model.MetaData.Prev.Value.Item1.Meta.Autopsic ◀
</a>
}
<div class="ha-hkb">
HKB
</div>
@if (Model.MetaData.Next != null)
{
<a href="@Model.MetaData.Next.Value.Item2">
▶ @Model.MetaData.Next.Value.Item1.Meta.Autopsic
</a>
}
</div>
}
</div> </div>
</div> </div>

View File

@@ -3,10 +3,8 @@
<div class="flex grow shrink-0"> <div class="flex grow shrink-0">
<div class="ha-themetoggles"> <div class="ha-themetoggles">
<input type="radio" id="ha-toggledark" name="ha-themetoggle"> <input type="radio" id="ha-toggledark" name="ha-themetoggle">
<input type="radio" id="ha-toggletwilight" name="ha-themetoggle">
<input type="radio" id="ha-togglebright" name="ha-themetoggle"> <input type="radio" id="ha-togglebright" name="ha-themetoggle">
<label for="ha-toggledark"></label> <label for="ha-toggledark"></label>
<label for="ha-toggletwilight"></label>
<label for="ha-togglebright"></label> <label for="ha-togglebright"></label>
<div class="ha-themetoggleslider"> <div class="ha-themetoggleslider">
</div> </div>

View File

@@ -25,13 +25,14 @@ public interface IXMLRoot {
var elements = root.XPathSelectElements(p); var elements = root.XPathSelectElements(p);
if (elements != null && elements.Any()) { if (elements != null && elements.Any()) {
if (ret == null) ret = new List<XElement>(); if (ret == null) ret = new List<XElement>();
ret.AddRange(elements); foreach (var e in elements)
if (!ret.Contains(e)) ret.Add(e);
} }
} }
return ret; return ret;
} }
// Generate certain metadat fields to display about this root // Generate certain metadata fields to display about this root
public abstract List<(string, string?)>? GenerateFields(XMLRootDocument document); public abstract List<(string, string?)>? GenerateFields(XMLRootDocument document);
// Generate an identification string of which the hash will be the filename. // Generate an identification string of which the hash will be the filename.

View File

@@ -5,5 +5,7 @@ using Microsoft.AspNetCore.Mvc.ModelBinding;
public interface IXMLService { public interface IXMLService {
public IXMLRoot? GetRoot(string name); public IXMLRoot? GetRoot(string name);
public List<IXMLRoot>? GetRoots(); public List<IXMLRoot>? GetRoots();
public List<XMLRootDocument>? ProbeHamannFile(XDocument document, ModelStateDictionary ModelState); public Task<List<XMLRootDocument>?> ProbeHamannFile(XDocument document, ModelStateDictionary ModelState);
public Task UpdateAvailableFiles(XMLRootDocument doc, string basefilepath, ModelStateDictionary ModelState);
public Dictionary<string, List<XMLRootDocument>> GetUsed();
} }

View File

@@ -0,0 +1,34 @@
namespace HaWeb.XMLParser;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;
public class IdentificationStringJSONConverter : JsonConverter<(string?, string?)>
{
public override (string?, string?) Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options) {
var s = reader.GetString();
if (s == null) return (null, null);
var split = s.Split('-');
string? str1 = null;
if (!String.IsNullOrWhiteSpace(split[0])) str1 = split[0];
if (s.Length > 1 && !String.IsNullOrWhiteSpace(split[1])) return (str1, split[1]);
else return (str1, null);
}
public override void Write(
Utf8JsonWriter writer,
(string?, string?) value,
JsonSerializerOptions options)
{
if (value.Item1 == null && value.Item2 == null) return;
var res = "";
if (value.Item1 != null) res += value.Item1;
if (value.Item2 != null) res += "-" + value.Item2;
writer.WriteStringValue(res);
}
}

View File

@@ -2,42 +2,45 @@ namespace HaWeb.XMLParser;
using System.Xml.Linq; using System.Xml.Linq;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.FileProviders;
public class XMLRootDocument { public class XMLRootDocument {
private XElement? _Element; private XElement? _Element;
private string? _filename; private string? _filename;
private string? _path;
private IXMLService _xmlService; private IXMLService _xmlService;
[JsonIgnore] [JsonIgnore]
public XElement Root { public XElement Root {
get { get {
if (_Element == null) { if (_Element == null) {
_Element = GetElement(); _Element = _GetElement();
} }
return _Element; return _Element;
} } }
}
public string FileName { get { public string FileName {
if (_filename == null) get {
_filename = _CreateFilename(); if (_filename == null)
return _filename; _filename = _CreateFilename();
} } return _filename;
}
}
[JsonIgnore]
public IFileInfo? File { get; private set; }
public string Prefix { get; private set; } public string Prefix { get; private set; }
public DateTime Date { get; private set; } public DateTime Date { get; private set; }
public bool Used { get; private set; } = false;
public (string?, string?) IdentificationString { get; private set; } public (string?, string?) IdentificationString { get; private set; }
[JsonIgnore] [JsonIgnore]
public List<(string, string)>? Fields { get; set; } public List<(string, string)>? Fields { get; set; }
// Entry point for file reading // Entry point for file reading
public XMLRootDocument(IXMLService xmlService, string prefix, (string?, string?) idString, DateTime date, string path) { public XMLRootDocument(IXMLService xmlService, IFileInfo file) {
_xmlService = xmlService; _xmlService = xmlService;
_path = path; SetFile(file);
Prefix = prefix;
IdentificationString = idString;
Date = date;
} }
// Entry point for XML upload reading // Entry point for XML upload reading
@@ -49,6 +52,19 @@ public class XMLRootDocument {
_Element = element; _Element = element;
} }
public void SetFile(IFileInfo file) {
File = file;
Date = file.LastModified.DateTime;
_GenerateFieldsFromFilename(file.Name);
}
public void SetUsed(bool used) {
Used = used;
if (used && _Element == null) {
_GetElement();
}
}
private string _CreateFilename() { private string _CreateFilename() {
var filename = _removeInvalidChars(Prefix) + "_"; var filename = _removeInvalidChars(Prefix) + "_";
if (!String.IsNullOrWhiteSpace(IdentificationString.Item1)) { if (!String.IsNullOrWhiteSpace(IdentificationString.Item1)) {
@@ -69,28 +85,39 @@ public class XMLRootDocument {
return s; return s;
} }
private XElement GetElement() { private void _GenerateFieldsFromFilename(string filename) {
if (_path == null || String.IsNullOrWhiteSpace(_path)) var split = filename.Split('_');
Prefix = split[0];
if (split.Length == 3) {
IdentificationString = (null, split[1]);
} else if (split.Length == 4) {
IdentificationString = (split[1], split[2]);
} else {
IdentificationString = (null, null);
}
}
private XElement _GetElement() {
if (File == null || String.IsNullOrWhiteSpace(File.PhysicalPath) || !File.Exists)
throw new Exception("Es ist kein Pfad für die XML-Datei vorhanden."); throw new Exception("Es ist kein Pfad für die XML-Datei vorhanden.");
var root = _xmlService.GetRoot(Prefix); var root = _xmlService.GetRoot(Prefix);
if (root == null) if (root == null)
throw new Exception("Kein gültiges Hamann-Dokument: " + _path + "Vom Prefix: " + Prefix); throw new Exception("Kein gültiges Hamann-Dokument: " + File.PhysicalPath + "Vom Prefix: " + Prefix);
XDocument? doc = null; XDocument? doc = null;
try { try {
doc = XDocument.Load(_path, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); doc = XDocument.Load(File.PhysicalPath, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
} } catch (Exception ex) {
catch (Exception ex) {
throw new Exception("Fehler beim Lesen des Dokuments: " + ex.Message); throw new Exception("Fehler beim Lesen des Dokuments: " + ex.Message);
} }
if (doc == null || doc.Root == null) if (doc == null || doc.Root == null)
throw new Exception("Das Dokument ist ungültig und kann nicht gelesen werden: " + _path); throw new Exception("Das Dokument ist ungültig und kann nicht gelesen werden: " + File.PhysicalPath);
var element = root.IsTypeOf(doc.Root); var element = root.IsTypeOf(doc.Root);
if (element == null || !element.Any()) if (element == null || !element.Any())
throw new Exception("Kein gültiges Hamann-Dokument: " + _path + "Vom Prefix: " + Prefix); throw new Exception("Kein gültiges Hamann-Dokument: " + File.PhysicalPath + "Vom Prefix: " + Prefix);
return element.First(); return element.First();
} }

View File

@@ -2,17 +2,43 @@ namespace HaWeb.XMLParser;
using HaWeb.Settings.XMLRoots; using HaWeb.Settings.XMLRoots;
using System.Xml.Linq; using System.Xml.Linq;
using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.FileProviders;
using Microsoft.FeatureManagement;
public class XMLService : IXMLService { public class XMLService : IXMLService {
private Dictionary<string, List<XMLRootDocument>?>? _availableFilesObj;
private Dictionary<string, List<XMLRootDocument>?>? _availableFiles {
get {
if (_availableFilesObj == null) {
_availableFilesObj = _GetAvailableFiles();
AutoDetermineUsed();
}
return _availableFilesObj;
} }
private IFileProvider _fileProvider;
private IFeatureManager _featureManager;
private Dictionary<string, IXMLRoot>? _Roots; private Dictionary<string, IXMLRoot>? _Roots;
public XMLService() { public bool UploadEnabled = false;
public bool UpdateEnabled = false;
public XMLService(IFileProvider provider, IFeatureManager featureManager) {
_fileProvider = provider;
_featureManager = featureManager;
if (provider == null)
throw new Exception("To Upload Files you need a FileProvider");
// Getting all classes which implement IXMLRoot for possible upload endpoints
var types = _GetAllTypesThatImplementInterface<IXMLRoot>().ToList(); var types = _GetAllTypesThatImplementInterface<IXMLRoot>().ToList();
types.ForEach( x => { types.ForEach( x => {
if (this._Roots == null) this._Roots = new Dictionary<string, IXMLRoot>(); if (this._Roots == null) this._Roots = new Dictionary<string, IXMLRoot>();
var instance = (IXMLRoot)Activator.CreateInstance(x)!; var instance = (IXMLRoot)Activator.CreateInstance(x)!;
if (instance != null) this._Roots.Add(instance.Prefix, instance); if (instance != null) this._Roots.Add(instance.Prefix, instance);
}); });
if (_Roots == null || !_Roots.Any())
throw new Exception("No classes for upload endpoints were found!");
} }
public IXMLRoot? GetRoot(string name) { public IXMLRoot? GetRoot(string name) {
@@ -22,7 +48,7 @@ public class XMLService : IXMLService {
public List<IXMLRoot>? GetRoots() => this._Roots == null ? null : this._Roots.Values.ToList(); public List<IXMLRoot>? GetRoots() => this._Roots == null ? null : this._Roots.Values.ToList();
public List<XMLRootDocument>? ProbeHamannFile(XDocument document, ModelStateDictionary ModelState) { public async Task<List<XMLRootDocument>?> ProbeHamannFile(XDocument document, ModelStateDictionary ModelState) {
if (document.Root!.Name != "opus") { if (document.Root!.Name != "opus") {
ModelState.AddModelError("Error", "A valid Hamann-Docuemnt must begin with <opus>"); ModelState.AddModelError("Error", "A valid Hamann-Docuemnt must begin with <opus>");
return null; return null;
@@ -43,6 +69,63 @@ public class XMLService : IXMLService {
return res; return res;
} }
public void UpdateAvailableFiles() {
_availableFilesObj = _GetAvailableFiles();
}
public void UpdateAvailableFiles(string prefix) {
if (_availableFilesObj == null) {
UpdateAvailableFiles();
return;
}
if (_availableFilesObj.ContainsKey(prefix))
_availableFilesObj.Remove(prefix);
if (_fileProvider.GetDirectoryContents(prefix).Exists) {
_availableFilesObj.Add(prefix, _GetAvailableFiles(prefix));
}
AutoDetermineUsed(prefix);
}
public async Task UpdateAvailableFiles(XMLRootDocument doc, string basefilepath, ModelStateDictionary ModelState) {
await _setEnabled();
if (!UploadEnabled) {
ModelState.AddModelError("Error", "The uploading of files is deactivated");
return;
}
await _Save(doc, basefilepath, ModelState);
if (!ModelState.IsValid) return;
UpdateAvailableFiles(doc.Prefix);
}
private async Task _Save(XMLRootDocument doc, string basefilepath, ModelStateDictionary ModelState) {
var type = doc.Prefix;
var directory = Path.Combine(basefilepath, type);
if (!Directory.Exists(directory))
Directory.CreateDirectory(directory);
var path = Path.Combine(directory, doc.FileName);
try {
using (var targetStream = System.IO.File.Create(path))
await doc.Save(targetStream, ModelState);
}
catch (Exception ex) {
ModelState.AddModelError("Error", "Speichern der Datei fehlgeschlagen: " + ex.Message);
return;
}
var info = _fileProvider.GetFileInfo(Path.Combine(doc.Prefix, doc.FileName));
if (info == null) {
ModelState.AddModelError("Error", "Auf die neu erstellte Dtaei konnte nicht zugegriffen werden");
return;
}
doc.SetFile(info);
UpdateAvailableFiles(type);
}
private XMLRootDocument _createXMLRootDocument(IXMLRoot Root, XElement element) { private XMLRootDocument _createXMLRootDocument(IXMLRoot Root, XElement element) {
var doc = new XMLRootDocument(this, Root.Prefix, Root.GenerateIdentificationString(element), element); var doc = new XMLRootDocument(this, Root.Prefix, Root.GenerateIdentificationString(element), element);
doc.Fields = Root.GenerateFields(doc); doc.Fields = Root.GenerateFields(doc);
@@ -56,4 +139,63 @@ public class XMLService : IXMLService {
.Where(type => typeof(T).IsAssignableFrom(type) && !type.IsInterface); .Where(type => typeof(T).IsAssignableFrom(type) && !type.IsInterface);
} }
private async Task _setEnabled() {
if (await _featureManager.IsEnabledAsync(HaWeb.Features.UploadService))
UploadEnabled = true;
if (await _featureManager.IsEnabledAsync(HaWeb.Features.UpdateService))
UpdateEnabled = true;
}
private Dictionary<string, List<XMLRootDocument>?>? _GetAvailableFiles() {
if (_Roots == null) return null;
Dictionary<string, List<XMLRootDocument>?>? res = null;
var dirs = _fileProvider.GetDirectoryContents(string.Empty).Where(x => x.IsDirectory);
foreach(var dir in dirs) {
if(_Roots.ContainsKey(dir.Name)) {
if (res == null) res = new Dictionary<string, List<XMLRootDocument>?>();
res.Add(dir.Name, _GetAvailableFiles(dir.Name));
}
}
return res;
}
private List<XMLRootDocument>? _GetAvailableFiles(string prefix) {
List<XMLRootDocument>? res = null;
var files = _fileProvider.GetDirectoryContents(prefix).Where(x => !x.IsDirectory && x.Name.StartsWith(prefix) && x.Name.EndsWith(".xml"));
foreach (var file in files) {
if (res == null) res = new List<XMLRootDocument>();
res.Add(new XMLRootDocument(this, file));
}
return res;
}
public void AutoDetermineUsed() {
if (_availableFilesObj == null) return;
foreach (var (prefix, _) in _availableFilesObj)
AutoDetermineUsed(prefix);
}
public void AutoDetermineUsed(string prefix) {
if (_Roots == null || _availableFilesObj == null) return;
_Roots.TryGetValue(prefix, out var root);
_availableFilesObj.TryGetValue(prefix, out var files);
if (files == null || root == null) return;
//TODO: Item1
var lookup = files.ToLookup(x => x.IdentificationString.Item2);
foreach (var idstring in lookup) {
var ordered = idstring.OrderBy(x => x.Date);
ordered.Last().SetUsed(true);
ordered.Take(ordered.Count() - 1).ToList().ForEach(x => x.SetUsed(false));
}
}
public Dictionary<string, List<XMLRootDocument>>? GetUsed() {
if (_availableFiles == null) return null;
return _availableFiles
.Where(x => x.Value != null)
.Select(x => x.Value!.Where(x => x.Used).ToList())
.Where(x => x.Any())
.ToDictionary(x => x.First().Prefix);
}
} }

16
HaWeb/omnisharp.json Normal file
View File

@@ -0,0 +1,16 @@
{
"FormattingOptions": {
"NewLinesForBracesInLambdaExpressionBody": false,
"NewLinesForBracesInAnonymousMethods": false,
"NewLinesForBracesInAnonymousTypes": false,
"NewLinesForBracesInControlBlocks": false,
"NewLinesForBracesInTypes": false,
"NewLinesForBracesInMethods": false,
"NewLinesForBracesInProperties": false,
"NewLinesForBracesInObjectCollectionArrayInitializers": false,
"NewLinesForBracesInAccessors": false,
"NewLineForElse": false,
"NewLineForCatch": false,
"NewLineForFinally": false
}
}

View File

@@ -468,6 +468,14 @@ Ensure the default browser behavior of the `hidden` attribute.
--tw-backdrop-sepia: ; --tw-backdrop-sepia: ;
} }
* {
transition-property: color, background-color, border-color, fill, stroke, -webkit-text-decoration-color;
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, -webkit-text-decoration-color;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 100ms;
}
/* TODO: check what can be inlined (eg. used once in the code, has no double paths etc...) */ /* TODO: check what can be inlined (eg. used once in the code, has no double paths etc...) */
/* Everything related to theme color */ /* Everything related to theme color */
@@ -570,21 +578,36 @@ body {
--tw-shadow: inset 0 2px 4px 0 rgb(0 0 0 / 0.05); --tw-shadow: inset 0 2px 4px 0 rgb(0 0 0 / 0.05);
--tw-shadow-colored: inset 0 2px 4px 0 var(--tw-shadow-color); --tw-shadow-colored: inset 0 2px 4px 0 var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
transition-property: color, background-color, border-color, fill, stroke, -webkit-text-decoration-color;
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, -webkit-text-decoration-color;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 300ms;
} }
.ha-footer .ha-themetoggles #ha-toggledark:checked ~ .ha-themetoggleslider { .dark .ha-footer .ha-themetoggles {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(30 41 59 / var(--tw-bg-opacity)); background-color: rgb(30 41 59 / var(--tw-bg-opacity));
} }
.ha-footer .ha-themetoggles #ha-toggletwilight:checked ~ .ha-themetoggleslider { .ha-footer .ha-themetoggles #ha-toggledark:checked ~ .ha-themetoggleslider {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(100 116 139 / var(--tw-bg-opacity)); background-color: rgb(226 232 240 / var(--tw-bg-opacity));
transition-property: color, background-color, border-color, fill, stroke, -webkit-text-decoration-color;
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, -webkit-text-decoration-color;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 300ms;
} }
.ha-footer .ha-themetoggles #ha-togglebright:checked ~ .ha-themetoggleslider { .ha-footer .ha-themetoggles #ha-togglebright:checked ~ .ha-themetoggleslider {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(248 250 252 / var(--tw-bg-opacity)); background-color: rgb(248 250 252 / var(--tw-bg-opacity));
transition-property: color, background-color, border-color, fill, stroke, -webkit-text-decoration-color;
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, -webkit-text-decoration-color;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 300ms;
} }
.ha-static { .ha-static {
@@ -1013,6 +1036,8 @@ body {
} }
.ha-tradzhtext .ha-marginalbox.ha-expanded-box .ha-marginallist, .ha-lettertext .ha-marginalbox.ha-expanded-box .ha-marginallist { .ha-tradzhtext .ha-marginalbox.ha-expanded-box .ha-marginallist, .ha-lettertext .ha-marginalbox.ha-expanded-box .ha-marginallist {
--tw-bg-opacity: 1;
background-color: rgb(241 245 249 / var(--tw-bg-opacity));
padding-bottom: 0.25rem; padding-bottom: 0.25rem;
--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
@@ -1020,6 +1045,8 @@ body {
} }
.dark .ha-tradzhtext .ha-marginalbox.ha-expanded-box .ha-marginallist, .dark .ha-lettertext .ha-marginalbox.ha-expanded-box .ha-marginallist { .dark .ha-tradzhtext .ha-marginalbox.ha-expanded-box .ha-marginallist, .dark .ha-lettertext .ha-marginalbox.ha-expanded-box .ha-marginallist {
--tw-bg-opacity: 1;
background-color: rgb(71 85 105 / var(--tw-bg-opacity));
--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
@@ -1115,6 +1142,8 @@ body {
color: rgb(31 41 55 / var(--tw-text-opacity)) !important; color: rgb(31 41 55 / var(--tw-text-opacity)) !important;
} }
/* Classes */
body { body {
height: 100%; height: 100%;
width: 100%; width: 100%;
@@ -1258,7 +1287,7 @@ body {
.ha-footer .ha-themetoggles { .ha-footer .ha-themetoggles {
position: relative; position: relative;
height: 1rem; height: 1rem;
width: 49px; width: 34px;
white-space: nowrap; white-space: nowrap;
border-radius: 1.5rem; border-radius: 1.5rem;
padding-left: 0.125rem; padding-left: 0.125rem;
@@ -1305,12 +1334,8 @@ body {
left: 0.25rem; left: 0.25rem;
} }
.ha-footer .ha-themetoggles #ha-toggletwilight:checked ~ .ha-themetoggleslider {
left: 19px;
}
.ha-footer .ha-themetoggles #ha-togglebright:checked ~ .ha-themetoggleslider { .ha-footer .ha-themetoggles #ha-togglebright:checked ~ .ha-themetoggleslider {
left: 34px; left: 19px;
} }
/* Classes for static pages */ /* Classes for static pages */
@@ -2254,6 +2279,7 @@ body {
flex-wrap: wrap; flex-wrap: wrap;
-moz-column-gap: 1.5rem; -moz-column-gap: 1.5rem;
column-gap: 1.5rem; column-gap: 1.5rem;
padding-right: 0.25rem;
font-size: 0.875rem; font-size: 0.875rem;
line-height: 1.25rem; line-height: 1.25rem;
line-height: 1.25; line-height: 1.25;
@@ -2791,6 +2817,10 @@ body {
width: 2rem; width: 2rem;
} }
.max-w-\[25\%\] {
max-width: 25%;
}
.shrink-0 { .shrink-0 {
flex-shrink: 0; flex-shrink: 0;
} }
@@ -2827,6 +2857,10 @@ body {
flex-wrap: wrap; flex-wrap: wrap;
} }
.gap-5 {
gap: 1.25rem;
}
.overflow-hidden { .overflow-hidden {
overflow: hidden; overflow: hidden;
} }

View File

@@ -80,6 +80,9 @@
} }
@layer components { @layer components {
* {
@apply transition-colors duration-100
}
/* TODO: check what can be inlined (eg. used once in the code, has no double paths etc...) */ /* TODO: check what can be inlined (eg. used once in the code, has no double paths etc...) */
/* Everything related to theme color */ /* Everything related to theme color */
@@ -118,19 +121,15 @@
} }
.ha-footer .ha-themetoggles { .ha-footer .ha-themetoggles {
@apply bg-slate-200 shadow-inner @apply bg-slate-200 dark:bg-slate-800 transition-colors duration-300 shadow-inner
} }
.ha-footer .ha-themetoggles #ha-toggledark:checked ~ .ha-themetoggleslider { .ha-footer .ha-themetoggles #ha-toggledark:checked ~ .ha-themetoggleslider {
@apply bg-slate-800 @apply bg-slate-200 transition-colors duration-300
}
.ha-footer .ha-themetoggles #ha-toggletwilight:checked ~ .ha-themetoggleslider {
@apply bg-slate-500
} }
.ha-footer .ha-themetoggles #ha-togglebright:checked ~ .ha-themetoggleslider { .ha-footer .ha-themetoggles #ha-togglebright:checked ~ .ha-themetoggleslider {
@apply bg-slate-50 @apply bg-slate-50 transition-colors duration-300
} }
.ha-static { .ha-static {
@@ -278,7 +277,7 @@
.ha-tradzhtext .ha-marginalbox.ha-expanded-box .ha-marginallist, .ha-tradzhtext .ha-marginalbox.ha-expanded-box .ha-marginallist,
.ha-lettertext .ha-marginalbox.ha-expanded-box .ha-marginallist { .ha-lettertext .ha-marginalbox.ha-expanded-box .ha-marginallist {
@apply shadow-md dark:shadow-lg pb-1 @apply shadow-md dark:shadow-lg pb-1 bg-slate-100 dark:bg-slate-600
} }
.ha-tradzhtext .ha-btn-collapsed-box, .ha-tradzhtext .ha-btn-collapsed-box,
@@ -322,7 +321,7 @@
@apply !text-hamannHighlight dark:!text-gray-800 @apply !text-hamannHighlight dark:!text-gray-800
} }
/* Classes */
body { body {
@apply text-base desktop:text-lg w-full h-full; @apply text-base desktop:text-lg w-full h-full;
} }
@@ -373,7 +372,7 @@
} }
.ha-footer .ha-themetoggles { .ha-footer .ha-themetoggles {
@apply whitespace-nowrap relative px-0.5 rounded-3xl h-4 w-[49px] @apply whitespace-nowrap relative px-0.5 rounded-3xl h-4 w-[34px]
} }
.ha-footer .ha-themetoggles * { .ha-footer .ha-themetoggles * {
@@ -396,12 +395,8 @@
@apply left-1 @apply left-1
} }
.ha-footer .ha-themetoggles #ha-toggletwilight:checked ~ .ha-themetoggleslider {
@apply left-[19px]
}
.ha-footer .ha-themetoggles #ha-togglebright:checked ~ .ha-themetoggleslider { .ha-footer .ha-themetoggles #ha-togglebright:checked ~ .ha-themetoggleslider {
@apply left-[34px] @apply left-[19px]
} }
/* Classes for static pages */ /* Classes for static pages */
@@ -898,7 +893,7 @@
.ha-tradzhtext .ha-marginalbox .ha-marginallist, .ha-tradzhtext .ha-marginalbox .ha-marginallist,
.ha-lettertext .ha-marginalbox .ha-marginallist { .ha-lettertext .ha-marginalbox .ha-marginallist {
@apply text-sm leading-tight flex flex-wrap gap-x-6 @apply text-sm leading-tight flex flex-wrap gap-x-6 pr-1
} }
.ha-tradzhtext .ha-marginalbox .ha-marginallist .ha-marginal, .ha-tradzhtext .ha-marginalbox .ha-marginallist .ha-marginal,

View File

@@ -221,21 +221,8 @@ const go_to_dark = function () {
document.documentElement.classList.add("dark"); document.documentElement.classList.add("dark");
}; };
const go_to_twilight = function () {
document.documentElement.classList.remove("dark");
let elements = document.getElementsByClassName("ha-twilighttogglebar");
for (let el of elements) {
el.classList.add("dark");
}
localStorage.setItem("theme", "ha-toggletwilight");
};
const go_to_bright = function () { const go_to_bright = function () {
document.documentElement.classList.remove("dark"); document.documentElement.classList.remove("dark");
let elements = document.getElementsByClassName("ha-twilighttogglebar");
for (let el of elements) {
el.classList.remove("dark");
}
localStorage.setItem("theme", "ha-togglebright"); localStorage.setItem("theme", "ha-togglebright");
}; };
@@ -335,7 +322,6 @@ window.addEventListener("load", function () {
// Register theme toggler // Register theme toggler
if ( if (
document.getElementById("ha-togglebright") !== null && document.getElementById("ha-togglebright") !== null &&
document.getElementById("ha-toggletwilight") !== null &&
this.document.getElementById("ha-toggledark") !== null this.document.getElementById("ha-toggledark") !== null
) { ) {
document document
@@ -344,9 +330,6 @@ window.addEventListener("load", function () {
document document
.getElementById("ha-toggledark") .getElementById("ha-toggledark")
.addEventListener("click", go_to_dark); .addEventListener("click", go_to_dark);
document
.getElementById("ha-toggletwilight")
.addEventListener("click", go_to_twilight);
} }
get_theme_settings("ha-togglebright"); get_theme_settings("ha-togglebright");
}); });

View File

@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<opus><definitions></definitions></opus> <opus></opus>