mirror of
				https://github.com/Theodor-Springmann-Stiftung/hamann-ausgabe-core.git
				synced 2025-10-30 09:45:32 +00:00 
			
		
		
		
	Creaded Upload Interface; Also added Wrapper around HaDocument to reload the Document
This commit is contained in:
		
							
								
								
									
										145
									
								
								HaWeb/Controllers/APIController.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								HaWeb/Controllers/APIController.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,145 @@ | |||||||
|  | namespace HaWeb.Controllers; | ||||||
|  | using Microsoft.AspNetCore.Mvc; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using Microsoft.AspNetCore.Http; | ||||||
|  | using Microsoft.AspNetCore.WebUtilities; | ||||||
|  | using Microsoft.Extensions.Configuration; | ||||||
|  | using Microsoft.Net.Http.Headers; | ||||||
|  | using HaWeb.Filters; | ||||||
|  | using HaWeb.FileHelpers; | ||||||
|  | using HaWeb.XMLParser; | ||||||
|  | using System.Text.Json.Serialization; | ||||||
|  | using System.Text.Json; | ||||||
|  | using HaDocument.Interfaces; | ||||||
|  | using HaXMLReader.Interfaces; | ||||||
|  | using Microsoft.FeatureManagement.Mvc; | ||||||
|  | using System.Runtime.InteropServices; | ||||||
|  | using Microsoft.AspNetCore.Http.Features; | ||||||
|  |  | ||||||
|  | // Controlling all the API-Endpoints | ||||||
|  | public class APIController : Controller { | ||||||
|  |  | ||||||
|  |     // DI | ||||||
|  |     private ILibrary _lib; | ||||||
|  |     private IReaderService _readerService; | ||||||
|  |     private readonly long _fileSizeLimit; | ||||||
|  |     private readonly string _targetFilePath; | ||||||
|  |     private readonly IXMLService _xmlService; | ||||||
|  |  | ||||||
|  |     // Options | ||||||
|  |     private static readonly string[] _permittedExtensions = { ".xml" }; | ||||||
|  |     private static readonly FormOptions _defaultFormOptions = new FormOptions(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     public APIController(ILibrary lib, IReaderService readerService, IXMLService xmlService, IConfiguration config) { | ||||||
|  |         _lib = lib; | ||||||
|  |         _readerService = readerService; | ||||||
|  |         _xmlService = xmlService; | ||||||
|  |         _fileSizeLimit = config.GetValue<long>("FileSizeLimit"); | ||||||
|  |         if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { | ||||||
|  |             _targetFilePath = config.GetValue<string>("StoredFilePathWindows"); | ||||||
|  |         } else { | ||||||
|  |             _targetFilePath = config.GetValue<string>("StoredFilePathLinux"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     [HttpGet] | ||||||
|  |     [Route("API/Syntaxcheck/{id}")] | ||||||
|  |     [DisableFormValueModelBinding] | ||||||
|  |     [ValidateAntiForgeryToken] | ||||||
|  |     [FeatureGate(Features.AdminService)] | ||||||
|  |     public async Task<IActionResult> SyntaxCheck(string id) { | ||||||
|  |         return Ok(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     //// UPLOAD //// | ||||||
|  |     [HttpPost] | ||||||
|  |     [Route("API/Upload")] | ||||||
|  |     [DisableFormValueModelBinding] | ||||||
|  |     [ValidateAntiForgeryToken] | ||||||
|  |     [FeatureGate(Features.UploadService)] | ||||||
|  |     public async Task<IActionResult> Upload(string? id) { | ||||||
|  |         List<XMLRootDocument>? docs = null; | ||||||
|  |         //// 1. Stage: Check Request format and request spec | ||||||
|  |         // Checks the Content-Type Field (must be multipart + Boundary) | ||||||
|  |         if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType)) { | ||||||
|  |             ModelState.AddModelError("Error", $"Wrong / No Content Type on the Request"); | ||||||
|  |             return BadRequest(ModelState); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Divides the multipart document into it's sections and sets up a reader | ||||||
|  |         var boundary = MultipartRequestHelper.GetBoundary(MediaTypeHeaderValue.Parse(Request.ContentType), _defaultFormOptions.MultipartBoundaryLengthLimit); | ||||||
|  |         var reader = new MultipartReader(boundary, HttpContext.Request.Body); | ||||||
|  |         MultipartSection? section = null; | ||||||
|  |         try { | ||||||
|  |             section = await reader.ReadNextSectionAsync(); | ||||||
|  |         } catch (Exception ex) { | ||||||
|  |             ModelState.AddModelError("Error", "The Request is bad: " + ex.Message); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         while (section != null) { | ||||||
|  |             // Multipart document content disposition header read for a section: | ||||||
|  |             // Starts with boundary, contains field name, content-dispo, filename, content-type | ||||||
|  |             var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition); | ||||||
|  |  | ||||||
|  |             if (contentDisposition != null && contentDisposition.Name == "__RequestVerificationToken") { | ||||||
|  |                 try { | ||||||
|  |                     section = await reader.ReadNextSectionAsync(); | ||||||
|  |                 } catch (Exception ex) { | ||||||
|  |                     ModelState.AddModelError("Error", "The Request is bad: " + ex.Message); | ||||||
|  |                 } | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (hasContentDispositionHeader && contentDisposition != null) { | ||||||
|  |                 // Checks if it is a section with content-disposition, name, filename | ||||||
|  |                 if (!MultipartRequestHelper.HasFileContentDisposition(contentDisposition)) { | ||||||
|  |                     ModelState.AddModelError("Error", $"Wrong Content-Dispostion Headers in Multipart Document"); | ||||||
|  |                     return BadRequest(ModelState); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 //// 2. Stage: Check File. Sanity checks on the file on a byte level, extension checking, is it empty etc. | ||||||
|  |                 var streamedFileContent = await XMLFileHelpers.ProcessStreamedFile( | ||||||
|  |                     section, contentDisposition, ModelState, | ||||||
|  |                     _permittedExtensions, _fileSizeLimit); | ||||||
|  |                 if (!ModelState.IsValid || streamedFileContent == null) | ||||||
|  |                     return BadRequest(ModelState); | ||||||
|  |  | ||||||
|  |                 //// 3. Stage: Valid XML checking using a simple XDocument.Load() | ||||||
|  |                 var xdocument = await XDocumentFileHelper.ProcessStreamedFile(streamedFileContent, ModelState); | ||||||
|  |                 if (!ModelState.IsValid || xdocument == null) | ||||||
|  |                     return UnprocessableEntity(ModelState); | ||||||
|  |  | ||||||
|  |                 //// 4. Stage: Is it a Hamann-Document? What kind? | ||||||
|  |                 var retdocs = await _xmlService.ProbeHamannFile(xdocument, ModelState); | ||||||
|  |                 if (!ModelState.IsValid || retdocs == null || !retdocs.Any()) | ||||||
|  |                     return UnprocessableEntity(ModelState); | ||||||
|  |  | ||||||
|  |                 //// 5. Stage: Saving the File(s) | ||||||
|  |                 foreach (var doc in retdocs) { | ||||||
|  |                     await _xmlService.UpdateAvailableFiles(doc, _targetFilePath, ModelState); | ||||||
|  |                     if (!ModelState.IsValid) return StatusCode(500, ModelState); | ||||||
|  |                     if (docs == null) docs = new List<XMLRootDocument>(); | ||||||
|  |                     docs.Add(doc); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             try { | ||||||
|  |                 section = await reader.ReadNextSectionAsync(); | ||||||
|  |             } catch (Exception ex) { | ||||||
|  |                 ModelState.AddModelError("Error", "The Request is bad: " + ex.Message); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 6. Stage: Success! Returning Ok, and redirecting  | ||||||
|  |         JsonSerializerOptions options = new() { | ||||||
|  |             ReferenceHandler = ReferenceHandler.Preserve, | ||||||
|  |             Converters = { | ||||||
|  |                 new IdentificationStringJSONConverter() | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         string json = JsonSerializer.Serialize(docs); | ||||||
|  |         return Created(nameof(UploadController), json); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -3,13 +3,13 @@ using Microsoft.AspNetCore.Mvc; | |||||||
| using HaDocument.Interfaces; | using HaDocument.Interfaces; | ||||||
| using HaXMLReader.Interfaces; | using HaXMLReader.Interfaces; | ||||||
| using Microsoft.FeatureManagement.Mvc; | using Microsoft.FeatureManagement.Mvc; | ||||||
|  | using HaWeb.FileHelpers; | ||||||
| public class AdminController : Controller { | public class AdminController : Controller { | ||||||
|     // DI |     // DI | ||||||
|     private ILibrary _lib; |     private IHaDocumentWrappper _lib; | ||||||
|     private IReaderService _readerService; |     private IReaderService _readerService; | ||||||
|  |  | ||||||
|     public AdminController(ILibrary lib, IReaderService readerService) { |     public AdminController(IHaDocumentWrappper lib, IReaderService readerService) { | ||||||
|         _lib = lib; |         _lib = lib; | ||||||
|         _readerService = readerService; |         _readerService = readerService; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| using System.Diagnostics; | using System.Diagnostics; | ||||||
| using Microsoft.AspNetCore.Mvc; | using Microsoft.AspNetCore.Mvc; | ||||||
| using HaWeb.Models; | using HaWeb.Models; | ||||||
|  | using HaWeb.FileHelpers; | ||||||
| using HaDocument.Interfaces; | using HaDocument.Interfaces; | ||||||
| using HaXMLReader.Interfaces; | using HaXMLReader.Interfaces; | ||||||
| using HaDocument.Models; | using HaDocument.Models; | ||||||
| @@ -12,10 +13,10 @@ public class Briefecontroller : Controller { | |||||||
|     public string? id { get; set; } |     public string? id { get; set; } | ||||||
|  |  | ||||||
|     // DI |     // DI | ||||||
|     private ILibrary _lib; |     private IHaDocumentWrappper _lib; | ||||||
|     private IReaderService _readerService; |     private IReaderService _readerService; | ||||||
|  |  | ||||||
|     public Briefecontroller(ILibrary lib, IReaderService readerService) { |     public Briefecontroller(IHaDocumentWrappper lib, IReaderService readerService) { | ||||||
|         _lib = lib; |         _lib = lib; | ||||||
|         _readerService = readerService; |         _readerService = readerService; | ||||||
|     } |     } | ||||||
| @@ -24,25 +25,26 @@ public class Briefecontroller : Controller { | |||||||
|     [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 lib = _lib.GetLibrary(); | ||||||
|         var url = "/Briefe/"; |         var url = "/Briefe/"; | ||||||
|         var defaultID = "1"; |         var defaultID = "1"; | ||||||
|  |  | ||||||
|         // Normalisation and Validation, (some) data aquisition |         // Normalisation and Validation, (some) data aquisition | ||||||
|         if (id == null) return Redirect(url + defaultID); |         if (id == null) return Redirect(url + defaultID); | ||||||
|         this.id = id.ToLower(); |         this.id = id.ToLower(); | ||||||
|         var preliminarymeta = _lib.Metas.Where(x => x.Value.Autopsic == this.id); |         var preliminarymeta = lib.Metas.Where(x => x.Value.Autopsic == this.id); | ||||||
|         if (preliminarymeta == null || !preliminarymeta.Any()) return error404(); |         if (preliminarymeta == null || !preliminarymeta.Any()) return error404(); | ||||||
|  |  | ||||||
|         // Get all neccessary data |         // Get all neccessary data | ||||||
|         var index = preliminarymeta.First().Key; |         var index = preliminarymeta.First().Key; | ||||||
|         var meta = preliminarymeta.First().Value; |         var meta = preliminarymeta.First().Value; | ||||||
|         var text = _lib.Letters.ContainsKey(index) ? _lib.Letters[index] : null; |         var text = lib.Letters.ContainsKey(index) ? lib.Letters[index] : null; | ||||||
|         var marginals = _lib.MarginalsByLetter.Contains(index) ? _lib.MarginalsByLetter[index] : null; |         var marginals = lib.MarginalsByLetter.Contains(index) ? lib.MarginalsByLetter[index] : null; | ||||||
|         var tradition = _lib.Traditions.ContainsKey(index) ? _lib.Traditions[index] : null; |         var tradition = lib.Traditions.ContainsKey(index) ? lib.Traditions[index] : null; | ||||||
|         var editreasons = _lib.Editreasons.ContainsKey(index) ? _lib.EditreasonsByLetter[index] : null; // TODO: Order |         var editreasons = lib.Editreasons.ContainsKey(index) ? lib.EditreasonsByLetter[index] : null; // TODO: Order | ||||||
|         var hands = _lib.Hands.ContainsKey(index) ? _lib.Hands[index] : null; |         var hands = lib.Hands.ContainsKey(index) ? lib.Hands[index] : null; | ||||||
|         var nextmeta = meta != _lib.MetasByDate.Last() ? _lib.MetasByDate.ItemRef(_lib.MetasByDate.IndexOf(meta) + 1) : null; |         var nextmeta = meta != lib.MetasByDate.Last() ? lib.MetasByDate.ItemRef(lib.MetasByDate.IndexOf(meta) + 1) : null; | ||||||
|         var prevmeta = meta != _lib.MetasByDate.First() ? _lib.MetasByDate.ItemRef(_lib.MetasByDate.IndexOf(meta) - 1) : null; |         var prevmeta = meta != lib.MetasByDate.First() ? lib.MetasByDate.ItemRef(lib.MetasByDate.IndexOf(meta) - 1) : null; | ||||||
|  |  | ||||||
|         // More Settings and variables |         // More Settings and variables | ||||||
|         ViewData["Title"] = "Brief " + id.ToLower(); |         ViewData["Title"] = "Brief " + id.ToLower(); | ||||||
| @@ -52,17 +54,17 @@ public class Briefecontroller : Controller { | |||||||
|         // Model creation |         // Model creation | ||||||
|         var hasMarginals = false; |         var hasMarginals = false; | ||||||
|         if (marginals != null && marginals.Any()) hasMarginals = true; |         if (marginals != null && marginals.Any()) hasMarginals = true; | ||||||
|         var model = new BriefeViewModel(this.id, index, generateMetaViewModel(meta, hasMarginals)); |         var model = new BriefeViewModel(this.id, index, generateMetaViewModel(lib, meta, hasMarginals)); | ||||||
|         if (nextmeta != null) model.MetaData.Next = (generateMetaViewModel(nextmeta, false), url + nextmeta.Autopsic); |         if (nextmeta != null) model.MetaData.Next = (generateMetaViewModel(lib, nextmeta, false), url + nextmeta.Autopsic); | ||||||
|         if (prevmeta != null) model.MetaData.Prev = (generateMetaViewModel(prevmeta, false), url + prevmeta.Autopsic); |         if (prevmeta != null) model.MetaData.Prev = (generateMetaViewModel(lib, prevmeta, false), url + prevmeta.Autopsic); | ||||||
|         if (hands != null && hands.Any()) model.ParsedHands = HaWeb.HTMLHelpers.LetterHelpers.CreateHands(_lib, hands); |         if (hands != null && hands.Any()) model.ParsedHands = HaWeb.HTMLHelpers.LetterHelpers.CreateHands(lib, hands); | ||||||
|         if (editreasons != null && editreasons.Any()) model.ParsedEdits = HaWeb.HTMLHelpers.LetterHelpers.CreateEdits(_lib, _readerService, editreasons); |         if (editreasons != null && editreasons.Any()) model.ParsedEdits = HaWeb.HTMLHelpers.LetterHelpers.CreateEdits(lib, _readerService, editreasons); | ||||||
|         if (tradition != null && !String.IsNullOrWhiteSpace(tradition.Element)) { |         if (tradition != null && !String.IsNullOrWhiteSpace(tradition.Element)) { | ||||||
|             var parsedTraditions = HaWeb.HTMLHelpers.LetterHelpers.CreateTraditions(_lib, _readerService, marginals, tradition, hands, editreasons); |             var parsedTraditions = HaWeb.HTMLHelpers.LetterHelpers.CreateTraditions(lib, _readerService, marginals, tradition, hands, editreasons); | ||||||
|             (model.ParsedTradition, model.ParsedMarginals, model.MinWidthTrad) = (parsedTraditions.sb_tradition.ToString(), parsedTraditions.ParsedMarginals, parsedTraditions.minwidth); |             (model.ParsedTradition, model.ParsedMarginals, model.MinWidthTrad) = (parsedTraditions.sb_tradition.ToString(), parsedTraditions.ParsedMarginals, parsedTraditions.minwidth); | ||||||
|         } |         } | ||||||
|         if (text != null && !String.IsNullOrWhiteSpace(text.Element)) { |         if (text != null && !String.IsNullOrWhiteSpace(text.Element)) { | ||||||
|             var parsedLetter = HaWeb.HTMLHelpers.LetterHelpers.CreateLetter(_lib, _readerService, meta, text, marginals, hands, editreasons); |             var parsedLetter = HaWeb.HTMLHelpers.LetterHelpers.CreateLetter(lib, _readerService, meta, text, marginals, hands, editreasons); | ||||||
|             (model.ParsedText, model.MinWidth) = (parsedLetter.sb_lettertext.ToString(), parsedLetter.minwidth); |             (model.ParsedText, model.MinWidth) = (parsedLetter.sb_lettertext.ToString(), parsedLetter.minwidth); | ||||||
|             if (model.ParsedMarginals != null && parsedLetter.ParsedMarginals != null) model.ParsedMarginals.AddRange(parsedLetter.ParsedMarginals); |             if (model.ParsedMarginals != null && parsedLetter.ParsedMarginals != null) model.ParsedMarginals.AddRange(parsedLetter.ParsedMarginals); | ||||||
|             else model.ParsedMarginals = parsedLetter.ParsedMarginals; |             else model.ParsedMarginals = parsedLetter.ParsedMarginals; | ||||||
| @@ -81,9 +83,9 @@ public class Briefecontroller : Controller { | |||||||
|         return Redirect("/Error404"); |         return Redirect("/Error404"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private BriefeMetaViewModel generateMetaViewModel(Meta meta, bool hasMarginals) { |     private BriefeMetaViewModel generateMetaViewModel(ILibrary lib, 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, | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ using HaXMLReader; | |||||||
| using HaXMLReader.EvArgs; | using HaXMLReader.EvArgs; | ||||||
| using HaDocument.Models; | using HaDocument.Models; | ||||||
| using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||||
|  | using HaWeb.FileHelpers; | ||||||
|  |  | ||||||
| namespace HaWeb.Controllers; | namespace HaWeb.Controllers; | ||||||
|  |  | ||||||
| @@ -16,20 +17,18 @@ public class RegisterController : Controller { | |||||||
|     [BindProperty(SupportsGet = true)] |     [BindProperty(SupportsGet = true)] | ||||||
|     public string? search { get; set; } |     public string? search { get; set; } | ||||||
|  |  | ||||||
|     [BindProperty(SupportsGet = true)] |  | ||||||
|     public string? id { get; set; } |  | ||||||
|  |  | ||||||
|     // DI |     // DI | ||||||
|     private ILibrary _lib; |     private IHaDocumentWrappper _lib; | ||||||
|     private IReaderService _readerService; |     private IReaderService _readerService; | ||||||
|  |  | ||||||
|     public RegisterController(ILibrary lib, IReaderService readerService) { |     public RegisterController(IHaDocumentWrappper 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 lib = _lib.GetLibrary(); | ||||||
|         var url = "/Register/Register/"; |         var url = "/Register/Register/"; | ||||||
|         var category = "neuzeit"; |         var category = "neuzeit"; | ||||||
|         var defaultLetter = "A"; |         var defaultLetter = "A"; | ||||||
| @@ -39,30 +38,30 @@ public class RegisterController : Controller { | |||||||
|  |  | ||||||
|         // Normalisation and validation |         // Normalisation and validation | ||||||
|         if (id == null) return Redirect(url + defaultLetter); |         if (id == null) return Redirect(url + defaultLetter); | ||||||
|         normalizeID(id, defaultLetter); |         id = normalizeID(id, defaultLetter); | ||||||
|         if (!_lib.CommentsByCategoryLetter[category].Contains(this.id)) return error404(); |         if (!lib.CommentsByCategoryLetter[category].Contains(id)) return error404(); | ||||||
|  |  | ||||||
|         // Data aquisition and validation |         // Data aquisition and validation | ||||||
|         var comments = _lib.CommentsByCategoryLetter[category][this.id].OrderBy(x => x.Index); |         var comments = lib.CommentsByCategoryLetter[category][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(); | ||||||
|         if (comments == null) return error404(); |         if (comments == null) return error404(); | ||||||
|  |  | ||||||
|         // 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)); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             res.Add(new CommentModel(parsedComment, parsedSubComments)); |             res.Add(new CommentModel(parsedComment, parsedSubComments)); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Model instantiation |         // Model instantiation | ||||||
|         var model = new RegisterViewModel(category, this.id, res, title) { |         var model = new RegisterViewModel(category, id, res, title) { | ||||||
|             AvailableCategories = availableCategories, |             AvailableCategories = availableCategories, | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
| @@ -72,6 +71,7 @@ public class RegisterController : Controller { | |||||||
|  |  | ||||||
|     public IActionResult Bibelstellen(string? id) { |     public IActionResult Bibelstellen(string? id) { | ||||||
|         // Setup settings and variables |         // Setup settings and variables | ||||||
|  |         var lib = _lib.GetLibrary(); | ||||||
|         var url = "/Register/Bibelstellen/"; |         var url = "/Register/Bibelstellen/"; | ||||||
|         var category = "bibel"; |         var category = "bibel"; | ||||||
|         var defaultLetter = "AT"; |         var defaultLetter = "AT"; | ||||||
| @@ -81,30 +81,30 @@ public class RegisterController : Controller { | |||||||
|  |  | ||||||
|         // Normalisation and Validation |         // Normalisation and Validation | ||||||
|         if (id == null) return Redirect(url + defaultLetter); |         if (id == null) return Redirect(url + defaultLetter); | ||||||
|         normalizeID(id, defaultLetter); |         id = normalizeID(id, defaultLetter); | ||||||
|         if (this.id != "AT" && this.id != "AP" && this.id != "NT") return error404(); |         if (id != "AT" && id != "AP" && id != "NT") return error404(); | ||||||
|  |  | ||||||
|         // Data aquisition and validation |         // Data aquisition and validation | ||||||
|         var comments = _lib.CommentsByCategory[category].ToLookup(x => x.Index.Substring(0, 2).ToUpper())[this.id].OrderBy(x => x.Order); |         var comments = lib.CommentsByCategory[category].ToLookup(x => x.Index.Substring(0, 2).ToUpper())[id].OrderBy(x => x.Order); | ||||||
|         var availableCategories = new List<(string, string)>() { ("Altes Testament", url + "AT"), ("Apogryphen", url + "AP"), ("Neues Testament", url + "NT") }; |         var availableCategories = new List<(string, string)>() { ("Altes Testament", url + "AT"), ("Apogryphen", url + "AP"), ("Neues Testament", url + "NT") }; | ||||||
|         if (comments == null) return error404(); |         if (comments == null) return error404(); | ||||||
|  |  | ||||||
|         // 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)); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             res.Add(new CommentModel(parsedComment, parsedSubComments)); |             res.Add(new CommentModel(parsedComment, parsedSubComments)); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Model instantiation |         // Model instantiation | ||||||
|         var model = new RegisterViewModel(category, this.id, res, title) { |         var model = new RegisterViewModel(category, id, res, title) { | ||||||
|             AvailableCategories = availableCategories, |             AvailableCategories = availableCategories, | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
| @@ -114,6 +114,7 @@ public class RegisterController : Controller { | |||||||
|  |  | ||||||
|     public IActionResult Forschung(string? id) { |     public IActionResult Forschung(string? id) { | ||||||
|         // Setup settings and variables |         // Setup settings and variables | ||||||
|  |         var lib = _lib.GetLibrary(); | ||||||
|         var url = "/Register/Forschung/"; |         var url = "/Register/Forschung/"; | ||||||
|         var category = "forschung"; |         var category = "forschung"; | ||||||
|         var defaultLetter = "A"; |         var defaultLetter = "A"; | ||||||
| @@ -123,37 +124,37 @@ public class RegisterController : Controller { | |||||||
|  |  | ||||||
|         // Normalisation and Validation |         // Normalisation and Validation | ||||||
|         if (id == null) return Redirect(url + defaultLetter); |         if (id == null) return Redirect(url + defaultLetter); | ||||||
|         normalizeID(id, defaultLetter); |         id = normalizeID(id, defaultLetter); | ||||||
|         if (this.id != "EDITIONEN" && !_lib.CommentsByCategoryLetter[category].Contains(this.id)) return error404(); |         if (id != "EDITIONEN" && !lib.CommentsByCategoryLetter[category].Contains(id)) return error404(); | ||||||
|         if (this.id == "EDITIONEN" && !_lib.CommentsByCategoryLetter.Keys.Contains(this.id.ToLower())) return error404(); |         if (id == "EDITIONEN" && !lib.CommentsByCategoryLetter.Keys.Contains(id.ToLower())) return error404(); | ||||||
|  |  | ||||||
|         // Data aquisition and validation |         // Data aquisition and validation | ||||||
|         IOrderedEnumerable<Comment>? comments = null; |         IOrderedEnumerable<Comment>? comments = null; | ||||||
|         if (this.id == "EDITIONEN") { |         if (id == "EDITIONEN") { | ||||||
|             comments = _lib.CommentsByCategory[this.id.ToLower()].OrderBy(x => x.Index); |             comments = lib.CommentsByCategory[id.ToLower()].OrderBy(x => x.Index); | ||||||
|         } else { |         } else { | ||||||
|             comments = _lib.CommentsByCategoryLetter[category][this.id].OrderBy(x => x.Index); |             comments = lib.CommentsByCategoryLetter[category][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(); | ||||||
|         var AvailableSideCategories = new List<(string, string)>() { ("Editionen", "Editionen") }; |         var AvailableSideCategories = new List<(string, string)>() { ("Editionen", "Editionen") }; | ||||||
|         if (comments == null) return error404(); |         if (comments == null) return error404(); | ||||||
|  |  | ||||||
|         // 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)); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             res.Add(new CommentModel(parsedComment, parsedSubComments)); |             res.Add(new CommentModel(parsedComment, parsedSubComments)); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Model instantiation |         // Model instantiation | ||||||
|         var model = new RegisterViewModel(category, this.id, res, title) { |         var model = new RegisterViewModel(category, id, res, title) { | ||||||
|             AvailableCategories = availableCategories, |             AvailableCategories = availableCategories, | ||||||
|             AvailableSideCategories = AvailableSideCategories |             AvailableSideCategories = AvailableSideCategories | ||||||
|         }; |         }; | ||||||
| @@ -162,11 +163,13 @@ public class RegisterController : Controller { | |||||||
|         return View("Index", model); |         return View("Index", model); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void normalizeID(string? id, string defaultid) { |     private string? normalizeID(string? id, string defaultid) { | ||||||
|         this.id = this.id.ToUpper(); |         if (id == null) return null; | ||||||
|  |         return id.ToUpper(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private bool validationCheck(HashSet<string> permitted) { |     private bool validationCheck(string? id, HashSet<string> permitted) { | ||||||
|  |         if (id == null) return false; | ||||||
|         if (!permitted.Contains(id)) { |         if (!permitted.Contains(id)) { | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -1,23 +0,0 @@ | |||||||
| namespace HaWeb.Controllers; |  | ||||||
| using Microsoft.AspNetCore.Mvc; |  | ||||||
| using HaDocument.Interfaces; |  | ||||||
| using HaXMLReader.Interfaces; |  | ||||||
| using Microsoft.FeatureManagement.Mvc; |  | ||||||
|  |  | ||||||
| public class UpdateController : Controller { |  | ||||||
|     // DI |  | ||||||
|     private ILibrary _lib; |  | ||||||
|     private IReaderService _readerService; |  | ||||||
|  |  | ||||||
|     public UpdateController(ILibrary lib, IReaderService readerService) { |  | ||||||
|         _lib = lib; |  | ||||||
|         _readerService = readerService; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     [Route("Admin/Update")] |  | ||||||
|     [FeatureGate(Features.UpdateService)] |  | ||||||
|     public IActionResult Index() { |  | ||||||
|         return View("../Admin/Upload/Index"); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -3,27 +3,17 @@ using Microsoft.AspNetCore.Mvc; | |||||||
| using HaDocument.Interfaces; | using HaDocument.Interfaces; | ||||||
| using HaXMLReader.Interfaces; | using HaXMLReader.Interfaces; | ||||||
| using Microsoft.FeatureManagement.Mvc; | using Microsoft.FeatureManagement.Mvc; | ||||||
| using System.IO; |  | ||||||
| using System.Net; |  | ||||||
| using System.Text; |  | ||||||
| using System.Runtime.InteropServices; | using System.Runtime.InteropServices; | ||||||
| using System.Threading.Tasks; |  | ||||||
| using Microsoft.AspNetCore.Http; |  | ||||||
| using Microsoft.AspNetCore.Http.Features; | using Microsoft.AspNetCore.Http.Features; | ||||||
| using Microsoft.AspNetCore.WebUtilities; |  | ||||||
| using Microsoft.Extensions.Configuration; | using Microsoft.Extensions.Configuration; | ||||||
| using Microsoft.Net.Http.Headers; |  | ||||||
| using HaWeb.Filters; | using HaWeb.Filters; | ||||||
| using HaWeb.FileHelpers; |  | ||||||
| using HaWeb.XMLParser; | using HaWeb.XMLParser; | ||||||
| using HaWeb.Models; | using HaWeb.Models; | ||||||
| using System.Xml.Linq; | using HaWeb.FileHelpers; | ||||||
| using System.Text.Json.Serialization; |  | ||||||
| using System.Text.Json; |  | ||||||
|  |  | ||||||
| public class UploadController : Controller { | public class UploadController : Controller { | ||||||
|     // DI |     // DI | ||||||
|     private ILibrary _lib; |     private IHaDocumentWrappper _lib; | ||||||
|     private IReaderService _readerService; |     private IReaderService _readerService; | ||||||
|     private readonly long _fileSizeLimit; |     private readonly long _fileSizeLimit; | ||||||
|     private readonly string _targetFilePath; |     private readonly string _targetFilePath; | ||||||
| @@ -34,7 +24,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(IHaDocumentWrappper lib, IReaderService readerService, IXMLService xmlService, IConfiguration config) { | ||||||
|         _lib = lib; |         _lib = lib; | ||||||
|         _readerService = readerService; |         _readerService = readerService; | ||||||
|         _xmlService = xmlService; |         _xmlService = xmlService; | ||||||
| @@ -48,94 +38,35 @@ public class UploadController : Controller { | |||||||
|  |  | ||||||
|     [HttpGet] |     [HttpGet] | ||||||
|     [Route("Admin/Upload/{id?}")] |     [Route("Admin/Upload/{id?}")] | ||||||
|     [FeatureGate(Features.UploadService)] |     [FeatureGate(Features.AdminService)] | ||||||
|     [GenerateAntiforgeryTokenCookie] |     [GenerateAntiforgeryTokenCookie] | ||||||
|     public IActionResult Index(string? id) { |     public IActionResult Index(string? id) { | ||||||
|         var model = new UploadViewModel(); |         if (id != null) { | ||||||
|         model.AvailableRoots = _xmlService.GetRoots(); |             var root = _xmlService.GetRoot(id); | ||||||
|         model.UsedFiles = _xmlService.GetUsed(); |             if (root == null) return error404(); | ||||||
|         var hello = "mama"; |  | ||||||
|  |             var roots = _xmlService.GetRoots(); | ||||||
|  |             if (roots == null) return error404(); | ||||||
|  |  | ||||||
|  |             var usedFiles = _xmlService.GetUsed(); | ||||||
|  |             var availableFiles = _xmlService.GetAvailableFiles(id); | ||||||
|  |  | ||||||
|  |             var model = new UploadViewModel(root.Type, id, roots, availableFiles, usedFiles); | ||||||
|             return View("../Admin/Upload/Index", model); |             return View("../Admin/Upload/Index", model); | ||||||
|         } |         } | ||||||
|  |         else { | ||||||
|  |             var roots = _xmlService.GetRoots(); | ||||||
|  |             if (roots == null) return error404(); | ||||||
|  |  | ||||||
|  |             var usedFiles = _xmlService.GetUsed(); | ||||||
|  |  | ||||||
|     //// UPLOAD //// |             var model = new UploadViewModel("Upload", id, roots, null, usedFiles); | ||||||
|     [HttpPost] |             return View("../Admin/Upload/Index", model); | ||||||
|     [Route("Admin/Upload")] |  | ||||||
|     [DisableFormValueModelBinding] |  | ||||||
|     [ValidateAntiForgeryToken] |  | ||||||
|     public async Task<IActionResult> Post() { |  | ||||||
|         List<XMLRootDocument>? docs = null; |  | ||||||
|         //// 1. Stage: Check Request format and request spec |  | ||||||
|         // Checks the Content-Type Field (must be multipart + Boundary) |  | ||||||
|         if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType)) { |  | ||||||
|             ModelState.AddModelError("Error", $"Wrong / No Content Type on the Request"); |  | ||||||
|             return BadRequest(ModelState); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Divides the multipart document into it's sections and sets up a reader |  | ||||||
|         var boundary = MultipartRequestHelper.GetBoundary(MediaTypeHeaderValue.Parse(Request.ContentType), _defaultFormOptions.MultipartBoundaryLengthLimit); |  | ||||||
|         var reader = new MultipartReader(boundary, HttpContext.Request.Body); |  | ||||||
|         MultipartSection? section = null; |  | ||||||
|         try { |  | ||||||
|             section = await reader.ReadNextSectionAsync(); |  | ||||||
|         } catch (Exception ex) { |  | ||||||
|             ModelState.AddModelError("Error", "The Request is bad: " + ex.Message); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         while (section != null) { |  | ||||||
|             // Multipart document content disposition header read for a section: |  | ||||||
|             // Starts with boundary, contains field name, content-dispo, filename, content-type |  | ||||||
|             var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition); |  | ||||||
|             if (hasContentDispositionHeader && contentDisposition != null) { |  | ||||||
|                 // Checks if it is a section with content-disposition, name, filename |  | ||||||
|                 if (!MultipartRequestHelper.HasFileContentDisposition(contentDisposition)) { |  | ||||||
|                     ModelState.AddModelError("Error", $"Wrong Content-Dispostion Headers in Multipart Document"); |  | ||||||
|                     return BadRequest(ModelState); |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 //// 2. Stage: Check File. Sanity checks on the file on a byte level, extension checking, is it empty etc. |  | ||||||
|                 var streamedFileContent = await XMLFileHelpers.ProcessStreamedFile( |  | ||||||
|                     section, contentDisposition, ModelState, |  | ||||||
|                     _permittedExtensions, _fileSizeLimit); |  | ||||||
|                 if (!ModelState.IsValid || streamedFileContent == null) |  | ||||||
|                     return BadRequest(ModelState); |  | ||||||
|  |  | ||||||
|                 //// 3. Stage: Valid XML checking using a simple XDocument.Load() |  | ||||||
|                 var xdocument = await XDocumentFileHelper.ProcessStreamedFile(streamedFileContent, ModelState); |  | ||||||
|                 if (!ModelState.IsValid || xdocument == null) |  | ||||||
|                     return UnprocessableEntity(ModelState); |  | ||||||
|  |  | ||||||
|                 //// 4. Stage: Is it a Hamann-Document? What kind? |  | ||||||
|                 var retdocs = await _xmlService.ProbeHamannFile(xdocument, ModelState); |  | ||||||
|                 if (!ModelState.IsValid || retdocs == null || !retdocs.Any()) |  | ||||||
|                     return UnprocessableEntity(ModelState); |  | ||||||
|  |  | ||||||
|                 //// 5. Stage: Saving the File(s) |  | ||||||
|                 foreach (var doc in retdocs) { |  | ||||||
|                     await _xmlService.UpdateAvailableFiles(doc, _targetFilePath, ModelState); |  | ||||||
|                     if (!ModelState.IsValid) return StatusCode(500, ModelState); |  | ||||||
|                     if (docs == null) docs = new List<XMLRootDocument>(); |  | ||||||
|                     docs.Add(doc); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|             try { |     private IActionResult error404() { | ||||||
|                 section = await reader.ReadNextSectionAsync(); |         Response.StatusCode = 404; | ||||||
|             } catch (Exception ex) { |         return Redirect("/Error404"); | ||||||
|                 ModelState.AddModelError("Error", "The Request is bad: " + ex.Message); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // 6. Stage: Success! Returning Ok, and redirecting  |  | ||||||
|         JsonSerializerOptions options = new() { |  | ||||||
|             ReferenceHandler = ReferenceHandler.Preserve, |  | ||||||
|             Converters = { |  | ||||||
|                 new IdentificationStringJSONConverter() |  | ||||||
|             } |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         string json = JsonSerializer.Serialize(docs); |  | ||||||
|         return Created(nameof(UploadController), json); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
							
								
								
									
										19
									
								
								HaWeb/FileHelpers/HaDocumentWrapper.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								HaWeb/FileHelpers/HaDocumentWrapper.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | namespace HaWeb.FileHelpers; | ||||||
|  | using HaDocument.Interfaces; | ||||||
|  |  | ||||||
|  | public class HaDocumentWrapper : IHaDocumentWrappper { | ||||||
|  |     public ILibrary Library; | ||||||
|  |  | ||||||
|  |     public HaDocumentWrapper() { | ||||||
|  |         Library = HaDocument.Document.Create(new HaWeb.Settings.HaDocumentOptions()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public ILibrary SetLibrary() { | ||||||
|  |         Library = HaDocument.Document.Create(new HaWeb.Settings.HaDocumentOptions()); | ||||||
|  |         return Library; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public ILibrary GetLibrary() { | ||||||
|  |         return Library; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										7
									
								
								HaWeb/FileHelpers/IHaDocumentWrapper.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								HaWeb/FileHelpers/IHaDocumentWrapper.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | namespace HaWeb.FileHelpers; | ||||||
|  | using HaDocument.Interfaces; | ||||||
|  |  | ||||||
|  | public interface IHaDocumentWrappper { | ||||||
|  |     public ILibrary SetLibrary(); | ||||||
|  |     public ILibrary GetLibrary(); | ||||||
|  | } | ||||||
							
								
								
									
										22
									
								
								HaWeb/Models/SyntaxCheckModel.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								HaWeb/Models/SyntaxCheckModel.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | namespace HaWeb.Models; | ||||||
|  |  | ||||||
|  | public class SyntaxCheckModel { | ||||||
|  |     public string Prefix { get; private set; } | ||||||
|  |     public List<SyntaxError>? Errors { get; set; } | ||||||
|  |     public List<SyntaxError>? Warnings { get; set; } | ||||||
|  |  | ||||||
|  |     public SyntaxCheckModel(string prefix) { | ||||||
|  |         Prefix = prefix; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | public class SyntaxError { | ||||||
|  |     public string Message { get; private set; } | ||||||
|  |     public string? File { get; set; } | ||||||
|  |     public string? Line { get; set; } | ||||||
|  |     public string? Column { get; set; } | ||||||
|  |  | ||||||
|  |     public SyntaxError(string message) { | ||||||
|  |         Message = message; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -2,7 +2,18 @@ namespace HaWeb.Models; | |||||||
| using HaWeb.XMLParser; | using HaWeb.XMLParser; | ||||||
|  |  | ||||||
| public class UploadViewModel { | public class UploadViewModel { | ||||||
|     public List<IXMLRoot>? AvailableRoots { get; set; } |     public string ActiveTitle { get; private set; } | ||||||
|     public List<XMLRootDocument>? AvailableFiles { get; set; } |     public string? Prefix { get; private set; } | ||||||
|     public Dictionary<string, List<XMLRootDocument>>? UsedFiles { get; set; } |     public List<IXMLRoot>? AvailableRoots { get; private set; } | ||||||
|  |     public List<XMLRootDocument>? AvailableFiles { get; private set; } | ||||||
|  |     public Dictionary<string, List<XMLRootDocument>>? UsedFiles { get; private set; } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     public UploadViewModel(string title, string? prefix, List<IXMLRoot>? roots, List<XMLRootDocument>? availableFiles, Dictionary<string, List<XMLRootDocument>>? usedFiles) { | ||||||
|  |         Prefix = prefix; | ||||||
|  |         ActiveTitle = title; | ||||||
|  |         AvailableRoots = roots; | ||||||
|  |         AvailableFiles = availableFiles; | ||||||
|  |         UsedFiles = usedFiles; | ||||||
|  |     } | ||||||
| } | } | ||||||
| @@ -29,7 +29,7 @@ var physicalProvider = new PhysicalFileProvider(filepath); | |||||||
|  |  | ||||||
|  |  | ||||||
| builder.Services.AddSingleton<IFileProvider>(physicalProvider); | builder.Services.AddSingleton<IFileProvider>(physicalProvider); | ||||||
| builder.Services.AddSingleton<ILibrary>(HaDocument.Document.Create(new Options())); | builder.Services.AddSingleton<HaWeb.FileHelpers.IHaDocumentWrappper, HaWeb.FileHelpers.HaDocumentWrapper>(); | ||||||
| builder.Services.AddTransient<IReaderService, ReaderService>(); | builder.Services.AddTransient<IReaderService, ReaderService>(); | ||||||
| builder.Services.AddSingleton<IXMLService, XMLService>(); | builder.Services.AddSingleton<IXMLService, XMLService>(); | ||||||
| builder.Services.AddFeatureManagement(); | builder.Services.AddFeatureManagement(); | ||||||
| @@ -50,12 +50,3 @@ app.UseAuthorization(); | |||||||
| app.UseStaticFiles(); | app.UseStaticFiles(); | ||||||
| app.MapControllers(); | app.MapControllers(); | ||||||
| app.Run(); | app.Run(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Options : IHaDocumentOptions { |  | ||||||
|     public string HamannXMLFilePath { get; set; } = HaWeb.Settings.General.XMLFILEPATH; |  | ||||||
|     public string[] AvailableVolumes { get; set; } = HaWeb.Settings.General.AVAILABLEVOLUMES; |  | ||||||
|     public bool NormalizeWhitespace { get; set; } = HaWeb.Settings.General.NORMALIZEWHITESPACE; |  | ||||||
|     public (int, int) AvailableYearRange {get; set; } = HaWeb.Settings.General.AVAILABLEYEARRANGE; |  | ||||||
| } |  | ||||||
| @@ -2,9 +2,5 @@ namespace HaWeb.Settings; | |||||||
| using HaWeb.Settings.XMLRoots; | using HaWeb.Settings.XMLRoots; | ||||||
|  |  | ||||||
| public static class General { | public static class General { | ||||||
|     // Classes generated by parsing the XML: |  | ||||||
|     public const string XMLFILEPATH = @"Hamann.xml"; |  | ||||||
|     public const bool NORMALIZEWHITESPACE = true; |  | ||||||
|     public static (int, int) AVAILABLEYEARRANGE = (1751, 1788); |  | ||||||
|     public static string[] AVAILABLEVOLUMES = { }; |  | ||||||
| } | } | ||||||
| @@ -1 +1,9 @@ | |||||||
| // TODO | namespace HaWeb.Settings; | ||||||
|  | using HaDocument.Interfaces; | ||||||
|  |  | ||||||
|  | class HaDocumentOptions : IHaDocumentOptions { | ||||||
|  |     public string HamannXMLFilePath { get; set; } = @"Hamann.xml"; | ||||||
|  |     public string[] AvailableVolumes { get; set; } = { }; | ||||||
|  |     public bool NormalizeWhitespace { get; set; } = true; | ||||||
|  |     public (int, int) AvailableYearRange {get; set; } = (1751, 1788); | ||||||
|  | } | ||||||
| @@ -1,42 +1,108 @@ | |||||||
| @model UploadViewModel; | @model UploadViewModel?; | ||||||
|  |  | ||||||
| <div class="ha-adminuploadfields"> | <div class="ha-adminuploadfields" id="ha-adminuploadfields"> | ||||||
|   @foreach (var item in Model.AvailableRoots.OrderBy(x => x.Type)) { |   @foreach (var item in Model.AvailableRoots.OrderBy(x => x.Type)) { | ||||||
|     <div class="ha-uploadfield"> |       <a class="ha-uploadfield" asp-controller="Upload" asp-action="Index" asp-route-id="@item.Prefix"> | ||||||
|         <div class="ha-uploadfieldname">@item.Type</div> |         <div class="ha-uploadfieldname">@item.Type</div> | ||||||
|         @if (Model.UsedFiles != null && Model.UsedFiles.ContainsKey(item.Prefix)) { |         @if (Model.UsedFiles != null && Model.UsedFiles.ContainsKey(item.Prefix)) { | ||||||
|  |           <div class="ha-uploadusedfiles"> | ||||||
|           @foreach(var file in Model.UsedFiles[item.Prefix]) { |           @foreach(var file in Model.UsedFiles[item.Prefix]) { | ||||||
|           <span>@file.File.Name</span> |             @if (file == Model.UsedFiles[item.Prefix].Last()) | ||||||
|  |             { | ||||||
|  |               <span class="ha-uploadusedfile">@file.File.Name</span> | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |               <span class="ha-uploadusedfile">@file.File.Name;</span> | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
|           </div> |           </div> | ||||||
|         } |         } | ||||||
|   <form class="ha-uploadform" id="uploadForm" action="Upload" method="post" enctype="multipart/form-data"> |         else { | ||||||
|       <label class="filelabel" id="dropzone"> |           <div class="ha-uploadusedfiles ha-uploadusedfilesnotfound">Keine Datei geladen!</div> | ||||||
|  |         } | ||||||
|  |       </a> | ||||||
|  |   } | ||||||
|  |   <div class="ha-uploadpublishforms"> | ||||||
|  |     <form class="ha-uploadform" id="uploadForm" asp-controller="API" asp-action="Upload" method="post" enctype="multipart/form-data"> | ||||||
|  |         <label class="ha-uploadfilelabel" id="dropzone"> | ||||||
|           <input class="hidden" id="file" type="file" accept=".xml" name="file" /> |           <input class="hidden" id="file" type="file" accept=".xml" name="file" /> | ||||||
|         Upload2 es |           <div class="ha-uploadtext">Upload</div> | ||||||
|  |           <div class="ha-lds-ellipsis" id="ha-lds-ellipsis"><div></div><div></div><div></div><div></div></div> | ||||||
|  |         </label> | ||||||
|  |         <div class="ha-uploadmessage" id="ha-uploadmessage"> | ||||||
|  |           Fehler!<br/> | ||||||
|  |           <output form="uploadForm" name="upload-result"></output> | ||||||
|  |         </div> | ||||||
|  |     </form> | ||||||
|  |  | ||||||
|  |     <form class="ha-publishform" action="Upload" method="post" enctype="multipart/form-data"> | ||||||
|  |         <label class="filelabel"> | ||||||
|  |           <input class="hidden"  type="file" accept=".xml" name="file" /> | ||||||
|  |           Veröffentlichen | ||||||
|         </label> |         </label> | ||||||
|         <div class="ha-uploadmessage"> |         <div class="ha-uploadmessage"> | ||||||
|  |              | ||||||
|             <output form="uploadForm" name="result"></output> |             <output form="uploadForm" name="result"></output> | ||||||
|         </div> |         </div> | ||||||
|     </form> |     </form> | ||||||
|  |   </div> | ||||||
|  | </div> | ||||||
|  |  | ||||||
|  |  | ||||||
|  | <div class="ha-uploadheader"> | ||||||
|  |   <h1 class="ha-uploadtitle">@Model.ActiveTitle</h1> | ||||||
|  |   @if (Model.Prefix != null) { | ||||||
|  |   <div class="ha-usedfilesheader"> | ||||||
|  |     <div class="ha-usedfilesheaderlist"> | ||||||
|  |       @if(Model.UsedFiles != null && Model.UsedFiles.ContainsKey(Model.Prefix)) { | ||||||
|  |         @foreach (var item in Model.UsedFiles[Model.Prefix]) | ||||||
|  |         { | ||||||
|  |           <div class="ha-usedfilesheaderfile">@item.File.Name</div> | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       else { | ||||||
|  |         <div class="ha-usedfilesnone">Keine Datei geladen</div> | ||||||
|  |       } | ||||||
|  |     @if (Model.AvailableFiles != null)  | ||||||
|  |     { | ||||||
|  |       <div class="ha-availablefilechooser"><div class="ha-plussign"></div><a class="ha-loadotherfilesbtn">Andere Dateien laden...</a></div> | ||||||
|  |     } | ||||||
|  |   </div> | ||||||
|  |   </div> | ||||||
|  |   } | ||||||
|  | </div> | ||||||
|  |  | ||||||
|  | <div class="ha-uploadcontainer"> | ||||||
|  | @if (Model.UsedFiles != null && Model.Prefix != null && Model.UsedFiles.ContainsKey(Model.Prefix)) { | ||||||
|  |   <div class="ha-errorswarnings"> | ||||||
|  |     <div class="ha-criticalerrors"> | ||||||
|  |  | ||||||
|  |     </div> | ||||||
|  |     <div class="ha-warnings"> | ||||||
|  |        | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  |  | ||||||
|  |   <div class="ha-crossfilechecking"> | ||||||
|  |      | ||||||
|  |   </div> | ||||||
|  | } | ||||||
| </div> | </div> | ||||||
|  |  | ||||||
| @section Scripts { | @section Scripts { | ||||||
|  |  | ||||||
|   <script> |   <script> | ||||||
|     "use strict"; |     "use strict"; | ||||||
|  |  | ||||||
|     const dropHandler = function (formelement, ev, dropzone) { |     const dropHandler = function (formelement, ev, dropzone) { | ||||||
|       ev.preventDefault(); |       ev.preventDefault(); | ||||||
|  |  | ||||||
|       if (ev.dataTransfer.items) { |       if (ev.dataTransfer.items) { | ||||||
|         if (ev.dataTransfer.items[0].kind === 'file') { |         if (ev.dataTransfer.items[0].kind === 'file') { | ||||||
|           var file = ev.dataTransfer.items[0].getAsFile(); |           var file = ev.dataTransfer.items[0].getAsFile(); | ||||||
|           AJAXSubmit(formelement, file); |           UPLOADSubmit(formelement, file); | ||||||
|         } else { |         } else { | ||||||
|           var file = ev.dataTransfer.files[0]; |           var file = ev.dataTransfer.files[0]; | ||||||
|           AJAXSubmit(formelement, file); |           UPLOADSubmit(formelement, file); | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| @@ -53,11 +119,12 @@ | |||||||
|       ev.preventDefault(); |       ev.preventDefault(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const AJAXSubmit = async function (oFormElement, file = null) { |     const UPLOADSubmit = async function (oFormElement, file = null) { | ||||||
|       var fd = new FormData(); |       var fd = new FormData(); | ||||||
|       if (file !== null) fd.append("file", file); |       if (file !== null) fd.append("file", file); | ||||||
|       else fd = new FormData(oFormElement); |       else fd = new FormData(oFormElement); | ||||||
|       oFormElement.elements.namedItem("result").value = "Wait"; |       document.getElementById("ha-lds-ellipsis").style.display = "inline-block"; | ||||||
|  |       document.getElementById("ha-uploadmessage").style.opacity = "0"; | ||||||
|       await fetch(oFormElement.action, { |       await fetch(oFormElement.action, { | ||||||
|           method: 'POST', |           method: 'POST', | ||||||
|           headers: { |           headers: { | ||||||
| @@ -68,12 +135,18 @@ | |||||||
|         .then(response => response.json()) |         .then(response => response.json()) | ||||||
|         .then(json => { |         .then(json => { | ||||||
|           if ("Error" in json) { |           if ("Error" in json) { | ||||||
|             oFormElement.elements.namedItem("result").value = json.Error; |             document.getElementById("ha-lds-ellipsis").style.display = "none"; | ||||||
|  |             document.getElementById("ha-uploadmessage").style.opacity = "1"; | ||||||
|  |             oFormElement.elements.namedItem("upload-result").value = json.Error; | ||||||
|           } else { |           } else { | ||||||
|             oFormElement.elements.namedItem("result").value = "Erfolg!"; |             document.getElementById("ha-lds-ellipsis").style.display = "none"; | ||||||
|  |             oFormElement.elements.namedItem("upload-result").value = "Erfolg!"; | ||||||
|  |             if ("Prefix" in json[0]) { | ||||||
|  |               window.location.replace("/Admin/Upload/" + json[0].Prefix); | ||||||
|  |             } | ||||||
|           } |           } | ||||||
|         }) |         }) | ||||||
|         .catch ((e) => console.error('Error:', error)) |         .catch ((e) => console.log('Error:', e)) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     function getCookie(name) { |     function getCookie(name) { | ||||||
| @@ -86,7 +159,7 @@ | |||||||
|       var submitelement = document.getElementById("file"); |       var submitelement = document.getElementById("file"); | ||||||
|       var formelement = document.getElementById("uploadForm"); |       var formelement = document.getElementById("uploadForm"); | ||||||
|       var dropzone = document.getElementById("dropzone"); |       var dropzone = document.getElementById("dropzone"); | ||||||
|       submitelement.addEventListener("change", () => AJAXSubmit(formelement)); |       submitelement.addEventListener("change", () => UPLOADSubmit(formelement)); | ||||||
|       dropzone.addEventListener("drop", (ev) => dropHandler(formelement, ev, dropzone)); |       dropzone.addEventListener("drop", (ev) => dropHandler(formelement, ev, dropzone)); | ||||||
|       dropzone.addEventListener("dragover", (ev) => dragOverHandler(ev, dropzone)); |       dropzone.addEventListener("dragover", (ev) => dragOverHandler(ev, dropzone)); | ||||||
|       dropzone.addEventListener("dragleave", (ev) => dragLeaveHander(ev, dropzone)); |       dropzone.addEventListener("dragleave", (ev) => dragLeaveHander(ev, dropzone)); | ||||||
|   | |||||||
| @@ -33,7 +33,7 @@ | |||||||
|     <div class="pb-24"> |     <div class="pb-24"> | ||||||
|     @await Html.PartialAsync("/Views/Shared/_Menu.cshtml") |     @await Html.PartialAsync("/Views/Shared/_Menu.cshtml") | ||||||
|  |  | ||||||
|     <main role="main" class="pb-3 w-full desktop:max-w-screen-desktop mx-auto"> |     <main role="main" class="pb-3 w-full desktop:max-w-screen-desktop mx-auto h-full"> | ||||||
|         @RenderBody() |         @RenderBody() | ||||||
|     </main> |     </main> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,4 +8,5 @@ public interface IXMLService { | |||||||
|     public Task<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 Task UpdateAvailableFiles(XMLRootDocument doc, string basefilepath, ModelStateDictionary ModelState); | ||||||
|     public Dictionary<string, List<XMLRootDocument>> GetUsed(); |     public Dictionary<string, List<XMLRootDocument>> GetUsed(); | ||||||
|  |     public List<XMLRootDocument>? GetAvailableFiles(string prefix); | ||||||
| } | } | ||||||
| @@ -87,6 +87,13 @@ public class XMLService : IXMLService { | |||||||
|         AutoDetermineUsed(prefix); |         AutoDetermineUsed(prefix); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public List<XMLRootDocument>? GetAvailableFiles(string prefix) { | ||||||
|  |         if (_Roots == null || _availableFiles == null) return null; | ||||||
|  |         if(!_Roots.ContainsKey(prefix) || !_availableFiles.ContainsKey(prefix)) return null; | ||||||
|  |  | ||||||
|  |         return _availableFiles[prefix]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     public async Task UpdateAvailableFiles(XMLRootDocument doc, string basefilepath, ModelStateDictionary ModelState) { |     public async Task UpdateAvailableFiles(XMLRootDocument doc, string basefilepath, ModelStateDictionary ModelState) { | ||||||
|         await _setEnabled(); |         await _setEnabled(); | ||||||
|         if (!UploadEnabled) { |         if (!UploadEnabled) { | ||||||
|   | |||||||
| @@ -1835,6 +1835,243 @@ body { | |||||||
|   line-height: 1.375; |   line-height: 1.375; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /* Classes for Upload View */ | ||||||
|  |  | ||||||
|  | .ha-adminuploadfields { | ||||||
|  |   display: flex; | ||||||
|  |   flex-direction: row; | ||||||
|  |   flex-wrap: wrap; | ||||||
|  |   -moz-column-gap: 1rem; | ||||||
|  |        column-gap: 1rem; | ||||||
|  |   row-gap: 1rem; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-adminuploadfields .ha-uploadfield { | ||||||
|  |   display: block; | ||||||
|  |   max-width: 20rem; | ||||||
|  |   flex-shrink: 0; | ||||||
|  |   flex-grow: 1; | ||||||
|  |   flex-basis: 16rem; | ||||||
|  |   border-radius: 0.25rem; | ||||||
|  |   --tw-bg-opacity: 1; | ||||||
|  |   background-color: rgb(248 250 252 / var(--tw-bg-opacity)); | ||||||
|  |   --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); | ||||||
|  |   --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color); | ||||||
|  |   box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-adminuploadfields .ha-uploadfield:hover { | ||||||
|  |   --tw-brightness: brightness(1.1); | ||||||
|  |   filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-adminuploadfields .ha-uploadfield.active { | ||||||
|  |   --tw-text-opacity: 1 !important; | ||||||
|  |   color: rgb(0 0 0 / var(--tw-text-opacity)) !important; | ||||||
|  |   --tw-shadow-color: #fee2e2; | ||||||
|  |   --tw-shadow: var(--tw-shadow-colored); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-adminuploadfields .ha-uploadfield .ha-uploadfieldname { | ||||||
|  |   padding-left: 0.75rem; | ||||||
|  |   padding-right: 0.75rem; | ||||||
|  |   padding-top: 0.5rem; | ||||||
|  |   padding-bottom: 0.25rem; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-adminuploadfields .ha-uploadusedfiles { | ||||||
|  |   width: auto; | ||||||
|  |   overflow: hidden; | ||||||
|  |   text-overflow: ellipsis; | ||||||
|  |   white-space: nowrap; | ||||||
|  |   border-top-width: 1px; | ||||||
|  |   --tw-border-opacity: 1; | ||||||
|  |   border-color: rgb(203 213 225 / var(--tw-border-opacity)); | ||||||
|  |   background-color: rgb(226 232 240 / var(--tw-bg-opacity)); | ||||||
|  |   --tw-bg-opacity: 0.3; | ||||||
|  |   padding-left: 0.5rem; | ||||||
|  |   padding-right: 0.5rem; | ||||||
|  |   padding-top: 0.125rem; | ||||||
|  |   padding-bottom: 0.125rem; | ||||||
|  |   font-size: 0.875rem; | ||||||
|  |   line-height: 1.25rem; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-adminuploadfields .ha-uploadusedfiles.ha-uploadusedfilesnotfound { | ||||||
|  |   --tw-border-opacity: 1; | ||||||
|  |   border-color: rgb(100 116 139 / var(--tw-border-opacity)); | ||||||
|  |   --tw-bg-opacity: 1; | ||||||
|  |   background-color: rgb(100 116 139 / var(--tw-bg-opacity)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-adminuploadfields .ha-uploadpublishforms { | ||||||
|  |   display: flex; | ||||||
|  |   flex-grow: 1; | ||||||
|  |   flex-direction: row; | ||||||
|  |   -moz-column-gap: 1rem; | ||||||
|  |        column-gap: 1rem; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-adminuploadfields .ha-uploadform { | ||||||
|  |   position: relative; | ||||||
|  |   flex-grow: 1; | ||||||
|  |   border-radius: 0.25rem; | ||||||
|  |   --tw-bg-opacity: 1; | ||||||
|  |   background-color: rgb(248 250 252 / var(--tw-bg-opacity)); | ||||||
|  |   --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); | ||||||
|  |   --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color); | ||||||
|  |   box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-adminuploadfields .ha-uploadform .ha-uploadtext { | ||||||
|  |   text-align: center; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-adminuploadfields .ha-uploadform .ha-lds-ellipsis { | ||||||
|  |   left: 50%; | ||||||
|  |   margin-left: -20px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-adminuploadfields .ha-uploadform .ha-uploadfilelabel { | ||||||
|  |   display: inline-block; | ||||||
|  |   height: 100%; | ||||||
|  |   width: 100%; | ||||||
|  |   cursor: pointer; | ||||||
|  |   padding-left: 0.5rem; | ||||||
|  |   padding-right: 0.5rem; | ||||||
|  |   padding-bottom: 0.25rem; | ||||||
|  |   padding-top: 0.5rem; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-adminuploadfields .ha-uploadform .ha-uploadmessage { | ||||||
|  |   border-radius: 0.125rem; | ||||||
|  |   background-color: rgb(51 65 85 / var(--tw-bg-opacity)); | ||||||
|  |   --tw-bg-opacity: 0.3; | ||||||
|  |   padding-left: 0.25rem; | ||||||
|  |   padding-right: 0.25rem; | ||||||
|  |   font-size: 0.875rem; | ||||||
|  |   line-height: 1.25rem; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-adminuploadfields .ha-publishform { | ||||||
|  |   flex-grow: 1; | ||||||
|  |   border-radius: 0.25rem; | ||||||
|  |   --tw-bg-opacity: 1; | ||||||
|  |   background-color: rgb(248 250 252 / var(--tw-bg-opacity)); | ||||||
|  |   padding-left: 0.75rem; | ||||||
|  |   padding-right: 0.75rem; | ||||||
|  |   padding-top: 0.5rem; | ||||||
|  |   padding-bottom: 0.5rem; | ||||||
|  |   --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); | ||||||
|  |   --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color); | ||||||
|  |   box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-uploadheader { | ||||||
|  |   margin-top: 1rem; | ||||||
|  |   display: flex; | ||||||
|  |   width: 100%; | ||||||
|  |   flex-direction: row; | ||||||
|  |   --tw-bg-opacity: 1; | ||||||
|  |   background-color: rgb(248 250 252 / var(--tw-bg-opacity)); | ||||||
|  |   padding-left: 4rem; | ||||||
|  |   padding-right: 4rem; | ||||||
|  |   padding-top: 3rem; | ||||||
|  |   padding-bottom: 2rem; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-uploadheader h1 { | ||||||
|  |   font-size: 3rem; | ||||||
|  |   line-height: 1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-uploadheader .ha-usedfilesheader { | ||||||
|  |   display: flex; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-uploadheader .ha-usedfilesheaderlist { | ||||||
|  |   margin-left: 1.5rem; | ||||||
|  |   margin-bottom: 0.25rem; | ||||||
|  |   display: flex; | ||||||
|  |   flex-direction: row; | ||||||
|  |   flex-wrap: wrap; | ||||||
|  |   align-content: flex-end; | ||||||
|  |   -moz-column-gap: 0.5rem; | ||||||
|  |        column-gap: 0.5rem; | ||||||
|  |   row-gap: 0.25rem; | ||||||
|  |   align-self: flex-end; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-uploadheader .ha-usedfilesheaderlist .ha-usedfilesheaderfile { | ||||||
|  |   border-radius: 0.25rem; | ||||||
|  |   background-color: rgb(51 65 85 / var(--tw-bg-opacity)); | ||||||
|  |   --tw-bg-opacity: 0.3; | ||||||
|  |   padding-left: 0.5rem; | ||||||
|  |   padding-right: 0.5rem; | ||||||
|  |   font-size: 0.875rem; | ||||||
|  |   line-height: 1.25rem; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-uploadheader .ha-usedfilesheaderlist .ha-availablefilechooser { | ||||||
|  |   border-radius: 0.25rem; | ||||||
|  |   background-color: rgb(51 65 85 / var(--tw-bg-opacity)); | ||||||
|  |   --tw-bg-opacity: 0.5; | ||||||
|  |   padding-right: 0.25rem; | ||||||
|  |   font-size: 0.875rem; | ||||||
|  |   line-height: 1.25rem; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-uploadheader .ha-usedfilesheaderlist .ha-availablefilechooser .ha-loadotherfilesbtn { | ||||||
|  |   display: inline-block; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-uploadcontainer { | ||||||
|  |   display: flex; | ||||||
|  |   height: 100%; | ||||||
|  |   width: 100%; | ||||||
|  |   flex-direction: column; | ||||||
|  |   row-gap: 0.5rem; | ||||||
|  |   --tw-bg-opacity: 1; | ||||||
|  |   background-color: rgb(248 250 252 / var(--tw-bg-opacity)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-uploadcontainer .ha-errorswarnings { | ||||||
|  |   display: flex; | ||||||
|  |   flex-direction: row; | ||||||
|  |   -moz-column-gap: 0.5rem; | ||||||
|  |        column-gap: 0.5rem; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-uploadcontainer .ha-errorswarnings .ha-criticalerrors, .ha-uploadcontainer .ha-errorswarnings .ha-warnings { | ||||||
|  |   min-height: 400px; | ||||||
|  |   min-width: 40%; | ||||||
|  |   flex-shrink: 1; | ||||||
|  |   flex-grow: 1; | ||||||
|  |   flex-basis: 50%; | ||||||
|  |   overflow-x: hidden; | ||||||
|  |   overflow-y: scroll; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-uploadcontainer .ha-errorswarnings .ha-criticalerrors { | ||||||
|  |   --tw-bg-opacity: 1; | ||||||
|  |   background-color: rgb(254 202 202 / var(--tw-bg-opacity)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-uploadcontainer .ha-errorswarnings .ha-warnings { | ||||||
|  |   --tw-bg-opacity: 1; | ||||||
|  |   background-color: rgb(254 215 170 / var(--tw-bg-opacity)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-uploadcontainer .ha-crossfilechecking { | ||||||
|  |   height: 100%; | ||||||
|  |   min-height: 400px; | ||||||
|  |   width: 100%; | ||||||
|  |   flex-shrink: 0; | ||||||
|  |   flex-grow: 1; | ||||||
|  |   --tw-bg-opacity: 1; | ||||||
|  |   background-color: rgb(165 243 252 / var(--tw-bg-opacity)); | ||||||
|  | } | ||||||
|  |  | ||||||
| /* Classes for Letter View */ | /* Classes for Letter View */ | ||||||
|  |  | ||||||
| .ha-letterheader { | .ha-letterheader { | ||||||
| @@ -2801,6 +3038,10 @@ body { | |||||||
|   display: none; |   display: none; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .h-full { | ||||||
|  |   height: 100%; | ||||||
|  | } | ||||||
|  |  | ||||||
| .h-8 { | .h-8 { | ||||||
|   height: 2rem; |   height: 2rem; | ||||||
| } | } | ||||||
| @@ -2817,10 +3058,6 @@ body { | |||||||
|   width: 2rem; |   width: 2rem; | ||||||
| } | } | ||||||
|  |  | ||||||
| .max-w-\[25\%\] { |  | ||||||
|   max-width: 25%; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .shrink-0 { | .shrink-0 { | ||||||
|   flex-shrink: 0; |   flex-shrink: 0; | ||||||
| } | } | ||||||
| @@ -2857,10 +3094,6 @@ body { | |||||||
|   flex-wrap: wrap; |   flex-wrap: wrap; | ||||||
| } | } | ||||||
|  |  | ||||||
| .gap-5 { |  | ||||||
|   gap: 1.25rem; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .overflow-hidden { | .overflow-hidden { | ||||||
|   overflow: hidden; |   overflow: hidden; | ||||||
| } | } | ||||||
| @@ -3063,6 +3296,8 @@ body { | |||||||
|   font-style: normal; |   font-style: normal; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /* Content */ | ||||||
|  |  | ||||||
| .ha-lettertext .ha-marginalbox:before { | .ha-lettertext .ha-marginalbox:before { | ||||||
|   content: ""; |   content: ""; | ||||||
| } | } | ||||||
| @@ -3107,6 +3342,8 @@ body { | |||||||
|   content: ""; |   content: ""; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /* Not implemented in tailwindcss */ | ||||||
|  |  | ||||||
| * { | * { | ||||||
|   -webkit-text-decoration-skip-ink: all; |   -webkit-text-decoration-skip-ink: all; | ||||||
|           text-decoration-skip-ink: all; |           text-decoration-skip-ink: all; | ||||||
| @@ -3117,10 +3354,6 @@ body { | |||||||
|   background-repeat: repeat; |   background-repeat: repeat; | ||||||
| } | } | ||||||
|  |  | ||||||
| /* ul { |  | ||||||
|     list-style-type:circle; |  | ||||||
| } */ |  | ||||||
|  |  | ||||||
| .ha-tradzhtext .ha-marginalbox.ha-collapsed-box .ha-marginallist .ha-marginal, | .ha-tradzhtext .ha-marginalbox.ha-collapsed-box .ha-marginallist .ha-marginal, | ||||||
| .ha-lettertext .ha-marginalbox.ha-collapsed-box .ha-marginallist .ha-marginal { | .ha-lettertext .ha-marginalbox.ha-collapsed-box .ha-marginallist .ha-marginal { | ||||||
|   display: -webkit-inline-box; |   display: -webkit-inline-box; | ||||||
| @@ -3214,6 +3447,8 @@ body { | |||||||
|        break-inside: avoid; |        break-inside: avoid; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /* Symbols, Icons, Drawings, Tooltips */ | ||||||
|  |  | ||||||
| .ha-menu-arrowsymbol::after { | .ha-menu-arrowsymbol::after { | ||||||
|   display: inline-block; |   display: inline-block; | ||||||
|   margin-left: 0.2em; |   margin-left: 0.2em; | ||||||
| @@ -3276,11 +3511,154 @@ body { | |||||||
|   font-weight: 900; |   font-weight: 900; | ||||||
| } | } | ||||||
|  |  | ||||||
| .ha-text { | .ha-uploadheader .ha-usedfilesheaderlist .ha-plussign { | ||||||
|   -webkit-user-select: contain; |   content: "\200E+ "; | ||||||
|      -moz-user-select: contain; |   font-weight: bold; | ||||||
|       -ms-user-select: element; |   display: inline-block; | ||||||
|           user-select: contain; |   background-color: lightslategray; | ||||||
|  |   padding-left: 0.25rem; | ||||||
|  |   padding-right: 0.25rem; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Tooltip text */ | ||||||
|  |  | ||||||
|  | .ha-uploadform .ha-uploadmessage { | ||||||
|  |   visibility: visible; | ||||||
|  |   text-align: center; | ||||||
|  |   padding: 5px 0; | ||||||
|  |   border-radius: 6px; | ||||||
|  |   /* Position the tooltip text - see examples below! */ | ||||||
|  |   position: absolute; | ||||||
|  |   z-index: 1; | ||||||
|  |   margin-top: 0.5rem; | ||||||
|  |   width: 360px; | ||||||
|  |   top: 100%; | ||||||
|  |   left: 50%; | ||||||
|  |   margin-left: -180px; | ||||||
|  |   /* Use half of the width (120/2 = 60), to center the tooltip */ | ||||||
|  |   opacity: 0; | ||||||
|  |   transition: opacity 1s; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-uploadform .ha-uploadmessage::after { | ||||||
|  |   content: " "; | ||||||
|  |   position: absolute; | ||||||
|  |   bottom: 100%; | ||||||
|  |   /* At the top of the tooltip */ | ||||||
|  |   left: 50%; | ||||||
|  |   margin-left: -5px; | ||||||
|  |   border-width: 5px; | ||||||
|  |   border-style: solid; | ||||||
|  |   border-color: transparent transparent grey transparent; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-lds-ellipsis { | ||||||
|  |   display: none; | ||||||
|  |   position: absolute; | ||||||
|  |   bottom: 20px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-lds-ellipsis div { | ||||||
|  |   position: absolute; | ||||||
|  |   width: 7px; | ||||||
|  |   height: 7px; | ||||||
|  |   border-radius: 50%; | ||||||
|  |   background: #000; | ||||||
|  |   -webkit-animation-timing-function: cubic-bezier(0, 1, 1, 0); | ||||||
|  |           animation-timing-function: cubic-bezier(0, 1, 1, 0); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-lds-ellipsis div:nth-child(1) { | ||||||
|  |   left: 6px; | ||||||
|  |   -webkit-animation: ha-lds-ellipsis1 0.6s infinite; | ||||||
|  |           animation: ha-lds-ellipsis1 0.6s infinite; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-lds-ellipsis div:nth-child(2) { | ||||||
|  |   left: 4px; | ||||||
|  |   -webkit-animation: ha-lds-ellipsis2 0.6s infinite; | ||||||
|  |           animation: ha-lds-ellipsis2 0.6s infinite; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-lds-ellipsis div:nth-child(3) { | ||||||
|  |   left: 16px; | ||||||
|  |   -webkit-animation: ha-lds-ellipsis2 0.6s infinite; | ||||||
|  |           animation: ha-lds-ellipsis2 0.6s infinite; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-lds-ellipsis div:nth-child(4) { | ||||||
|  |   left: 30px; | ||||||
|  |   -webkit-animation: ha-lds-ellipsis3 0.6s infinite; | ||||||
|  |           animation: ha-lds-ellipsis3 0.6s infinite; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @-webkit-keyframes ha-lds-ellipsis1 { | ||||||
|  |   0% { | ||||||
|  |     transform: scale(0); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   100% { | ||||||
|  |     transform: scale(1); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @keyframes ha-lds-ellipsis1 { | ||||||
|  |   0% { | ||||||
|  |     transform: scale(0); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   100% { | ||||||
|  |     transform: scale(1); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @-webkit-keyframes ha-lds-ellipsis3 { | ||||||
|  |   0% { | ||||||
|  |     transform: scale(1); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   100% { | ||||||
|  |     transform: scale(0); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @keyframes ha-lds-ellipsis3 { | ||||||
|  |   0% { | ||||||
|  |     transform: scale(1); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   100% { | ||||||
|  |     transform: scale(0); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @-webkit-keyframes ha-lds-ellipsis2 { | ||||||
|  |   0% { | ||||||
|  |     transform: translate(0, 0); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   100% { | ||||||
|  |     transform: translate(16px, 0); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @keyframes ha-lds-ellipsis2 { | ||||||
|  |   0% { | ||||||
|  |     transform: translate(0, 0); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   100% { | ||||||
|  |     transform: translate(16px, 0); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-cross::before, | ||||||
|  | .ha-cross::after { | ||||||
|  |   content: ""; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-cross::before { | ||||||
|  |   transform: skewY(-27deg); | ||||||
| } | } | ||||||
|  |  | ||||||
| /* Classes for tables */ | /* Classes for tables */ | ||||||
| @@ -3819,15 +4197,6 @@ body { | |||||||
|   min-width: 8.333%; |   min-width: 8.333%; | ||||||
| } | } | ||||||
|  |  | ||||||
| .ha-cross::before, |  | ||||||
| .ha-cross::after { |  | ||||||
|   content: ""; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .ha-cross::before { |  | ||||||
|   transform: skewY(-27deg); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .hover\:text-black:hover { | .hover\:text-black:hover { | ||||||
|   --tw-text-opacity: 1; |   --tw-text-opacity: 1; | ||||||
|   color: rgb(0 0 0 / var(--tw-text-opacity)); |   color: rgb(0 0 0 / var(--tw-text-opacity)); | ||||||
|   | |||||||
| @@ -664,6 +664,116 @@ | |||||||
|     @apply flex leading-snug |     @apply flex leading-snug | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   /* Classes for Upload View */ | ||||||
|  |   .ha-adminuploadfields { | ||||||
|  |     @apply flex flex-row flex-wrap gap-x-4 gap-y-4 | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .ha-adminuploadfields .ha-uploadfield { | ||||||
|  |     @apply block shrink-0 grow bg-slate-50 rounded shadow basis-64 max-w-xs | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .ha-adminuploadfields .ha-uploadfield:hover { | ||||||
|  |     @apply brightness-110 | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .ha-adminuploadfields .ha-uploadfield.active { | ||||||
|  |     @apply !text-black shadow-red-100 | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .ha-adminuploadfields .ha-uploadfield .ha-uploadfieldname { | ||||||
|  |     @apply px-3 pt-2 pb-1 | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .ha-adminuploadfields .ha-uploadusedfiles { | ||||||
|  |     @apply text-sm whitespace-nowrap overflow-hidden text-ellipsis w-auto bg-slate-200 border-t border-slate-300 bg-opacity-30 px-2 py-0.5  | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .ha-adminuploadfields .ha-uploadusedfiles.ha-uploadusedfilesnotfound { | ||||||
|  |     @apply bg-slate-500 border-slate-500 | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .ha-adminuploadfields .ha-uploadpublishforms { | ||||||
|  |     @apply flex flex-row gap-x-4 grow | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .ha-adminuploadfields .ha-uploadform { | ||||||
|  |     @apply bg-slate-50 rounded shadow grow relative | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .ha-adminuploadfields .ha-uploadform .ha-uploadtext { | ||||||
|  |     @apply text-center | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .ha-adminuploadfields .ha-uploadform .ha-lds-ellipsis { | ||||||
|  |     @apply left-1/2 -ml-[20px] | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .ha-adminuploadfields .ha-uploadform .ha-uploadfilelabel { | ||||||
|  |     @apply inline-block px-2 py-1 pt-2 cursor-pointer w-full h-full | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .ha-adminuploadfields .ha-uploadform .ha-uploadmessage { | ||||||
|  |     @apply text-sm bg-slate-700 bg-opacity-30 px-1 rounded-sm | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .ha-adminuploadfields .ha-publishform { | ||||||
|  |     @apply bg-slate-50 rounded shadow grow px-3 py-2  | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .ha-uploadheader { | ||||||
|  |     @apply bg-slate-50 w-full mt-4 px-16 pt-12 pb-8 flex flex-row | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .ha-uploadheader h1 { | ||||||
|  |     @apply text-5xl | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .ha-uploadheader .ha-usedfilesheader { | ||||||
|  |     @apply flex | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .ha-uploadheader .ha-usedfilesheaderlist { | ||||||
|  |     @apply flex flex-row flex-wrap ml-6 gap-x-2 gap-y-1 self-end content-end mb-1 | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .ha-uploadheader .ha-usedfilesheaderlist .ha-usedfilesheaderfile { | ||||||
|  |     @apply text-sm px-2 bg-slate-700 bg-opacity-30 rounded | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .ha-uploadheader .ha-usedfilesheaderlist .ha-availablefilechooser { | ||||||
|  |     @apply text-sm pr-1 bg-slate-700 bg-opacity-50 rounded | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .ha-uploadheader .ha-usedfilesheaderlist .ha-availablefilechooser .ha-loadotherfilesbtn { | ||||||
|  |     @apply inline-block | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .ha-uploadcontainer { | ||||||
|  |     @apply w-full bg-slate-50 flex flex-col gap-y-2 h-full | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .ha-uploadcontainer .ha-errorswarnings { | ||||||
|  |     @apply flex flex-row gap-x-2 | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .ha-uploadcontainer .ha-errorswarnings .ha-criticalerrors, | ||||||
|  |   .ha-uploadcontainer .ha-errorswarnings .ha-warnings { | ||||||
|  |     @apply basis-1/2 min-w-[40%] min-h-[400px] overflow-x-hidden overflow-y-scroll grow shrink | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .ha-uploadcontainer .ha-errorswarnings .ha-criticalerrors { | ||||||
|  |     @apply bg-red-200 | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .ha-uploadcontainer .ha-errorswarnings .ha-warnings { | ||||||
|  |     @apply bg-orange-200 | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .ha-uploadcontainer .ha-crossfilechecking { | ||||||
|  |     @apply w-full bg-cyan-200 grow shrink-0 h-full min-h-[400px] | ||||||
|  |   } | ||||||
|  |  | ||||||
|   /* Classes for Letter View */ |   /* Classes for Letter View */ | ||||||
|  |  | ||||||
|   .ha-letterheader { |   .ha-letterheader { | ||||||
| @@ -1137,6 +1247,7 @@ | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /* Content */ | ||||||
| .ha-lettertext .ha-marginalbox:before { | .ha-lettertext .ha-marginalbox:before { | ||||||
|   content: ""; |   content: ""; | ||||||
| } | } | ||||||
| @@ -1181,6 +1292,7 @@ | |||||||
|   content: ""; |   content: ""; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /* Not implemented in tailwindcss */ | ||||||
| * { | * { | ||||||
|   text-decoration-skip-ink: all; |   text-decoration-skip-ink: all; | ||||||
| } | } | ||||||
| @@ -1190,10 +1302,6 @@ body { | |||||||
|   background-repeat: repeat; |   background-repeat: repeat; | ||||||
| } | } | ||||||
|  |  | ||||||
| /* ul { |  | ||||||
|     list-style-type:circle; |  | ||||||
| } */ |  | ||||||
|  |  | ||||||
| .ha-tradzhtext .ha-marginalbox.ha-collapsed-box .ha-marginallist .ha-marginal, | .ha-tradzhtext .ha-marginalbox.ha-collapsed-box .ha-marginallist .ha-marginal, | ||||||
| .ha-lettertext .ha-marginalbox.ha-collapsed-box .ha-marginallist .ha-marginal { | .ha-lettertext .ha-marginalbox.ha-collapsed-box .ha-marginallist .ha-marginal { | ||||||
|     display: -webkit-inline-box; |     display: -webkit-inline-box; | ||||||
| @@ -1282,6 +1390,7 @@ body { | |||||||
|   break-inside: avoid; |   break-inside: avoid; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /* Symbols, Icons, Drawings, Tooltips */ | ||||||
| .ha-menu-arrowsymbol::after { | .ha-menu-arrowsymbol::after { | ||||||
|   display: inline-block; |   display: inline-block; | ||||||
|   margin-left: 0.2em; |   margin-left: 0.2em; | ||||||
| @@ -1344,9 +1453,121 @@ body { | |||||||
|   font-weight: 900; |   font-weight: 900; | ||||||
| } | } | ||||||
|  |  | ||||||
| .ha-text { | .ha-uploadheader .ha-usedfilesheaderlist .ha-plussign { | ||||||
|     user-select: contain; |   content: "\200E+ "; | ||||||
|  |   font-weight: bold; | ||||||
|  |   display: inline-block; | ||||||
|  |   background-color: lightslategray; | ||||||
|  |   padding-left: 0.25rem; | ||||||
|  |   padding-right: 0.25rem; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /* Tooltip text */ | ||||||
|  | .ha-uploadform .ha-uploadmessage { | ||||||
|  |   visibility: visible; | ||||||
|  |   text-align: center; | ||||||
|  |   padding: 5px 0; | ||||||
|  |   border-radius: 6px; | ||||||
|  |   | ||||||
|  |   /* Position the tooltip text - see examples below! */ | ||||||
|  |   position: absolute; | ||||||
|  |   z-index: 1; | ||||||
|  |  | ||||||
|  |   margin-top: 0.5rem; | ||||||
|  |   width: 360px; | ||||||
|  |   top: 100%; | ||||||
|  |   left: 50%; | ||||||
|  |   margin-left: -180px; /* Use half of the width (120/2 = 60), to center the tooltip */ | ||||||
|  |  | ||||||
|  |   opacity: 0; | ||||||
|  |   transition: opacity 1s; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-uploadform .ha-uploadmessage::after { | ||||||
|  |   content: " "; | ||||||
|  |   position: absolute; | ||||||
|  |   bottom: 100%;  /* At the top of the tooltip */ | ||||||
|  |   left: 50%; | ||||||
|  |   margin-left: -5px; | ||||||
|  |   border-width: 5px; | ||||||
|  |   border-style: solid; | ||||||
|  |   border-color: transparent transparent grey transparent; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | .ha-lds-ellipsis { | ||||||
|  |   display: none; | ||||||
|  |   position: absolute; | ||||||
|  |   bottom: 20px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-lds-ellipsis div { | ||||||
|  |   position: absolute; | ||||||
|  |   width: 7px; | ||||||
|  |   height: 7px; | ||||||
|  |   border-radius: 50%; | ||||||
|  |   background: #000; | ||||||
|  |   animation-timing-function: cubic-bezier(0, 1, 1, 0); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-lds-ellipsis div:nth-child(1) { | ||||||
|  |   left: 6px; | ||||||
|  |   animation: ha-lds-ellipsis1 0.6s infinite; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-lds-ellipsis div:nth-child(2) { | ||||||
|  |   left: 4px; | ||||||
|  |   animation: ha-lds-ellipsis2 0.6s infinite; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-lds-ellipsis div:nth-child(3) { | ||||||
|  |   left: 16px; | ||||||
|  |   animation: ha-lds-ellipsis2 0.6s infinite; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-lds-ellipsis div:nth-child(4) { | ||||||
|  |   left: 30px; | ||||||
|  |   animation: ha-lds-ellipsis3 0.6s infinite; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @keyframes ha-lds-ellipsis1 { | ||||||
|  |   0% { | ||||||
|  |     transform: scale(0); | ||||||
|  |   } | ||||||
|  |   100% { | ||||||
|  |     transform: scale(1); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @keyframes ha-lds-ellipsis3 { | ||||||
|  |   0% { | ||||||
|  |     transform: scale(1); | ||||||
|  |   } | ||||||
|  |   100% { | ||||||
|  |     transform: scale(0); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @keyframes ha-lds-ellipsis2 { | ||||||
|  |   0% { | ||||||
|  |     transform: translate(0, 0); | ||||||
|  |   } | ||||||
|  |   100% { | ||||||
|  |     transform: translate(16px, 0); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-cross::before, | ||||||
|  | .ha-cross::after { | ||||||
|  |   content: ""; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .ha-cross::before { | ||||||
|  |   -webkit-transform: skewY(-27deg); | ||||||
|  |   transform: skewY(-27deg); | ||||||
|  | } | ||||||
|  |  | ||||||
| /* Classes for tables */ | /* Classes for tables */ | ||||||
| .ha-table { | .ha-table { | ||||||
|   overflow: hidden; |   overflow: hidden; | ||||||
| @@ -1881,13 +2102,3 @@ body { | |||||||
|   left: 91.666%; |   left: 91.666%; | ||||||
|   min-width: 8.333%; |   min-width: 8.333%; | ||||||
| } | } | ||||||
|  |  | ||||||
| .ha-cross::before, |  | ||||||
| .ha-cross::after { |  | ||||||
|   content: ""; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .ha-cross::before { |  | ||||||
|   -webkit-transform: skewY(-27deg); |  | ||||||
|   transform: skewY(-27deg); |  | ||||||
| } |  | ||||||
| @@ -259,6 +259,8 @@ window.addEventListener("load", function () { | |||||||
|     markactive_startswith(document.getElementById("ha-topnav")); |     markactive_startswith(document.getElementById("ha-topnav")); | ||||||
|   if (document.getElementById("ha-register-nav") != null) |   if (document.getElementById("ha-register-nav") != null) | ||||||
|     markactive_exact(document.getElementById("ha-register-nav")); |     markactive_exact(document.getElementById("ha-register-nav")); | ||||||
|  |   if (this.document.getElementById("ha-adminuploadfields") != null) | ||||||
|  |     markactive_startswith(document.getElementById("ha-adminuploadfields")); | ||||||
|  |  | ||||||
|   // Letter / Register View: Collapse all unfit boxes + resize observer |   // Letter / Register View: Collapse all unfit boxes + resize observer | ||||||
|   collapseboxes(); |   collapseboxes(); | ||||||
|   | |||||||
| @@ -1,2 +1,2 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?> | ||||||
| <opus></opus> | <opus><lol></lol>></opus> | ||||||
		Reference in New Issue
	
	Block a user
	 schnulller
					schnulller