diff --git a/HaWeb/Controllers/AdminController.cs b/HaWeb/Controllers/AdminController.cs new file mode 100644 index 0000000..6ed805b --- /dev/null +++ b/HaWeb/Controllers/AdminController.cs @@ -0,0 +1,26 @@ +namespace HaWeb.Controllers; +using Microsoft.AspNetCore.Mvc; +using HaDocument.Interfaces; +using HaXMLReader.Interfaces; +using Microsoft.FeatureManagement.Mvc; + +public class AdminController : Controller +{ + // DI + private ILibrary _lib; + private IReaderService _readerService; + + public AdminController(ILibrary lib, IReaderService readerService) + { + _lib = lib; + _readerService = readerService; + } + + + [Route("Admin")] + [FeatureGate(Features.AdminService)] + public IActionResult Index() + { + return Redirect("/Admin/Upload"); + } +} \ No newline at end of file diff --git a/HaWeb/Controllers/UpdateController.cs b/HaWeb/Controllers/UpdateController.cs new file mode 100644 index 0000000..1c7b831 --- /dev/null +++ b/HaWeb/Controllers/UpdateController.cs @@ -0,0 +1,26 @@ +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"); + } +} \ No newline at end of file diff --git a/HaWeb/Controllers/UploadController.cs b/HaWeb/Controllers/UploadController.cs new file mode 100644 index 0000000..1a4efb6 --- /dev/null +++ b/HaWeb/Controllers/UploadController.cs @@ -0,0 +1,115 @@ +namespace HaWeb.Controllers; +using Microsoft.AspNetCore.Mvc; +using HaDocument.Interfaces; +using HaXMLReader.Interfaces; +using Microsoft.FeatureManagement.Mvc; +using System.IO; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.WebUtilities; +using Microsoft.Extensions.Configuration; +using Microsoft.Net.Http.Headers; +using HaWeb.Filters; +using HaWeb.FileHelpers; + +public class UploadController : Controller +{ + // DI + private ILibrary _lib; + private IReaderService _readerService; + private readonly long _fileSizeLimit; + private readonly string _targetFilePath; + + // Options + private static readonly string[] _permittedExtensions = { ".xml" }; + private static readonly FormOptions _defaultFormOptions = new FormOptions(); + + + public UploadController(ILibrary lib, IReaderService readerService, IConfiguration config) + { + _lib = lib; + _readerService = readerService; + _fileSizeLimit = config.GetValue("FileSizeLimit"); + _targetFilePath = config.GetValue("StoredFilesPath"); + } + + [HttpGet] + [Route("Admin/Upload")] + [FeatureGate(Features.UploadService)] + [GenerateAntiforgeryTokenCookie] + public IActionResult Index() + { + return View("../Admin/Upload/Index"); + } + + [HttpPost] + [Route("Admin/Upload")] + [DisableFormValueModelBinding] + [ValidateAntiForgeryToken] + public async Task Post() { + +//// 1. Stage: Check Request and File on a byte-level + // Checks the COntent-Type Field (must be multipart + Boundary) + if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType)) + { + ModelState.AddModelError("File", $"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); + var section = await reader.ReadNextSectionAsync(); + + 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("File", $"Wrong Content-Dispostion Headers in Multipart Document"); + return BadRequest(ModelState); + } + + // 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) + return BadRequest(ModelState); + +//// 2. Stage: Valid XML checking + +//// 3. Stage: Is it a Hamann-Document? What kind? + +//// 4. Stage: Get Filename for the stageing area + +//// 5. Stage: Saving the File + // // Encode Filename for display + // var trustedFileNameForDisplay = WebUtility.HtmlEncode(contentDisposition.FileName.Value); + + + // // TODO: generatre storage filename + // var trustedFileNameForFileStorage = Path.GetRandomFileName(); + // using (var targetStream = System.IO.File.Create(Path.Combine(_targetFilePath, trustedFileNameForFileStorage))) + // await targetStream.WriteAsync(streamedFileContent); + } + + // Drain any remaining section body that hasn't been consumed and + // read the headers for the next section. + section = await reader.ReadNextSectionAsync(); + } + +//// Success! Return Last Created File View + return Created(nameof(UploadController), null); + } +} \ No newline at end of file diff --git a/HaWeb/FileHelpers/MultipartRequestHelper.cs b/HaWeb/FileHelpers/MultipartRequestHelper.cs new file mode 100644 index 0000000..d9da021 --- /dev/null +++ b/HaWeb/FileHelpers/MultipartRequestHelper.cs @@ -0,0 +1,43 @@ +namespace HaWeb.FileHelpers; +using System; +using System.IO; +using Microsoft.Net.Http.Headers; + +public static class MultipartRequestHelper +{ + // Content-Type: multipart/form-data; boundary="----WebKitFormBoundarymx2fSWqWSd0OxQqq" + // The spec at https://tools.ietf.org/html/rfc2046#section-5.1 states that 70 characters is a reasonable limit. + public static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit) + { + var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary).Value; + + if (string.IsNullOrWhiteSpace(boundary)) + throw new InvalidDataException("Missing content-type boundary."); + + if (boundary.Length > lengthLimit) + throw new InvalidDataException($"Multipart boundary length limit {lengthLimit} exceeded."); + + return boundary; + } + + public static bool IsMultipartContentType(string? contentType) + => !string.IsNullOrEmpty(contentType) && contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0; + + public static bool HasFormDataContentDisposition(ContentDispositionHeaderValue contentDisposition) + { + // Content-Disposition: form-data; name="key"; + return contentDisposition != null + && contentDisposition.DispositionType.Equals("form-data") + && string.IsNullOrEmpty(contentDisposition.FileName.Value) + && string.IsNullOrEmpty(contentDisposition.FileNameStar.Value); + } + + public static bool HasFileContentDisposition(ContentDispositionHeaderValue? contentDisposition) + { + return contentDisposition != null + && contentDisposition.DispositionType.Equals("form-data") + && (!string.IsNullOrEmpty(contentDisposition.FileName.Value) + || !string.IsNullOrEmpty(contentDisposition.FileNameStar.Value)); + } +} + diff --git a/HaWeb/FileHelpers/XMLFileHelpers.cs b/HaWeb/FileHelpers/XMLFileHelpers.cs new file mode 100644 index 0000000..cdd914b --- /dev/null +++ b/HaWeb/FileHelpers/XMLFileHelpers.cs @@ -0,0 +1,200 @@ +namespace HaWeb.FileHelpers; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.IO; +using System.Linq; +using System.Net; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.WebUtilities; +using Microsoft.Net.Http.Headers; + +public static class XMLFileHelpers +{ + // File Signatures Database (https://www.filesignatures.net/) + private static readonly Dictionary> _fileSignature = new Dictionary> + { + { ".gif", new List { new byte[] { 0x47, 0x49, 0x46, 0x38 } } }, + { ".png", new List { new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A } } }, + { ".jpeg", new List + { + new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 }, + new byte[] { 0xFF, 0xD8, 0xFF, 0xE2 }, + new byte[] { 0xFF, 0xD8, 0xFF, 0xE3 }, + } + }, + { ".jpg", new List + { + new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 }, + new byte[] { 0xFF, 0xD8, 0xFF, 0xE1 }, + new byte[] { 0xFF, 0xD8, 0xFF, 0xE8 }, + } + }, + { ".zip", new List + { + new byte[] { 0x50, 0x4B, 0x03, 0x04 }, + new byte[] { 0x50, 0x4B, 0x4C, 0x49, 0x54, 0x45 }, + new byte[] { 0x50, 0x4B, 0x53, 0x70, 0x58 }, + new byte[] { 0x50, 0x4B, 0x05, 0x06 }, + new byte[] { 0x50, 0x4B, 0x07, 0x08 }, + new byte[] { 0x57, 0x69, 0x6E, 0x5A, 0x69, 0x70 }, + } + }, + { ".xml", new List + { + new byte[] { 0x3C, 0x3F, 0x78, 0x6D, 0x6C, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x3D, 0x22, 0x31, 0x2E, 0x30, 0x22, 0x3F, 0x3E }, + new byte[] { 0xEF, 0xBB, 0xBF, 0x3C, 0x3F, 0x78, 0x6D, 0x6C, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x3D, 0x22, 0x31, 0x2E, 0x30, 0x22, 0x3F, 0x3E }, + new byte[] { 0xEF, 0xBB, 0xBF, 0x3C, 0x3F, 0x78, 0x6D, 0x6C, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x3D, 0x22, 0x31, 0x2E, 0x30, 0x22, 0x20, 0x65, 0x6E, 0x63, 0x6F, 0x64, 0x69, 0x6E, 0x67, 0x3D, 0x22, 0x75, 0x74, 0x66, 0x2D, 0x38, 0x22, 0x3F, 0x3E }, + new byte[] { 0x3C, 0x3F, 0x78, 0x6D, 0x6C, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x3D, 0x22, 0x31, 0x2E, 0x30, 0x22, 0x20, 0x65, 0x6E, 0x63, 0x6F, 0x64, 0x69, 0x6E, 0x67, 0x3D, 0x22, 0x75, 0x74, 0x66, 0x2D, 0x38, 0x22, 0x3F, 0x3E } + } + } + }; + + // Unused as of rn, used to take a file and do the same sanity checks as below + // public static async Task ProcessFormFile(IFormFile formFile, ModelStateDictionary modelState, string[] permittedExtensions, long sizeLimit) + // { + // var fieldDisplayName = string.Empty; + + // // Use reflection to obtain the display name for the model + // // property associated with this IFormFile. If a display + // // name isn't found, error messages simply won't show + // // a display name. + // MemberInfo property = + // typeof(T).GetProperty( + // formFile.Name.Substring(formFile.Name.IndexOf(".", + // StringComparison.Ordinal) + 1)); + + // if (property != null) + // { + // if (property.GetCustomAttribute(typeof(DisplayAttribute)) is + // DisplayAttribute displayAttribute) + // { + // fieldDisplayName = $"{displayAttribute.Name} "; + // } + // } + + // // Don't trust the file name sent by the client. To display + // // the file name, HTML-encode the value. + // var trustedFileNameForDisplay = WebUtility.HtmlEncode( + // formFile.FileName); + + // // Check the file length. This check doesn't catch files that only have + // // a BOM as their content. + // if (formFile.Length == 0) + // { + // modelState.AddModelError(formFile.Name, + // $"{fieldDisplayName}({trustedFileNameForDisplay}) is empty."); + + // return Array.Empty(); + // } + + // if (formFile.Length > sizeLimit) + // { + // var megabyteSizeLimit = sizeLimit / 1048576; + // modelState.AddModelError(formFile.Name, + // $"{fieldDisplayName}({trustedFileNameForDisplay}) exceeds " + + // $"{megabyteSizeLimit:N1} MB."); + + // return Array.Empty(); + // } + + // try + // { + // using (var memoryStream = new MemoryStream()) + // { + // await formFile.CopyToAsync(memoryStream); + + // // Check the content length in case the file's only + // // content was a BOM and the content is actually + // // empty after removing the BOM. + // if (memoryStream.Length == 0) + // { + // modelState.AddModelError(formFile.Name, + // $"{fieldDisplayName}({trustedFileNameForDisplay}) is empty."); + // } + + // if (!IsValidFileExtensionAndSignature( + // formFile.FileName, memoryStream, permittedExtensions)) + // { + // modelState.AddModelError(formFile.Name, + // $"{fieldDisplayName}({trustedFileNameForDisplay}) file " + + // "type isn't permitted or the file's signature " + + // "doesn't match the file's extension."); + // } + // else + // { + // return memoryStream.ToArray(); + // } + // } + // } + // catch (Exception ex) + // { + // modelState.AddModelError(formFile.Name, + // $"{fieldDisplayName}({trustedFileNameForDisplay}) upload failed. " + + // $"Please contact the Help Desk for support. Error: {ex.HResult}"); + // } + + // return Array.Empty(); + // } + + public static async Task ProcessStreamedFile( + MultipartSection section, ContentDispositionHeaderValue contentDisposition, + ModelStateDictionary modelState, string[] permittedExtensions, long sizeLimit) + { + try + { + using (var memoryStream = new MemoryStream()) + { + await section.Body.CopyToAsync(memoryStream); + + // Check if the file is empty or exceeds the size limit. + if (memoryStream.Length == 0) + modelState.AddModelError("File", "The file is empty."); + else if (memoryStream.Length > sizeLimit) + { + var megabyteSizeLimit = sizeLimit / 1048576; + modelState.AddModelError("File", $"The file exceeds {megabyteSizeLimit:N1} MB."); + } + + // Check file extension and first bytes + else if (!IsValidFileExtensionAndSignature(contentDisposition.FileName.Value, memoryStream, permittedExtensions)) + modelState.AddModelError("File", "The file must be of the following specs:
" + + "1. The file must hava a .xml File-Extension
" + + "2. To make sure the file isn't executable the file must start with: or "); + + // Return the File as a byte array + else return memoryStream.ToArray(); + } + } + catch (Exception ex) + { + modelState.AddModelError("File", $"The upload failed. Error: {ex.HResult}"); + } + + return Array.Empty(); + } + + private static bool IsValidFileExtensionAndSignature(string fileName, Stream data, string[] permittedExtensions) + { + if (string.IsNullOrEmpty(fileName) || data == null || data.Length == 0) + return false; + + var ext = Path.GetExtension(fileName).ToLowerInvariant(); + + if (string.IsNullOrEmpty(ext) || !permittedExtensions.Contains(ext)) + return false; + + data.Position = 0; + + using (var reader = new BinaryReader(data)) + { + var signatures = _fileSignature[ext]; + var headerBytes = reader.ReadBytes(signatures.Max(m => m.Length)); + return signatures.Any(signature => + headerBytes.Take(signature.Length).SequenceEqual(signature)); + } + } +} \ No newline at end of file diff --git a/HaWeb/Filters/Antiforgery.cs b/HaWeb/Filters/Antiforgery.cs new file mode 100644 index 0000000..b6c1662 --- /dev/null +++ b/HaWeb/Filters/Antiforgery.cs @@ -0,0 +1,27 @@ +namespace HaWeb.Filters; +using Microsoft.AspNetCore.Antiforgery; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.DependencyInjection; + +public class GenerateAntiforgeryTokenCookieAttribute : ResultFilterAttribute +{ + public override void OnResultExecuting(ResultExecutingContext context) + { + var antiforgery = context.HttpContext.RequestServices.GetService(); + + // Send the request token as a JavaScript-readable cookie + var tokens = antiforgery.GetAndStoreTokens(context.HttpContext); + + context.HttpContext.Response.Cookies.Append( + "RequestVerificationToken", + tokens.RequestToken, + new CookieOptions() { HttpOnly = false }); + } + + public override void OnResultExecuted(ResultExecutedContext context) + { + } +} + + diff --git a/HaWeb/Filters/ModelBinding.cs b/HaWeb/Filters/ModelBinding.cs new file mode 100644 index 0000000..37e5cf7 --- /dev/null +++ b/HaWeb/Filters/ModelBinding.cs @@ -0,0 +1,20 @@ +namespace HaWeb.Filters; +using System; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] +public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter +{ + public void OnResourceExecuting(ResourceExecutingContext context) + { + var factories = context.ValueProviderFactories; + factories.RemoveType(); + factories.RemoveType(); + factories.RemoveType(); + } + + public void OnResourceExecuted(ResourceExecutedContext context) + { + } +} diff --git a/HaWeb/HaWeb.csproj b/HaWeb/HaWeb.csproj index ed13233..fd3f5d4 100644 --- a/HaWeb/HaWeb.csproj +++ b/HaWeb/HaWeb.csproj @@ -25,6 +25,7 @@ + diff --git a/HaWeb/Program.cs b/HaWeb/Program.cs index 1f80b13..a611319 100644 --- a/HaWeb/Program.cs +++ b/HaWeb/Program.cs @@ -1,6 +1,7 @@ using HaXMLReader; using HaXMLReader.Interfaces; using HaDocument.Interfaces; +using Microsoft.FeatureManagement; var builder = WebApplication.CreateBuilder(args); @@ -9,6 +10,7 @@ builder.Services.AddControllersWithViews(); builder.Services.AddHttpContextAccessor(); builder.Services.AddSingleton(x => HaDocument.Document.Create(new Options())); builder.Services.AddTransient(); +builder.Services.AddFeatureManagement(); // builder.Services.AddWebOptimizer(); @@ -26,10 +28,11 @@ if (!app.Environment.IsDevelopment()) // app.UseWebOptimizer(); app.UseAuthorization(); app.UseStaticFiles(); -app.UseRouting(); app.MapControllers(); app.Run(); + + class Options : IHaDocumentOptions { public string HamannXMLFilePath { get; set; } = HaWeb.Settings.General.XMLFILEPATH; public string[] AvailableVolumes { get; set; } = HaWeb.Settings.General.AVAILABLEVOLUMES; diff --git a/HaWeb/README.md b/HaWeb/README.md index 264f2ab..a77fc49 100644 --- a/HaWeb/README.md +++ b/HaWeb/README.md @@ -35,8 +35,10 @@ Briefe beim Namen - GND Normdaten der Namen - -TODO tabellen ok, ausser 939: dort sind htabs geschachtelt +TODO 1127 zu breit +TODO tabellen ok, ausser 939, 806 falsch geschachtelt: dort sind htabs geschachtelt +TODO 659 align center und align-right ueberschneidugn +TODO Kommentare und min-size von ha-lettertetx TODO Word-wrap before align, tabs -TODO blinken before js - +TODO pills are not mobile friendly (hover / click) +TODO Evtl alignment von center / right an der letzten oder nächsten zeile \ No newline at end of file diff --git a/HaWeb/Settings/Features.cs b/HaWeb/Settings/Features.cs new file mode 100644 index 0000000..fc8defa --- /dev/null +++ b/HaWeb/Settings/Features.cs @@ -0,0 +1,8 @@ +namespace HaWeb; + +public static class Features +{ + public const string AdminService = "AdminService"; + public const string UploadService = "UploadService"; + public const string UpdateService = "UpdateService"; +} diff --git a/HaWeb/Views/Admin/Index.cshtml b/HaWeb/Views/Admin/Index.cshtml new file mode 100644 index 0000000..e69de29 diff --git a/HaWeb/Views/Admin/Upload/Index.cshtml b/HaWeb/Views/Admin/Upload/Index.cshtml new file mode 100644 index 0000000..854e771 --- /dev/null +++ b/HaWeb/Views/Admin/Upload/Index.cshtml @@ -0,0 +1,57 @@ +Hello from Upload Index! + +
+
+
+ +
+
+ +
+
+ + + +
+ +
+
+ +@section Scripts { + + + +} diff --git a/HaWeb/Views/Shared/_Layout.cshtml b/HaWeb/Views/Shared/_Layout.cshtml index c42558d..c9ed0d8 100644 --- a/HaWeb/Views/Shared/_Layout.cshtml +++ b/HaWeb/Views/Shared/_Layout.cshtml @@ -45,6 +45,8 @@ @await Html.PartialAsync("/Views/Shared/_Javascript.cshtml") + + @RenderSection("Scripts", required: false) diff --git a/HaWeb/appsettings.json b/HaWeb/appsettings.json index 4d56694..3bf08ce 100644 --- a/HaWeb/appsettings.json +++ b/HaWeb/appsettings.json @@ -5,5 +5,12 @@ "Microsoft.AspNetCore": "Warning" } }, - "AllowedHosts": "*" + "FeatureManagement": { + "AdminService": true, + "UploadService": true, + "UpdateService": false + }, + "AllowedHosts": "*", + "StoredFilesPath": "/home/simon/Downloads/", + "FileSizeLimit": 209799152 } diff --git a/HaWeb/wwwroot/css/output.css b/HaWeb/wwwroot/css/output.css index 8325b59..7b2ea91 100644 --- a/HaWeb/wwwroot/css/output.css +++ b/HaWeb/wwwroot/css/output.css @@ -682,15 +682,7 @@ body { color: rgb(229 231 235 / var(--tw-text-opacity)) !important; } -.ha-register .ha-neuzeit .ha-register-body .ha-commenthead .ha-letlinks, .ha-register .ha-forschung .ha-register-body .ha-commenthead .ha-letlinks { - border-left-width: 2px; - --tw-border-opacity: 1; - border-color: rgb(203 213 225 / var(--tw-border-opacity)); -} - .dark .ha-register .ha-neuzeit .ha-register-body .ha-commenthead .ha-letlinks, .dark .ha-register .ha-forschung .ha-register-body .ha-commenthead .ha-letlinks { - --tw-border-opacity: 1; - border-color: rgb(100 116 139 / var(--tw-border-opacity)); --tw-bg-opacity: 1; background-color: rgb(15 23 42 / var(--tw-bg-opacity)); --tw-text-opacity: 1; @@ -704,12 +696,28 @@ body { } } +.ha-register .ha-neuzeit .ha-register-body .ha-commenthead .ha-letlinks::before, .ha-register .ha-forschung .ha-register-body .ha-commenthead .ha-letlinks::before { + --tw-bg-opacity: 1; + background-color: rgb(148 163 184 / var(--tw-bg-opacity)); +} + +.dark .ha-register .ha-neuzeit .ha-register-body .ha-commenthead .ha-letlinks::before, .dark .ha-register .ha-forschung .ha-register-body .ha-commenthead .ha-letlinks::before { + --tw-bg-opacity: 1; + background-color: rgb(100 116 139 / var(--tw-bg-opacity)); +} + .ha-register .ha-register-body .ha-commenthead .ha-letlinks.ha-expanded-box { --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } +.dark .ha-register .ha-register-body .ha-commenthead .ha-letlinks.ha-expanded-box { + --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + .ha-register .ha-btn-collapsed-box { position: absolute; top: -0.15rem; @@ -813,12 +821,14 @@ body { .ha-letterheader { border-bottom-width: 2px; --tw-border-opacity: 1; - border-color: rgb(226 232 240 / var(--tw-border-opacity)); + border-color: rgb(203 213 225 / var(--tw-border-opacity)); --tw-bg-opacity: 1; background-color: rgb(248 250 252 / var(--tw-bg-opacity)); } .dark .ha-letterheader { + --tw-border-opacity: 1; + border-color: rgb(248 250 252 / var(--tw-border-opacity)); --tw-bg-opacity: 1; background-color: rgb(15 23 42 / var(--tw-bg-opacity)); --tw-text-opacity: 1; @@ -849,14 +859,16 @@ body { } .ha-letterheader .ha-lettertabs a.active { - border-bottom-width: 2px; + border-bottom-width: 3px; --tw-border-opacity: 1; - border-color: rgb(226 232 240 / var(--tw-border-opacity)); + border-color: rgb(203 213 225 / var(--tw-border-opacity)); --tw-text-opacity: 1; color: rgb(216 0 0 / var(--tw-text-opacity)); } .dark .ha-letterheader .ha-lettertabs a.active { + --tw-border-opacity: 1; + border-color: rgb(248 250 252 / var(--tw-border-opacity)); font-weight: 700; --tw-text-opacity: 1 !important; color: rgb(229 231 235 / var(--tw-text-opacity)) !important; @@ -932,6 +944,7 @@ body { } .ha-additions { + display: none; --tw-bg-opacity: 1; background-color: rgb(248 250 252 / var(--tw-bg-opacity)); } @@ -1000,11 +1013,18 @@ body { } .ha-tradzhtext .ha-marginalbox.ha-expanded-box .ha-marginallist, .ha-lettertext .ha-marginalbox.ha-expanded-box .ha-marginallist { + padding-bottom: 0.25rem; --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } +.dark .ha-tradzhtext .ha-marginalbox.ha-expanded-box .ha-marginallist, .dark .ha-lettertext .ha-marginalbox.ha-expanded-box .ha-marginallist { + --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + .ha-tradzhtext .ha-btn-collapsed-box, .ha-lettertext .ha-btn-collapsed-box { --tw-text-opacity: 1; color: rgb(51 65 85 / var(--tw-text-opacity)); @@ -1025,50 +1045,42 @@ body { color: rgb(229 231 235 / var(--tw-text-opacity)); } -.ha-added, .ha-added - * - :not(.ha-linecount *, .ha-linecount, .ha-marginal *, .ha-marginal) { +.ha-added, .ha-added *:not(.ha-linecount *, .ha-linecount, .ha-marginalbox *, .ha-marginalbox, .ha-marginal *, .ha-marginal, .ha-btn-collapsed-box) { --tw-bg-opacity: 1; background-color: rgb(203 213 225 / var(--tw-bg-opacity)); } -.dark .ha-added, .dark .ha-added - * - :not(.ha-linecount *, .ha-linecount, .ha-marginal *, .ha-marginal) { +.dark .ha-added, .dark .ha-added *:not(.ha-linecount *, .ha-linecount, .ha-marginalbox *, .ha-marginalbox, .ha-marginal *, .ha-marginal, .ha-btn-collapsed-box) { --tw-bg-opacity: 1; background-color: rgb(71 85 105 / var(--tw-bg-opacity)); } -.ha-note, .ha-note - * - :not(.ha-linecount *, .ha-linecount, .ha-marginal *, .ha-marginal) { +.ha-note, .ha-note *:not(.ha-linecount *, .ha-linecount, .ha-marginalbox *, .ha-marginalbox, .ha-marginal *, .ha-marginal, .ha-btn-collapsed-box) { --tw-text-opacity: 1; color: rgb(51 65 85 / var(--tw-text-opacity)); } -.dark .ha-note, .dark .ha-note - * - :not(.ha-linecount *, .ha-linecount, .ha-marginal *, .ha-marginal) { +.dark .ha-note, .dark .ha-note *:not(.ha-linecount *, .ha-linecount, .ha-marginalbox *, .ha-marginalbox, .ha-marginal *, .ha-marginal, .ha-btn-collapsed-box) { --tw-text-opacity: 1; color: rgb(100 116 139 / var(--tw-text-opacity)); } -.ha-ful, .ha-ful * :not(.ha-linecount *, .ha-linecount, .ha-marginal *, .ha-marginal) { +.ha-ful, .ha-ful *:not(.ha-linecount *, .ha-linecount, .ha-marginalbox *, .ha-marginalbox, .ha-marginal *, .ha-marginal, .ha-btn-collapsed-box) { --tw-border-opacity: 1; border-color: rgb(0 0 0 / var(--tw-border-opacity)); } -.dark .ha-ful, .dark .ha-ful * :not(.ha-linecount *, .ha-linecount, .ha-marginal *, .ha-marginal) { +.dark .ha-ful, .dark .ha-ful *:not(.ha-linecount *, .ha-linecount, .ha-marginalbox *, .ha-marginalbox, .ha-marginal *, .ha-marginal, .ha-btn-collapsed-box) { --tw-border-opacity: 1; border-color: rgb(255 255 255 / var(--tw-border-opacity)); } -.ha-tul, .ha-tul * :not(.ha-linecount *, .ha-linecount, .ha-marginal *, .ha-marginal) { +.ha-tul, .ha-tul *:not(.ha-linecount *, .ha-linecount, .ha-marginalbox *, .ha-marginalbox, .ha-marginal *, .ha-marginal, .ha-btn-collapsed-box) { --tw-border-opacity: 1; border-color: rgb(0 0 0 / var(--tw-border-opacity)); } -.dark .ha-tul, .dark .ha-tul * :not(.ha-linecount *, .ha-linecount, .ha-marginal *, .ha-marginal) { +.dark .ha-tul, .dark .ha-tul *:not(.ha-linecount *, .ha-linecount, .ha-marginalbox *, .ha-marginalbox, .ha-marginal *, .ha-marginal, .ha-btn-collapsed-box) { --tw-border-opacity: 1; border-color: rgb(255 255 255 / var(--tw-border-opacity)); } @@ -1610,6 +1622,16 @@ body { } } +.ha-register .ha-register-body .ha-comment .ha-commenthead .ha-letlinks::before { + position: absolute; + top: 0.1rem; + bottom: 0.1rem; + left: 0px; + width: 0.125rem; + --tw-content: ''; + content: var(--tw-content); +} + .ha-register .ha-register-body .ha-comment @@ -1868,6 +1890,7 @@ body { .ha-lettertext { position: relative; margin-left: 1rem; + display: flow-root; max-width: 38rem; padding-left: 1rem; padding-right: 1rem; @@ -1894,6 +1917,7 @@ body { .ha-marginals { position: relative; margin-left: 1rem; + display: none; max-width: 48rem; padding-left: 1rem; padding-right: 1rem; @@ -1986,7 +2010,7 @@ body { .ha-additions .ha-tradition .ha-tradzhtext { position: relative; margin-left: -1rem; - display: block !important; + display: flow-root !important; width: -webkit-fit-content; width: -moz-fit-content; width: fit-content; @@ -2126,19 +2150,11 @@ body { @media (min-width: 700px) { .ha-linecount.ha-firstline { - left: -4rem; display: inline-block; - max-width: 5rem; padding-bottom: 0.25rem; } } -@media (min-width: 1190px) { - .ha-linecount.ha-firstline { - left: -5.5rem; - } -} - .ha-linecount { -webkit-user-select: none; -moz-user-select: none; @@ -2152,9 +2168,9 @@ body { @media (min-width: 700px) { .ha-linecount { position: absolute; - left: -8.6rem; + right: 100%; + margin-right: 0.5rem; margin-top: 0.25rem; - width: 8rem; text-align: right; } } @@ -2196,7 +2212,7 @@ body { display: none !important; } -.ha-tradzhtext .ha-marginal::before, .ha-lettertext .ha-marginal:before { +.ha-tradzhtext .ha-marginal::before, .ha-lettertext .ha-marginal::before { position: absolute; top: 0.1rem; bottom: 0.1rem; @@ -2219,9 +2235,6 @@ body { font-size: 0.875rem; line-height: 1.25rem; line-height: 1.25; - -webkit-hyphens: auto; - -ms-hyphens: auto; - hyphens: auto; } @media (min-width: 960px) { @@ -2239,8 +2252,8 @@ body { .ha-tradzhtext .ha-marginalbox .ha-marginallist, .ha-lettertext .ha-marginalbox .ha-marginallist { display: flex; flex-wrap: wrap; - -moz-column-gap: 1rem; - column-gap: 1rem; + -moz-column-gap: 1.5rem; + column-gap: 1.5rem; font-size: 0.875rem; line-height: 1.25rem; line-height: 1.25; @@ -2249,7 +2262,6 @@ body { .ha-tradzhtext .ha-marginalbox .ha-marginallist .ha-marginal, .ha-lettertext .ha-marginalbox .ha-marginallist .ha-marginal { position: relative; display: inline; - flex-grow: 1; padding-left: 0.5rem; } @@ -2265,6 +2277,13 @@ body { text-decoration-style: solid; } +.ha-tradzhtext .ha-marginalbox .ha-marginallist .ha-marginal, .ha-lettertext .ha-marginalbox .ha-marginallist .ha-marginal, .ha-tradzhtext .ha-marginalbox .ha-marginallist .ha-marginal *, .ha-lettertext .ha-marginalbox .ha-marginallist .ha-marginal * { + min-height: 0px; + min-width: 0px; + overflow: hidden; + text-overflow: ellipsis; +} + .ha-tradzhtext .ha-btn-collapsed-box, .ha-lettertext .ha-btn-collapsed-box { position: absolute; left: 100%; @@ -2347,47 +2366,41 @@ body { font-family: Libertine, serif; } -.ha-aq, .ha-aq * :not(.ha-linecount *, .ha-linecount, .ha-marginal *, .ha-marginal) { +.ha-aq, .ha-aq *:not(.ha-linecount *, .ha-linecount, .ha-marginalbox *, .ha-marginalbox, .ha-marginal, .ha-marginal *, .ha-btn-collapsed-box) { font-family: Biolinum, sans-serif; } -.ha-ul, .ha-ul * :not(.ha-linecount *, .ha-linecount, .ha-marginal *, .ha-marginal) { +.ha-ul, .ha-ul *:not(.ha-linecount *, .ha-linecount, .ha-marginalbox *, .ha-marginalbox, .ha-marginal, .ha-marginal *, .ha-btn-collapsed-box) { -webkit-text-decoration-line: underline; text-decoration-line: underline; } -.ha-del, .ha-del * :not(.ha-linecount *, .ha-linecount, .ha-marginal *, .ha-marginal, .ha-diagdel) { +.ha-del, .ha-del *:not(.ha-linecount *, .ha-linecount, .ha-marginalbox *, .ha-marginalbox, .ha-diagdel, .ha-marginal, .ha-marginal *, .ha-btn-collapsed-box) { display: inline; -webkit-text-decoration-line: line-through; text-decoration-line: line-through; } -.ha-hand, .ha-hand - * - :not(.ha-linecount *, .ha-linecount, .ha-marginal *, .ha-marginal) { +.ha-hand, .ha-hand *:not(.ha-linecount *, .ha-linecount, .ha-marginalbox *, .ha-marginalbox, .ha-marginal, .ha-marginal *, .ha-btn-collapsed-box) { font-family: Playfair, serif; font-size: 0.9rem; } -.ha-added, .ha-added - * - :not(.ha-linecount *, .ha-linecount, .ha-marginal *, .ha-marginal) { +.ha-added, .ha-added *:not(.ha-linecount *, .ha-linecount, .ha-marginalbox *, .ha-marginalbox, .ha-marginal, .ha-marginal *, .ha-btn-collapsed-box) { border-radius: 0.125rem; padding-left: 0.25rem; padding-right: 0.25rem; } -.ha-note, .ha-note - * - :not(.ha-linecount *, .ha-linecount, .ha-marginal *, .ha-marginal) { +.ha-note, .ha-note *:not(.ha-linecount *, .ha-linecount, .ha-marginalbox *, .ha-marginalbox, .ha-marginal, .ha-marginal *, .ha-btn-collapsed-box) { font-style: italic; } -.ha-emph { +.ha-emph *:not(.ha-linecount *, .ha-linecount, .ha-marginalbox *, .ha-marginalbox, .ha-marginal, .ha-marginal *, .ha-btn-collapsed-box) { font-style: italic; } -.ha-sup { +.ha-sup *:not(.ha-linecount *, .ha-linecount, .ha-marginalbox *, .ha-marginalbox, .ha-marginal, .ha-marginal *, .ha-btn-collapsed-box) { position: relative; top: -0.3em; font-size: 80%; @@ -2414,20 +2427,20 @@ body { line-height: 1; } -.ha-ful, .ha-ful * :not(.ha-linecount *, .ha-linecount, .ha-marginal *, .ha-marginal) { +.ha-ful, .ha-ful *:not(.ha-linecount *, .ha-linecount, .ha-marginalbox *, .ha-marginalbox, .ha-marginal *, .ha-marginal, .ha-btn-collapsed-box) { display: inline; border-bottom-width: 1px; padding-bottom: 2px; } -.ha-dul, .ha-dul * :not(.ha-linecount *, .ha-linecount, .ha-marginal *, .ha-marginal) { +.ha-dul, .ha-dul *:not(.ha-linecount *, .ha-linecount, .ha-marginalbox *, .ha-marginalbox, .ha-marginal *, .ha-marginal, .ha-btn-collapsed-box) { -webkit-text-decoration-line: underline; text-decoration-line: underline; -webkit-text-decoration-style: double; text-decoration-style: double; } -.ha-tul, .ha-tul * :not(.ha-linecount *, .ha-linecount, .ha-marginal *, .ha-marginal) { +.ha-tul, .ha-tul *:not(.ha-linecount *, .ha-linecount, .ha-marginalbox *, .ha-marginalbox, .ha-marginal *, .ha-marginal, .ha-btn-collapsed-box) { border-bottom-width: 3px; border-style: double; -webkit-text-decoration-line: underline; @@ -2670,8 +2683,10 @@ body { } } -.ha-collapsed-box { +.ha-collapsed-box, .ha-collapsed-box * { z-index: 0; + min-height: 0px; + min-width: 0px; cursor: default; overflow: hidden; text-overflow: ellipsis; @@ -2684,11 +2699,6 @@ body { padding-bottom: 0.25rem; } -/* .ha-register .ha-neuzeit .ha-register-body .ha-commenthead .ha-collapsed-box:hover, - .ha-register .ha-forschung .ha-register-body .ha-commenthead .ha-collapsed-box:hover { - @apply shadow-md z-[1000] !h-auto - } */ - .pointer-events-none { pointer-events: none; } @@ -2757,6 +2767,10 @@ body { display: table; } +.flow-root { + display: flow-root; +} + .hidden { display: none; } @@ -3073,6 +3087,12 @@ body { list-style-type:circle; } */ +.ha-tradzhtext .ha-marginalbox.ha-collapsed-box .ha-marginallist .ha-marginal, +.ha-lettertext .ha-marginalbox.ha-collapsed-box .ha-marginallist .ha-marginal { + display: -webkit-inline-box; + -webkit-box-orient: vertical; +} + .ha-diagdel { text-decoration: none !important; -webkit-text-decoration-line: none !important; @@ -3098,7 +3118,7 @@ body { } .ha-del .ha-del, -.ha-del .ha-del * { +.ha-del .ha-del *:not(.ha-linecount *, .ha-linecount, .ha-marginalbox *, .ha-marginalbox, .ha-marginal *, .ha-marginal, .ha-btn-collapsed-box) { -moz-text-decoration-style: double; -webkit-text-decoration-style: double !important; text-decoration-thickness: 1px; diff --git a/HaWeb/wwwroot/css/site.css b/HaWeb/wwwroot/css/site.css index 278fc7c..19eb4cc 100644 --- a/HaWeb/wwwroot/css/site.css +++ b/HaWeb/wwwroot/css/site.css @@ -168,11 +168,16 @@ .ha-register .ha-neuzeit .ha-register-body .ha-commenthead .ha-letlinks, .ha-register .ha-forschung .ha-register-body .ha-commenthead .ha-letlinks { - @apply desktop:bg-slate-50 dark:bg-slate-900 dark:text-slate-50 border-l-2 border-slate-300 dark:border-slate-500 + @apply desktop:bg-slate-50 dark:bg-slate-900 dark:text-slate-50 + } + + .ha-register .ha-neuzeit .ha-register-body .ha-commenthead .ha-letlinks::before, + .ha-register .ha-forschung .ha-register-body .ha-commenthead .ha-letlinks::before { + @apply bg-slate-400 dark:bg-slate-500 } .ha-register .ha-register-body .ha-commenthead .ha-letlinks.ha-expanded-box { - @apply shadow-md + @apply shadow-md dark:shadow-lg } .ha-register .ha-btn-collapsed-box { @@ -213,7 +218,7 @@ } .ha-letterheader { - @apply bg-slate-50 dark:bg-slate-900 dark:text-slate-50 border-slate-200 border-b-2 dark:shadow-xl + @apply bg-slate-50 dark:bg-slate-900 dark:text-slate-50 border-slate-300 dark:border-slate-50 border-b-2 dark:shadow-xl } .ha-letterheader .ha-lettertabs a { @@ -221,7 +226,7 @@ } .ha-letterheader .ha-lettertabs a.active { - @apply border-b-2 border-slate-200 text-hamannHighlight dark:!text-gray-200 dark:font-bold + @apply border-b-[3px] border-slate-300 dark:border-slate-50 text-hamannHighlight dark:!text-gray-200 dark:font-bold } .ha-letterheader .ha-lettermetalinks { @@ -245,7 +250,7 @@ } .ha-additions { - @apply bg-slate-50 dark:bg-slate-900 + @apply hidden bg-slate-50 dark:bg-slate-900 } .ha-additions .ha-edits .ha-editentries table tr:nth-child(even) { @@ -273,7 +278,7 @@ .ha-tradzhtext .ha-marginalbox.ha-expanded-box .ha-marginallist, .ha-lettertext .ha-marginalbox.ha-expanded-box .ha-marginallist { - @apply shadow-md + @apply shadow-md dark:shadow-lg pb-1 } .ha-tradzhtext .ha-btn-collapsed-box, @@ -282,26 +287,22 @@ } .ha-added, - .ha-added - * - :not(.ha-linecount *, .ha-linecount, .ha-marginal *, .ha-marginal) { + .ha-added *:not(.ha-linecount *, .ha-linecount, .ha-marginalbox *, .ha-marginalbox, .ha-marginal *, .ha-marginal, .ha-btn-collapsed-box) { @apply bg-slate-300 dark:bg-slate-600 } .ha-note, - .ha-note - * - :not(.ha-linecount *, .ha-linecount, .ha-marginal *, .ha-marginal) { + .ha-note *:not(.ha-linecount *, .ha-linecount, .ha-marginalbox *, .ha-marginalbox, .ha-marginal *, .ha-marginal, .ha-btn-collapsed-box) { @apply text-slate-700 dark:text-slate-500 } .ha-ful, - .ha-ful * :not(.ha-linecount *, .ha-linecount, .ha-marginal *, .ha-marginal) { + .ha-ful *:not(.ha-linecount *, .ha-linecount, .ha-marginalbox *, .ha-marginalbox, .ha-marginal *, .ha-marginal, .ha-btn-collapsed-box) { @apply border-black dark:border-white } .ha-tul, - .ha-tul * :not(.ha-linecount *, .ha-linecount, .ha-marginal *, .ha-marginal) { + .ha-tul *:not(.ha-linecount *, .ha-linecount, .ha-marginalbox *, .ha-marginalbox, .ha-marginal *, .ha-marginal, .ha-btn-collapsed-box) { @apply border-black dark:border-white } @@ -541,6 +542,10 @@ @apply inline-block font-normal text-xs md:text-sm leading-snug font-sans caps-allpetite ml-2 } + .ha-register .ha-register-body .ha-comment .ha-commenthead .ha-letlinks::before { + @apply absolute top-[0.1rem] bottom-[0.1rem] left-0 w-0.5 content-[''] + } + .ha-register .ha-register-body .ha-comment @@ -711,11 +716,11 @@ } .ha-lettertext { - @apply max-w-[38rem] desktop:max-w-[45rem] sm:shrink-0 ml-4 sm:ml-12 px-4 pt-4 pb-8 relative font-serif leading-[1.48] numeric-mediaeval + @apply max-w-[38rem] desktop:max-w-[45rem] sm:shrink-0 ml-4 sm:ml-12 px-4 pt-4 pb-8 relative flow-root font-serif leading-[1.48] numeric-mediaeval } .ha-marginals { - @apply max-w-3xl ml-4 md:ml-12 px-4 py-4 relative leading-[1.48] numeric-mediaeval + @apply hidden max-w-3xl ml-4 md:ml-12 px-4 py-4 relative leading-[1.48] numeric-mediaeval } .ha-marginals table td { @@ -763,7 +768,7 @@ } .ha-additions .ha-tradition .ha-tradzhtext { - @apply !block font-serif relative w-fit -ml-4 pl-4 unhyphenate + @apply !flow-root font-serif relative w-fit -ml-4 pl-4 unhyphenate } .ha-additions .ha-tradition a { @@ -857,11 +862,11 @@ } .ha-linecount.ha-firstline { - @apply hidden sm:inline-block rounded px-1 sm:pb-1 caps-allpetite normal-nums sm:max-w-[5rem] whitespace-nowrap sm:-left-[4rem] desktop:-left-[5.5rem] + @apply hidden sm:inline-block rounded px-1 sm:pb-1 caps-allpetite normal-nums whitespace-nowrap } .ha-linecount { - @apply sm:absolute sm:-left-[8.6rem] sm:text-right sm:w-32 text-xs sm:mt-1 font-sans select-none + @apply sm:absolute sm:right-full sm:mr-2 sm:text-right text-xs sm:mt-1 font-sans select-none } .ha-linecount .ha-zhline { @@ -882,23 +887,23 @@ } .ha-tradzhtext .ha-marginal::before, - .ha-lettertext .ha-marginal:before { + .ha-lettertext .ha-marginal::before { @apply absolute top-[0.1rem] bottom-[0.1rem] left-0 w-0.5 content-[''] } .ha-tradzhtext .ha-marginalbox, .ha-lettertext .ha-marginalbox { - @apply hidden pl-2 md:block absolute left-full ml-6 mt-1 w-[16rem] desktop:w-[24rem] text-sm leading-tight hyphenate rounded-sm font-sans + @apply hidden pl-2 md:block absolute left-full ml-6 mt-1 w-[16rem] desktop:w-[24rem] text-sm leading-tight rounded-sm font-sans } .ha-tradzhtext .ha-marginalbox .ha-marginallist, .ha-lettertext .ha-marginalbox .ha-marginallist { - @apply text-sm leading-tight flex flex-wrap gap-x-4 + @apply text-sm leading-tight flex flex-wrap gap-x-6 } .ha-tradzhtext .ha-marginalbox .ha-marginallist .ha-marginal, .ha-lettertext .ha-marginalbox .ha-marginallist .ha-marginal { - @apply pl-2 inline grow relative + @apply pl-2 inline relative } .ha-tradzhtext .ha-marginalbox .ha-marginallist .ha-marginal a, @@ -906,6 +911,13 @@ @apply !underline decoration-dotted hover:decoration-solid } + .ha-tradzhtext .ha-marginalbox .ha-marginallist .ha-marginal, + .ha-lettertext .ha-marginalbox .ha-marginallist .ha-marginal, + .ha-tradzhtext .ha-marginalbox .ha-marginallist .ha-marginal *, + .ha-lettertext .ha-marginalbox .ha-marginallist .ha-marginal * { + @apply min-h-0 min-w-0 overflow-ellipsis overflow-hidden + } + .ha-tradzhtext .ha-btn-collapsed-box, .ha-lettertext .ha-btn-collapsed-box { @apply absolute left-full ml-4 hidden md:block cursor-pointer leading-none mt-0.5 @@ -957,46 +969,40 @@ } .ha-aq, - .ha-aq * :not(.ha-linecount *, .ha-linecount, .ha-marginal *, .ha-marginal) { + .ha-aq *:not(.ha-linecount *, .ha-linecount, .ha-marginalbox *, .ha-marginalbox, .ha-marginal, .ha-marginal *, .ha-btn-collapsed-box) { @apply font-sans } .ha-ul, - .ha-ul * :not(.ha-linecount *, .ha-linecount, .ha-marginal *, .ha-marginal) { + .ha-ul *:not(.ha-linecount *, .ha-linecount, .ha-marginalbox *, .ha-marginalbox, .ha-marginal, .ha-marginal *, .ha-btn-collapsed-box) { @apply underline } .ha-del, - .ha-del * :not(.ha-linecount *, .ha-linecount, .ha-marginal *, .ha-marginal, .ha-diagdel) { + .ha-del *:not(.ha-linecount *, .ha-linecount, .ha-marginalbox *, .ha-marginalbox, .ha-diagdel, .ha-marginal, .ha-marginal *, .ha-btn-collapsed-box) { @apply inline line-through } .ha-hand, - .ha-hand - * - :not(.ha-linecount *, .ha-linecount, .ha-marginal *, .ha-marginal) { + .ha-hand *:not(.ha-linecount *, .ha-linecount, .ha-marginalbox *, .ha-marginalbox, .ha-marginal, .ha-marginal *, .ha-btn-collapsed-box) { @apply font-classy text-[0.9rem] } .ha-added, - .ha-added - * - :not(.ha-linecount *, .ha-linecount, .ha-marginal *, .ha-marginal) { + .ha-added *:not(.ha-linecount *, .ha-linecount, .ha-marginalbox *, .ha-marginalbox, .ha-marginal, .ha-marginal *, .ha-btn-collapsed-box) { @apply px-1 rounded-sm } .ha-note, - .ha-note - * - :not(.ha-linecount *, .ha-linecount, .ha-marginal *, .ha-marginal) { + .ha-note *:not(.ha-linecount *, .ha-linecount, .ha-marginalbox *, .ha-marginalbox, .ha-marginal, .ha-marginal *, .ha-btn-collapsed-box) { @apply italic } - .ha-emph { + .ha-emph *:not(.ha-linecount *, .ha-linecount, .ha-marginalbox *, .ha-marginalbox, .ha-marginal, .ha-marginal *, .ha-btn-collapsed-box) { @apply italic } - .ha-sup { + .ha-sup *:not(.ha-linecount *, .ha-linecount, .ha-marginalbox *, .ha-marginalbox, .ha-marginal, .ha-marginal *, .ha-btn-collapsed-box) { @apply relative -top-[0.3em] text-[80%] } @@ -1010,17 +1016,17 @@ } .ha-ful, - .ha-ful * :not(.ha-linecount *, .ha-linecount, .ha-marginal *, .ha-marginal) { + .ha-ful *:not(.ha-linecount *, .ha-linecount, .ha-marginalbox *, .ha-marginalbox, .ha-marginal *, .ha-marginal, .ha-btn-collapsed-box) { @apply inline border-b pb-[2px] } .ha-dul, - .ha-dul * :not(.ha-linecount *, .ha-linecount, .ha-marginal *, .ha-marginal) { + .ha-dul *:not(.ha-linecount *, .ha-linecount, .ha-marginalbox *, .ha-marginalbox, .ha-marginal *, .ha-marginal, .ha-btn-collapsed-box) { @apply underline decoration-double } .ha-tul, - .ha-tul * :not(.ha-linecount *, .ha-linecount, .ha-marginal *, .ha-marginal) { + .ha-tul *:not(.ha-linecount *, .ha-linecount, .ha-marginalbox *, .ha-marginalbox, .ha-marginal *, .ha-marginal, .ha-btn-collapsed-box) { @apply underline border-b-[3px] border-double } @@ -1126,18 +1132,14 @@ @apply hidden desktop:block absolute -top-[0.15rem] cursor-pointer } - .ha-collapsed-box { - @apply z-0 overflow-hidden overflow-ellipsis cursor-default + .ha-collapsed-box, + .ha-collapsed-box * { + @apply z-0 overflow-hidden min-w-0 min-h-0 overflow-ellipsis cursor-default } .ha-expanded-box { @apply pb-1 z-[1000] !h-auto !max-h-screen } - - /* .ha-register .ha-neuzeit .ha-register-body .ha-commenthead .ha-collapsed-box:hover, - .ha-register .ha-forschung .ha-register-body .ha-commenthead .ha-collapsed-box:hover { - @apply shadow-md z-[1000] !h-auto - } */ } .ha-lettertext .ha-marginalbox:before { @@ -1197,6 +1199,12 @@ body { list-style-type:circle; } */ +.ha-tradzhtext .ha-marginalbox.ha-collapsed-box .ha-marginallist .ha-marginal, +.ha-lettertext .ha-marginalbox.ha-collapsed-box .ha-marginallist .ha-marginal { + display: -webkit-inline-box; + -webkit-box-orient: vertical; + } + .ha-diagdel { text-decoration: none !important; text-decoration-line: none !important; @@ -1222,7 +1230,7 @@ body { } .ha-del .ha-del, -.ha-del .ha-del * { +.ha-del .ha-del *:not(.ha-linecount *, .ha-linecount, .ha-marginalbox *, .ha-marginalbox, .ha-marginal *, .ha-marginal, .ha-btn-collapsed-box) { -moz-text-decoration-style: double; -webkit-text-decoration-style: double !important; text-decoration-thickness: 1px; diff --git a/HaWeb/wwwroot/js/site.js b/HaWeb/wwwroot/js/site.js index aaf4f4f..cb6523a 100644 --- a/HaWeb/wwwroot/js/site.js +++ b/HaWeb/wwwroot/js/site.js @@ -54,7 +54,7 @@ const getLineHeight = function (element) { return ret; }; -const collapsebox = function (element, height) { +const collapsebox = function (element, height, lineheight) { element.style.maxHeight = height + "px"; element.classList.add("ha-collapsed-box"); element.classList.remove("ha-expanded-box"); @@ -135,14 +135,38 @@ const overlappingcollapsebox = function (selector, hoverfunction) { if (i < boxes.length - 1) { let element = boxes[i]; let thisrect = element.getBoundingClientRect(); - let nextrect = boxes[i+1].getBoundingClientRect(); + let nextrect = boxes[i + 1].getBoundingClientRect(); let overlap = thisrect.bottom - nextrect.top; - if (overlap >= 0 && !(window.getComputedStyle(element).display === "none")) { + if ( + overlap >= 0 && + !(window.getComputedStyle(element).display === "none") + ) { let newlength = thisrect.height - overlap; let remainder = newlength % lineheight; newlength = newlength - remainder - 1; - requestAnimationFrame(() => { collapsebox(element, newlength) }); - requestAnimationFrame(() => { addbuttoncaollapsebox(element, newlength, hoverfunction) }); + + // Line clamping for Marginals + if (element.classList.contains("ha-marginalbox")) { + let marginals = element.querySelectorAll(".ha-marginal"); + let h = 0; + for (let m of marginals) { + let cr = m.getBoundingClientRect(); + let eh = cr.bottom - cr.top; + h += eh; + if (h >= newlength) { + let lines = Math.floor(eh / lineheight); + let cutoff = Math.floor((h-newlength) / lineheight); + m.style.cssText += "-webkit-line-clamp: " + (lines-cutoff) + ";"; + } + } + } + + requestAnimationFrame(() => { + collapsebox(element, newlength, lineheight); + }); + requestAnimationFrame(() => { + addbuttoncaollapsebox(element, newlength, hoverfunction); + }); } } } @@ -176,52 +200,57 @@ const showhidebutton = function ( for (let element of divlist) { let hiddenelement = document.getElementById(element); - if (hiddenelement !== null) hiddenelement.classList.add("hide"); + if (hiddenelement !== null) { + hiddenelement.classList.add("hide") + hiddenelement.classList.remove("flow-root"); + }; } if (button !== null) button.classList.add("active"); - if (div !== null) div.classList.remove("hide"); + if (div !== null) { + div.classList.add("flow-root"); + div.classList.remove("hide"); + } }); } }; // Functions for switching theme -const go_to_dark = function() { - localStorage.setItem('theme', 'ha-toggledark'); - document.documentElement.classList.add('dark'); -} +const go_to_dark = function () { + localStorage.setItem("theme", "ha-toggledark"); + document.documentElement.classList.add("dark"); +}; -const go_to_twilight = function() { - document.documentElement.classList.remove('dark') +const go_to_twilight = function () { + document.documentElement.classList.remove("dark"); let elements = document.getElementsByClassName("ha-twilighttogglebar"); for (let el of elements) { el.classList.add("dark"); } - localStorage.setItem('theme', 'ha-toggletwilight'); -} + localStorage.setItem("theme", "ha-toggletwilight"); +}; -const go_to_bright = function() { - document.documentElement.classList.remove('dark'); +const go_to_bright = function () { + document.documentElement.classList.remove("dark"); let elements = document.getElementsByClassName("ha-twilighttogglebar"); for (let el of elements) { el.classList.remove("dark"); } - localStorage.setItem('theme', 'ha-togglebright'); -} + localStorage.setItem("theme", "ha-togglebright"); +}; // Functions for reading theme settings -const get_theme_settings = function(standard) { - var theme = localStorage.getItem('theme'); +const get_theme_settings = function (standard) { + var theme = localStorage.getItem("theme"); if (theme === null) theme = standard; let toggleSwitch = document.getElementById(theme).click(); -} +}; -const collapseboxes = function() { +const collapseboxes = function () { overlappingcollapsebox(".ha-neuzeit .ha-letlinks", true); overlappingcollapsebox(".ha-forschung .ha-letlinks", true); overlappingcollapsebox(".ha-lettertext .ha-marginalbox", true); -} - +}; //////////////////////////////// ONLOAD //////////////////////////////////// window.addEventListener("load", function () { @@ -247,24 +276,59 @@ window.addEventListener("load", function () { // Letter / Register View: Collapse all unfit boxes + resize observer collapseboxes(); var doit; - this.window.addEventListener('resize', function() { + this.window.addEventListener("resize", function () { this.clearTimeout(doit); this.setTimeout(collapseboxes, 250); }); - // Letter View: Show / Hide Tabs let buttonlist = ["ha-lettertextbtn", "ha-additionsbtn", "ha-marginalsbtn"]; let divlist = ["ha-lettertext", "ha-additions", "ha-marginals"]; if (this.document.getElementById("ha-lettertextbtn") !== null) { - showhidebutton("ha-lettertextbtn", "ha-lettertext", buttonlist, divlist, false); - showhidebutton("ha-additionsbtn", "ha-additions", buttonlist, divlist, true); - showhidebutton("ha-marginalsbtn", "ha-marginals", buttonlist, divlist, true); + showhidebutton( + "ha-lettertextbtn", + "ha-lettertext", + buttonlist, + divlist, + false + ); + showhidebutton( + "ha-additionsbtn", + "ha-additions", + buttonlist, + divlist, + true + ); + showhidebutton( + "ha-marginalsbtn", + "ha-marginals", + buttonlist, + divlist, + true + ); } else { - showhidebutton("ha-lettertextbtn", "ha-lettertext", buttonlist, divlist, true); - showhidebutton("ha-additionsbtn", "ha-additions", buttonlist, divlist, false); - showhidebutton("ha-marginalsbtn", "ha-marginals", buttonlist, divlist, true); + showhidebutton( + "ha-lettertextbtn", + "ha-lettertext", + buttonlist, + divlist, + true + ); + showhidebutton( + "ha-additionsbtn", + "ha-additions", + buttonlist, + divlist, + false + ); + showhidebutton( + "ha-marginalsbtn", + "ha-marginals", + buttonlist, + divlist, + true + ); } // Theme: Get saved theme from memory and check the box accordingly diff --git a/XML/XML/Bibel-Kommentar_2021-07-21.xml b/XML/XML/Bibel-Kommentar_2021-07-21.xml index 544aad9..bcfa9e7 100644 --- a/XML/XML/Bibel-Kommentar_2021-07-21.xml +++ b/XML/XML/Bibel-Kommentar_2021-07-21.xml @@ -1,4 +1,5 @@ - + + diff --git a/XML/XML/Register-Kommentar_2022-03-31.xml b/XML/XML/Register-Kommentar_2022-03-31.xml index 2e76952..1180391 100644 --- a/XML/XML/Register-Kommentar_2022-03-31.xml +++ b/XML/XML/Register-Kommentar_2022-03-31.xml @@ -1,4 +1,5 @@ - + + diff --git a/XML/XML/forschung_2022-05-09.xml b/XML/XML/forschung_2022-05-09.xml index 9de4803..1081a09 100644 --- a/XML/XML/forschung_2022-05-09.xml +++ b/XML/XML/forschung_2022-05-09.xml @@ -1,3 +1,4 @@ +