Crated basic file upload method; also included appsettings and feauture-management

This commit is contained in:
schnulller
2022-06-02 03:19:04 +02:00
parent 34a4fccc91
commit d81eb942e7
21 changed files with 790 additions and 158 deletions

View File

@@ -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");
}
}

View File

@@ -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");
}
}

View File

@@ -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<long>("FileSizeLimit");
_targetFilePath = config.GetValue<string>("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<IActionResult> 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);
}
}

View File

@@ -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));
}
}

View File

@@ -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<string, List<byte[]>> _fileSignature = new Dictionary<string, List<byte[]>>
{
{ ".gif", new List<byte[]> { new byte[] { 0x47, 0x49, 0x46, 0x38 } } },
{ ".png", new List<byte[]> { new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A } } },
{ ".jpeg", new List<byte[]>
{
new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
new byte[] { 0xFF, 0xD8, 0xFF, 0xE2 },
new byte[] { 0xFF, 0xD8, 0xFF, 0xE3 },
}
},
{ ".jpg", new List<byte[]>
{
new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
new byte[] { 0xFF, 0xD8, 0xFF, 0xE1 },
new byte[] { 0xFF, 0xD8, 0xFF, 0xE8 },
}
},
{ ".zip", new List<byte[]>
{
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<byte[]>
{
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<byte[]> ProcessFormFile<T>(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<byte>();
// }
// if (formFile.Length > sizeLimit)
// {
// var megabyteSizeLimit = sizeLimit / 1048576;
// modelState.AddModelError(formFile.Name,
// $"{fieldDisplayName}({trustedFileNameForDisplay}) exceeds " +
// $"{megabyteSizeLimit:N1} MB.");
// return Array.Empty<byte>();
// }
// 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<byte>();
// }
public static async Task<byte[]> 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:<br>" +
"1. The file must hava a .xml File-Extension<br>" +
"2. To make sure the file isn't executable the file must start with: <?xml version=\"1.0\" encoding=\"utf-8\"?> or <?xml version=\"1.0\"?>");
// 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<byte>();
}
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));
}
}
}

View File

@@ -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<IAntiforgery>();
// 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)
{
}
}

View File

@@ -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<FormValueProviderFactory>();
factories.RemoveType<FormFileValueProviderFactory>();
factories.RemoveType<JQueryFormValueProviderFactory>();
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
}

View File

@@ -25,6 +25,7 @@
</Target>
<ItemGroup>
<PackageReference Include="LigerShark.WebOptimizer.Core" Version="3.0.357" />
<PackageReference Include="Microsoft.FeatureManagement.AspNetCore" Version="2.5.1" />
</ItemGroup>
<ItemGroup>

View File

@@ -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<ILibrary>(x => HaDocument.Document.Create(new Options()));
builder.Services.AddTransient<IReaderService, ReaderService>();
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;

View File

@@ -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

View File

@@ -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";
}

View File

View File

@@ -0,0 +1,57 @@
Hello from Upload Index!
<form id="uploadForm" action="Upload" method="post"
enctype="multipart/form-data" onsubmit="AJAXSubmit(this);return false;">
<dl>
<dt>
<label for="file">File</label>
</dt>
<dd>
<input id="file" type="file" name="file" />
</dd>
</dl>
<input class="btn" type="submit" value="Upload" />
<div style="margin-top:15px">
<output form="uploadForm" name="result"></output>
</div>
</form>
@section Scripts {
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"
asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
asp-fallback-test="window.jQuery"
crossorigin="anonymous"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=">
</script>
<script>
"use strict";
async function AJAXSubmit (oFormElement) {
const formData = new FormData(oFormElement);
try {
const response = await fetch(oFormElement.action, {
method: 'POST',
headers: {
'RequestVerificationToken': getCookie('RequestVerificationToken')
},
body: formData
});
oFormElement.elements.namedItem("result").value =
'Result: ' + response.status + ' ' + response.statusText;
} catch (error) {
console.error('Error:', error);
}
}
function getCookie(name) {
var value = "; " + document.cookie;
var parts = value.split("; " + name + "=");
if (parts.length == 2) return parts.pop().split(";").shift();
}
</script>
}

View File

@@ -45,6 +45,8 @@
<environment exclude="Development">
@await Html.PartialAsync("/Views/Shared/_Javascript.cshtml")
</environment>
@RenderSection("Scripts", required: false)
</body>
</html>

View File

@@ -5,5 +5,12 @@
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
"FeatureManagement": {
"AdminService": true,
"UploadService": true,
"UpdateService": false
},
"AllowedHosts": "*",
"StoredFilesPath": "/home/simon/Downloads/",
"FileSizeLimit": 209799152
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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