mirror of
https://github.com/Theodor-Springmann-Stiftung/lenz-web.git
synced 2025-10-29 01:05:32 +00:00
Finsihed xml model & small bugfixes
This commit is contained in:
@@ -1,11 +1,5 @@
|
|||||||
package functions
|
package functions
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/Theodor-Springmann-Stiftung/lenz-web/helpers/xsdtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Month struct {
|
type Month struct {
|
||||||
Full string
|
Full string
|
||||||
Short string
|
Short string
|
||||||
@@ -54,50 +48,6 @@ var TRANSLD = []Weekday{
|
|||||||
{"Sonntag", "So", 7},
|
{"Sonntag", "So", 7},
|
||||||
}
|
}
|
||||||
|
|
||||||
func HRDateShort(date string) string {
|
|
||||||
xsdt, err := xsdtime.New(date)
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
t := xsdt.Type()
|
|
||||||
if t == xsdtime.GYear {
|
|
||||||
return strconv.Itoa(xsdt.Year)
|
|
||||||
}
|
|
||||||
|
|
||||||
if t == xsdtime.GYearMonth {
|
|
||||||
return strconv.Itoa(xsdt.Month) + "." + strconv.Itoa(xsdt.Year)
|
|
||||||
}
|
|
||||||
|
|
||||||
if t == xsdtime.Date {
|
|
||||||
return strconv.Itoa(xsdt.Day) + "." + strconv.Itoa(xsdt.Month) + "." + strconv.Itoa(xsdt.Year)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func HRDateYear(date string) string {
|
|
||||||
xsdt, err := xsdtime.New(date)
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
t := xsdt.Type()
|
|
||||||
if t == xsdtime.GYear {
|
|
||||||
return strconv.Itoa(xsdt.Year)
|
|
||||||
}
|
|
||||||
|
|
||||||
if t == xsdtime.GYearMonth {
|
|
||||||
return strconv.Itoa(xsdt.Year)
|
|
||||||
}
|
|
||||||
|
|
||||||
if t == xsdtime.Date {
|
|
||||||
return strconv.Itoa(xsdt.Year)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func MonthName(i int) Month {
|
func MonthName(i int) Month {
|
||||||
if i > 12 || i < 1 {
|
if i > 12 || i < 1 {
|
||||||
return TRANSLM[0]
|
return TRANSLM[0]
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// INFO: This timeout is stupid. Uploads can take a long time, other routes might not. It's messy.
|
||||||
|
REQUEST_TIMEOUT = 16 * time.Second
|
||||||
|
SERVER_TIMEOUT = 16 * time.Second
|
||||||
|
|
||||||
|
// INFO: Maybe this is too long/short?
|
||||||
|
CACHE_TIME = 24 * time.Hour
|
||||||
|
CACHE_GC_INTERVAL = 120 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
STATIC_FILEPATH = "./views/assets"
|
||||||
|
ROUTES_FILEPATH = "./views/routes"
|
||||||
|
LAYOUT_FILEPATH = "./views/layouts"
|
||||||
|
)
|
||||||
|
|||||||
BIN
views/assets/bg.jpg
Normal file
BIN
views/assets/bg.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
55
views/assets/css/fonts.css
Normal file
55
views/assets/css/fonts.css
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: "Linux Libertine";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/assets/fonts/LinLibertine_R_G.ttf) format("truetype");
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Linux Libertine";
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 500;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/assets/fonts/LinLibertine_RI_G.ttf) format("truetype");
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Linux Libertine";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: bold;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/assets/fonts/LinLibertine_RB_G.ttf) format("truetype");
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Linux Libertine";
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: bold;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/assets/fonts/LinLibertine_RBI_G.ttf) format("truetype");
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Linux Biolinum";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/assets/fonts/LinBiolinum_R_G) format("truetype");
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Linux Biolinum";
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 500;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/assets/fonts/LinBiolinum_RI_G) format("truetype");
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Linux Biolinum";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: bold;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(/assets/fonts/LinBiolinum_RB_G) format("truetype")
|
||||||
|
}
|
||||||
655
views/assets/css/hint.css
Normal file
655
views/assets/css/hint.css
Normal file
@@ -0,0 +1,655 @@
|
|||||||
|
/*! Hint.css - v2.7.0 - 2021-10-01
|
||||||
|
* https://kushagra.dev/lab/hint/
|
||||||
|
* Copyright (c) 2021 Kushagra Gour */
|
||||||
|
|
||||||
|
/*-------------------------------------*\
|
||||||
|
HINT.css - A CSS tooltip library
|
||||||
|
\*-------------------------------------*/
|
||||||
|
/**
|
||||||
|
* HINT.css is a tooltip library made in pure CSS.
|
||||||
|
*
|
||||||
|
* Source: https://github.com/chinchang/hint.css
|
||||||
|
* Demo: http://kushagragour.in/lab/hint/
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* source: hint-core.scss
|
||||||
|
*
|
||||||
|
* Defines the basic styling for the tooltip.
|
||||||
|
* Each tooltip is made of 2 parts:
|
||||||
|
* 1) body (:after)
|
||||||
|
* 2) arrow (:before)
|
||||||
|
*
|
||||||
|
* Classes added:
|
||||||
|
* 1) hint
|
||||||
|
*/
|
||||||
|
[class*="hint--"] {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
/**
|
||||||
|
* tooltip arrow
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* tooltip body
|
||||||
|
*/ }
|
||||||
|
[class*="hint--"]:before, [class*="hint--"]:after {
|
||||||
|
position: absolute;
|
||||||
|
-webkit-transform: translate3d(0, 0, 0);
|
||||||
|
-moz-transform: translate3d(0, 0, 0);
|
||||||
|
transform: translate3d(0, 0, 0);
|
||||||
|
visibility: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
z-index: 1000000;
|
||||||
|
pointer-events: none;
|
||||||
|
-webkit-transition: 0.3s ease;
|
||||||
|
-moz-transition: 0.3s ease;
|
||||||
|
transition: 0.3s ease;
|
||||||
|
-webkit-transition-delay: 0ms;
|
||||||
|
-moz-transition-delay: 0ms;
|
||||||
|
transition-delay: 0ms; }
|
||||||
|
[class*="hint--"]:hover:before, [class*="hint--"]:hover:after {
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1; }
|
||||||
|
[class*="hint--"]:hover:before, [class*="hint--"]:hover:after {
|
||||||
|
-webkit-transition-delay: 100ms;
|
||||||
|
-moz-transition-delay: 100ms;
|
||||||
|
transition-delay: 100ms; }
|
||||||
|
[class*="hint--"]:before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
background: transparent;
|
||||||
|
border: 6px solid transparent;
|
||||||
|
z-index: 1000001; }
|
||||||
|
[class*="hint--"]:after {
|
||||||
|
background: #383838;
|
||||||
|
color: white;
|
||||||
|
padding: 8px 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||||
|
line-height: 12px;
|
||||||
|
white-space: nowrap; }
|
||||||
|
[class*="hint--"][aria-label]:after {
|
||||||
|
content: attr(aria-label); }
|
||||||
|
[class*="hint--"][data-hint]:after {
|
||||||
|
content: attr(data-hint); }
|
||||||
|
|
||||||
|
[aria-label='']:before, [aria-label='']:after,
|
||||||
|
[data-hint='']:before,
|
||||||
|
[data-hint='']:after {
|
||||||
|
display: none !important; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* source: hint-position.scss
|
||||||
|
*
|
||||||
|
* Defines the positoning logic for the tooltips.
|
||||||
|
*
|
||||||
|
* Classes added:
|
||||||
|
* 1) hint--top
|
||||||
|
* 2) hint--bottom
|
||||||
|
* 3) hint--left
|
||||||
|
* 4) hint--right
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* set default color for tooltip arrows
|
||||||
|
*/
|
||||||
|
.hint--top-left:before {
|
||||||
|
border-top-color: #383838; }
|
||||||
|
|
||||||
|
.hint--top-right:before {
|
||||||
|
border-top-color: #383838; }
|
||||||
|
|
||||||
|
.hint--top:before {
|
||||||
|
border-top-color: #383838; }
|
||||||
|
|
||||||
|
.hint--bottom-left:before {
|
||||||
|
border-bottom-color: #383838; }
|
||||||
|
|
||||||
|
.hint--bottom-right:before {
|
||||||
|
border-bottom-color: #383838; }
|
||||||
|
|
||||||
|
.hint--bottom:before {
|
||||||
|
border-bottom-color: #383838; }
|
||||||
|
|
||||||
|
.hint--left:before {
|
||||||
|
border-left-color: #383838; }
|
||||||
|
|
||||||
|
.hint--right:before {
|
||||||
|
border-right-color: #383838; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* top tooltip
|
||||||
|
*/
|
||||||
|
.hint--top:before {
|
||||||
|
margin-bottom: -11px; }
|
||||||
|
|
||||||
|
.hint--top:before, .hint--top:after {
|
||||||
|
bottom: 100%;
|
||||||
|
left: 50%; }
|
||||||
|
|
||||||
|
.hint--top:before {
|
||||||
|
left: calc(50% - 6px); }
|
||||||
|
|
||||||
|
.hint--top:after {
|
||||||
|
-webkit-transform: translateX(-50%);
|
||||||
|
-moz-transform: translateX(-50%);
|
||||||
|
transform: translateX(-50%); }
|
||||||
|
|
||||||
|
.hint--top:hover:before {
|
||||||
|
-webkit-transform: translateY(-8px);
|
||||||
|
-moz-transform: translateY(-8px);
|
||||||
|
transform: translateY(-8px); }
|
||||||
|
|
||||||
|
.hint--top:hover:after {
|
||||||
|
-webkit-transform: translateX(-50%) translateY(-8px);
|
||||||
|
-moz-transform: translateX(-50%) translateY(-8px);
|
||||||
|
transform: translateX(-50%) translateY(-8px); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* bottom tooltip
|
||||||
|
*/
|
||||||
|
.hint--bottom:before {
|
||||||
|
margin-top: -11px; }
|
||||||
|
|
||||||
|
.hint--bottom:before, .hint--bottom:after {
|
||||||
|
top: 100%;
|
||||||
|
left: 50%; }
|
||||||
|
|
||||||
|
.hint--bottom:before {
|
||||||
|
left: calc(50% - 6px); }
|
||||||
|
|
||||||
|
.hint--bottom:after {
|
||||||
|
-webkit-transform: translateX(-50%);
|
||||||
|
-moz-transform: translateX(-50%);
|
||||||
|
transform: translateX(-50%); }
|
||||||
|
|
||||||
|
.hint--bottom:hover:before {
|
||||||
|
-webkit-transform: translateY(8px);
|
||||||
|
-moz-transform: translateY(8px);
|
||||||
|
transform: translateY(8px); }
|
||||||
|
|
||||||
|
.hint--bottom:hover:after {
|
||||||
|
-webkit-transform: translateX(-50%) translateY(8px);
|
||||||
|
-moz-transform: translateX(-50%) translateY(8px);
|
||||||
|
transform: translateX(-50%) translateY(8px); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* right tooltip
|
||||||
|
*/
|
||||||
|
.hint--right:before {
|
||||||
|
margin-left: -11px;
|
||||||
|
margin-bottom: -6px; }
|
||||||
|
|
||||||
|
.hint--right:after {
|
||||||
|
margin-bottom: -14px; }
|
||||||
|
|
||||||
|
.hint--right:before, .hint--right:after {
|
||||||
|
left: 100%;
|
||||||
|
bottom: 50%; }
|
||||||
|
|
||||||
|
.hint--right:hover:before {
|
||||||
|
-webkit-transform: translateX(8px);
|
||||||
|
-moz-transform: translateX(8px);
|
||||||
|
transform: translateX(8px); }
|
||||||
|
|
||||||
|
.hint--right:hover:after {
|
||||||
|
-webkit-transform: translateX(8px);
|
||||||
|
-moz-transform: translateX(8px);
|
||||||
|
transform: translateX(8px); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* left tooltip
|
||||||
|
*/
|
||||||
|
.hint--left:before {
|
||||||
|
margin-right: -11px;
|
||||||
|
margin-bottom: -6px; }
|
||||||
|
|
||||||
|
.hint--left:after {
|
||||||
|
margin-bottom: -14px; }
|
||||||
|
|
||||||
|
.hint--left:before, .hint--left:after {
|
||||||
|
right: 100%;
|
||||||
|
bottom: 50%; }
|
||||||
|
|
||||||
|
.hint--left:hover:before {
|
||||||
|
-webkit-transform: translateX(-8px);
|
||||||
|
-moz-transform: translateX(-8px);
|
||||||
|
transform: translateX(-8px); }
|
||||||
|
|
||||||
|
.hint--left:hover:after {
|
||||||
|
-webkit-transform: translateX(-8px);
|
||||||
|
-moz-transform: translateX(-8px);
|
||||||
|
transform: translateX(-8px); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* top-left tooltip
|
||||||
|
*/
|
||||||
|
.hint--top-left:before {
|
||||||
|
margin-bottom: -11px; }
|
||||||
|
|
||||||
|
.hint--top-left:before, .hint--top-left:after {
|
||||||
|
bottom: 100%;
|
||||||
|
left: 50%; }
|
||||||
|
|
||||||
|
.hint--top-left:before {
|
||||||
|
left: calc(50% - 6px); }
|
||||||
|
|
||||||
|
.hint--top-left:after {
|
||||||
|
-webkit-transform: translateX(-100%);
|
||||||
|
-moz-transform: translateX(-100%);
|
||||||
|
transform: translateX(-100%); }
|
||||||
|
|
||||||
|
.hint--top-left:after {
|
||||||
|
margin-left: 12px; }
|
||||||
|
|
||||||
|
.hint--top-left:hover:before {
|
||||||
|
-webkit-transform: translateY(-8px);
|
||||||
|
-moz-transform: translateY(-8px);
|
||||||
|
transform: translateY(-8px); }
|
||||||
|
|
||||||
|
.hint--top-left:hover:after {
|
||||||
|
-webkit-transform: translateX(-100%) translateY(-8px);
|
||||||
|
-moz-transform: translateX(-100%) translateY(-8px);
|
||||||
|
transform: translateX(-100%) translateY(-8px); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* top-right tooltip
|
||||||
|
*/
|
||||||
|
.hint--top-right:before {
|
||||||
|
margin-bottom: -11px; }
|
||||||
|
|
||||||
|
.hint--top-right:before, .hint--top-right:after {
|
||||||
|
bottom: 100%;
|
||||||
|
left: 50%; }
|
||||||
|
|
||||||
|
.hint--top-right:before {
|
||||||
|
left: calc(50% - 6px); }
|
||||||
|
|
||||||
|
.hint--top-right:after {
|
||||||
|
-webkit-transform: translateX(0);
|
||||||
|
-moz-transform: translateX(0);
|
||||||
|
transform: translateX(0); }
|
||||||
|
|
||||||
|
.hint--top-right:after {
|
||||||
|
margin-left: -12px; }
|
||||||
|
|
||||||
|
.hint--top-right:hover:before {
|
||||||
|
-webkit-transform: translateY(-8px);
|
||||||
|
-moz-transform: translateY(-8px);
|
||||||
|
transform: translateY(-8px); }
|
||||||
|
|
||||||
|
.hint--top-right:hover:after {
|
||||||
|
-webkit-transform: translateY(-8px);
|
||||||
|
-moz-transform: translateY(-8px);
|
||||||
|
transform: translateY(-8px); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* bottom-left tooltip
|
||||||
|
*/
|
||||||
|
.hint--bottom-left:before {
|
||||||
|
margin-top: -11px; }
|
||||||
|
|
||||||
|
.hint--bottom-left:before, .hint--bottom-left:after {
|
||||||
|
top: 100%;
|
||||||
|
left: 50%; }
|
||||||
|
|
||||||
|
.hint--bottom-left:before {
|
||||||
|
left: calc(50% - 6px); }
|
||||||
|
|
||||||
|
.hint--bottom-left:after {
|
||||||
|
-webkit-transform: translateX(-100%);
|
||||||
|
-moz-transform: translateX(-100%);
|
||||||
|
transform: translateX(-100%); }
|
||||||
|
|
||||||
|
.hint--bottom-left:after {
|
||||||
|
margin-left: 12px; }
|
||||||
|
|
||||||
|
.hint--bottom-left:hover:before {
|
||||||
|
-webkit-transform: translateY(8px);
|
||||||
|
-moz-transform: translateY(8px);
|
||||||
|
transform: translateY(8px); }
|
||||||
|
|
||||||
|
.hint--bottom-left:hover:after {
|
||||||
|
-webkit-transform: translateX(-100%) translateY(8px);
|
||||||
|
-moz-transform: translateX(-100%) translateY(8px);
|
||||||
|
transform: translateX(-100%) translateY(8px); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* bottom-right tooltip
|
||||||
|
*/
|
||||||
|
.hint--bottom-right:before {
|
||||||
|
margin-top: -11px; }
|
||||||
|
|
||||||
|
.hint--bottom-right:before, .hint--bottom-right:after {
|
||||||
|
top: 100%;
|
||||||
|
left: 50%; }
|
||||||
|
|
||||||
|
.hint--bottom-right:before {
|
||||||
|
left: calc(50% - 6px); }
|
||||||
|
|
||||||
|
.hint--bottom-right:after {
|
||||||
|
-webkit-transform: translateX(0);
|
||||||
|
-moz-transform: translateX(0);
|
||||||
|
transform: translateX(0); }
|
||||||
|
|
||||||
|
.hint--bottom-right:after {
|
||||||
|
margin-left: -12px; }
|
||||||
|
|
||||||
|
.hint--bottom-right:hover:before {
|
||||||
|
-webkit-transform: translateY(8px);
|
||||||
|
-moz-transform: translateY(8px);
|
||||||
|
transform: translateY(8px); }
|
||||||
|
|
||||||
|
.hint--bottom-right:hover:after {
|
||||||
|
-webkit-transform: translateY(8px);
|
||||||
|
-moz-transform: translateY(8px);
|
||||||
|
transform: translateY(8px); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* source: hint-sizes.scss
|
||||||
|
*
|
||||||
|
* Defines width restricted tooltips that can span
|
||||||
|
* across multiple lines.
|
||||||
|
*
|
||||||
|
* Classes added:
|
||||||
|
* 1) hint--small
|
||||||
|
* 2) hint--medium
|
||||||
|
* 3) hint--large
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
.hint--small:after,
|
||||||
|
.hint--medium:after,
|
||||||
|
.hint--large:after {
|
||||||
|
white-space: normal;
|
||||||
|
line-height: 1.4em;
|
||||||
|
word-wrap: break-word; }
|
||||||
|
|
||||||
|
.hint--small:after {
|
||||||
|
width: 80px; }
|
||||||
|
|
||||||
|
.hint--medium:after {
|
||||||
|
width: 150px; }
|
||||||
|
|
||||||
|
.hint--large:after {
|
||||||
|
width: 300px; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* source: hint-theme.scss
|
||||||
|
*
|
||||||
|
* Defines basic theme for tooltips.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
[class*="hint--"] {
|
||||||
|
/**
|
||||||
|
* tooltip body
|
||||||
|
*/ }
|
||||||
|
[class*="hint--"]:after {
|
||||||
|
text-shadow: 0 -1px 0px black;
|
||||||
|
box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.3); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* source: hint-color-types.scss
|
||||||
|
*
|
||||||
|
* Contains tooltips of various types based on color differences.
|
||||||
|
*
|
||||||
|
* Classes added:
|
||||||
|
* 1) hint--error
|
||||||
|
* 2) hint--warning
|
||||||
|
* 3) hint--info
|
||||||
|
* 4) hint--success
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Error
|
||||||
|
*/
|
||||||
|
.hint--error:after {
|
||||||
|
background-color: #b34e4d;
|
||||||
|
text-shadow: 0 -1px 0px #592726; }
|
||||||
|
|
||||||
|
.hint--error.hint--top-left:before {
|
||||||
|
border-top-color: #b34e4d; }
|
||||||
|
|
||||||
|
.hint--error.hint--top-right:before {
|
||||||
|
border-top-color: #b34e4d; }
|
||||||
|
|
||||||
|
.hint--error.hint--top:before {
|
||||||
|
border-top-color: #b34e4d; }
|
||||||
|
|
||||||
|
.hint--error.hint--bottom-left:before {
|
||||||
|
border-bottom-color: #b34e4d; }
|
||||||
|
|
||||||
|
.hint--error.hint--bottom-right:before {
|
||||||
|
border-bottom-color: #b34e4d; }
|
||||||
|
|
||||||
|
.hint--error.hint--bottom:before {
|
||||||
|
border-bottom-color: #b34e4d; }
|
||||||
|
|
||||||
|
.hint--error.hint--left:before {
|
||||||
|
border-left-color: #b34e4d; }
|
||||||
|
|
||||||
|
.hint--error.hint--right:before {
|
||||||
|
border-right-color: #b34e4d; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Warning
|
||||||
|
*/
|
||||||
|
.hint--warning:after {
|
||||||
|
background-color: #c09854;
|
||||||
|
text-shadow: 0 -1px 0px #6c5328; }
|
||||||
|
|
||||||
|
.hint--warning.hint--top-left:before {
|
||||||
|
border-top-color: #c09854; }
|
||||||
|
|
||||||
|
.hint--warning.hint--top-right:before {
|
||||||
|
border-top-color: #c09854; }
|
||||||
|
|
||||||
|
.hint--warning.hint--top:before {
|
||||||
|
border-top-color: #c09854; }
|
||||||
|
|
||||||
|
.hint--warning.hint--bottom-left:before {
|
||||||
|
border-bottom-color: #c09854; }
|
||||||
|
|
||||||
|
.hint--warning.hint--bottom-right:before {
|
||||||
|
border-bottom-color: #c09854; }
|
||||||
|
|
||||||
|
.hint--warning.hint--bottom:before {
|
||||||
|
border-bottom-color: #c09854; }
|
||||||
|
|
||||||
|
.hint--warning.hint--left:before {
|
||||||
|
border-left-color: #c09854; }
|
||||||
|
|
||||||
|
.hint--warning.hint--right:before {
|
||||||
|
border-right-color: #c09854; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Info
|
||||||
|
*/
|
||||||
|
.hint--info:after {
|
||||||
|
background-color: #3986ac;
|
||||||
|
text-shadow: 0 -1px 0px #1a3c4d; }
|
||||||
|
|
||||||
|
.hint--info.hint--top-left:before {
|
||||||
|
border-top-color: #3986ac; }
|
||||||
|
|
||||||
|
.hint--info.hint--top-right:before {
|
||||||
|
border-top-color: #3986ac; }
|
||||||
|
|
||||||
|
.hint--info.hint--top:before {
|
||||||
|
border-top-color: #3986ac; }
|
||||||
|
|
||||||
|
.hint--info.hint--bottom-left:before {
|
||||||
|
border-bottom-color: #3986ac; }
|
||||||
|
|
||||||
|
.hint--info.hint--bottom-right:before {
|
||||||
|
border-bottom-color: #3986ac; }
|
||||||
|
|
||||||
|
.hint--info.hint--bottom:before {
|
||||||
|
border-bottom-color: #3986ac; }
|
||||||
|
|
||||||
|
.hint--info.hint--left:before {
|
||||||
|
border-left-color: #3986ac; }
|
||||||
|
|
||||||
|
.hint--info.hint--right:before {
|
||||||
|
border-right-color: #3986ac; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success
|
||||||
|
*/
|
||||||
|
.hint--success:after {
|
||||||
|
background-color: #458746;
|
||||||
|
text-shadow: 0 -1px 0px #1a321a; }
|
||||||
|
|
||||||
|
.hint--success.hint--top-left:before {
|
||||||
|
border-top-color: #458746; }
|
||||||
|
|
||||||
|
.hint--success.hint--top-right:before {
|
||||||
|
border-top-color: #458746; }
|
||||||
|
|
||||||
|
.hint--success.hint--top:before {
|
||||||
|
border-top-color: #458746; }
|
||||||
|
|
||||||
|
.hint--success.hint--bottom-left:before {
|
||||||
|
border-bottom-color: #458746; }
|
||||||
|
|
||||||
|
.hint--success.hint--bottom-right:before {
|
||||||
|
border-bottom-color: #458746; }
|
||||||
|
|
||||||
|
.hint--success.hint--bottom:before {
|
||||||
|
border-bottom-color: #458746; }
|
||||||
|
|
||||||
|
.hint--success.hint--left:before {
|
||||||
|
border-left-color: #458746; }
|
||||||
|
|
||||||
|
.hint--success.hint--right:before {
|
||||||
|
border-right-color: #458746; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* source: hint-always.scss
|
||||||
|
*
|
||||||
|
* Defines a persisted tooltip which shows always.
|
||||||
|
*
|
||||||
|
* Classes added:
|
||||||
|
* 1) hint--always
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
.hint--always:after, .hint--always:before {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible; }
|
||||||
|
|
||||||
|
.hint--always.hint--top:before {
|
||||||
|
-webkit-transform: translateY(-8px);
|
||||||
|
-moz-transform: translateY(-8px);
|
||||||
|
transform: translateY(-8px); }
|
||||||
|
|
||||||
|
.hint--always.hint--top:after {
|
||||||
|
-webkit-transform: translateX(-50%) translateY(-8px);
|
||||||
|
-moz-transform: translateX(-50%) translateY(-8px);
|
||||||
|
transform: translateX(-50%) translateY(-8px); }
|
||||||
|
|
||||||
|
.hint--always.hint--top-left:before {
|
||||||
|
-webkit-transform: translateY(-8px);
|
||||||
|
-moz-transform: translateY(-8px);
|
||||||
|
transform: translateY(-8px); }
|
||||||
|
|
||||||
|
.hint--always.hint--top-left:after {
|
||||||
|
-webkit-transform: translateX(-100%) translateY(-8px);
|
||||||
|
-moz-transform: translateX(-100%) translateY(-8px);
|
||||||
|
transform: translateX(-100%) translateY(-8px); }
|
||||||
|
|
||||||
|
.hint--always.hint--top-right:before {
|
||||||
|
-webkit-transform: translateY(-8px);
|
||||||
|
-moz-transform: translateY(-8px);
|
||||||
|
transform: translateY(-8px); }
|
||||||
|
|
||||||
|
.hint--always.hint--top-right:after {
|
||||||
|
-webkit-transform: translateY(-8px);
|
||||||
|
-moz-transform: translateY(-8px);
|
||||||
|
transform: translateY(-8px); }
|
||||||
|
|
||||||
|
.hint--always.hint--bottom:before {
|
||||||
|
-webkit-transform: translateY(8px);
|
||||||
|
-moz-transform: translateY(8px);
|
||||||
|
transform: translateY(8px); }
|
||||||
|
|
||||||
|
.hint--always.hint--bottom:after {
|
||||||
|
-webkit-transform: translateX(-50%) translateY(8px);
|
||||||
|
-moz-transform: translateX(-50%) translateY(8px);
|
||||||
|
transform: translateX(-50%) translateY(8px); }
|
||||||
|
|
||||||
|
.hint--always.hint--bottom-left:before {
|
||||||
|
-webkit-transform: translateY(8px);
|
||||||
|
-moz-transform: translateY(8px);
|
||||||
|
transform: translateY(8px); }
|
||||||
|
|
||||||
|
.hint--always.hint--bottom-left:after {
|
||||||
|
-webkit-transform: translateX(-100%) translateY(8px);
|
||||||
|
-moz-transform: translateX(-100%) translateY(8px);
|
||||||
|
transform: translateX(-100%) translateY(8px); }
|
||||||
|
|
||||||
|
.hint--always.hint--bottom-right:before {
|
||||||
|
-webkit-transform: translateY(8px);
|
||||||
|
-moz-transform: translateY(8px);
|
||||||
|
transform: translateY(8px); }
|
||||||
|
|
||||||
|
.hint--always.hint--bottom-right:after {
|
||||||
|
-webkit-transform: translateY(8px);
|
||||||
|
-moz-transform: translateY(8px);
|
||||||
|
transform: translateY(8px); }
|
||||||
|
|
||||||
|
.hint--always.hint--left:before {
|
||||||
|
-webkit-transform: translateX(-8px);
|
||||||
|
-moz-transform: translateX(-8px);
|
||||||
|
transform: translateX(-8px); }
|
||||||
|
|
||||||
|
.hint--always.hint--left:after {
|
||||||
|
-webkit-transform: translateX(-8px);
|
||||||
|
-moz-transform: translateX(-8px);
|
||||||
|
transform: translateX(-8px); }
|
||||||
|
|
||||||
|
.hint--always.hint--right:before {
|
||||||
|
-webkit-transform: translateX(8px);
|
||||||
|
-moz-transform: translateX(8px);
|
||||||
|
transform: translateX(8px); }
|
||||||
|
|
||||||
|
.hint--always.hint--right:after {
|
||||||
|
-webkit-transform: translateX(8px);
|
||||||
|
-moz-transform: translateX(8px);
|
||||||
|
transform: translateX(8px); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* source: hint-rounded.scss
|
||||||
|
*
|
||||||
|
* Defines rounded corner tooltips.
|
||||||
|
*
|
||||||
|
* Classes added:
|
||||||
|
* 1) hint--rounded
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
.hint--rounded:after {
|
||||||
|
border-radius: 4px; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* source: hint-effects.scss
|
||||||
|
*
|
||||||
|
* Defines various transition effects for the tooltips.
|
||||||
|
*
|
||||||
|
* Classes added:
|
||||||
|
* 1) hint--no-animate
|
||||||
|
* 2) hint--bounce
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
.hint--no-animate:before, .hint--no-animate:after {
|
||||||
|
-webkit-transition-duration: 0ms;
|
||||||
|
-moz-transition-duration: 0ms;
|
||||||
|
transition-duration: 0ms; }
|
||||||
|
|
||||||
|
.hint--bounce:before, .hint--bounce:after {
|
||||||
|
-webkit-transition: opacity 0.3s ease, visibility 0.3s ease, -webkit-transform 0.3s cubic-bezier(0.71, 1.7, 0.77, 1.24);
|
||||||
|
-moz-transition: opacity 0.3s ease, visibility 0.3s ease, -moz-transform 0.3s cubic-bezier(0.71, 1.7, 0.77, 1.24);
|
||||||
|
transition: opacity 0.3s ease, visibility 0.3s ease, transform 0.3s cubic-bezier(0.71, 1.7, 0.77, 1.24); }
|
||||||
|
|
||||||
|
.hint--no-shadow:before, .hint--no-shadow:after {
|
||||||
|
text-shadow: initial;
|
||||||
|
box-shadow: initial; }
|
||||||
|
|
||||||
|
.hint--no-arrow:before {
|
||||||
|
display: none; }
|
||||||
5
views/assets/css/hint.min.css
vendored
Normal file
5
views/assets/css/hint.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
3104
views/assets/css/remixicon.css
Normal file
3104
views/assets/css/remixicon.css
Normal file
File diff suppressed because it is too large
Load Diff
BIN
views/assets/fonts/LinBiolinum_RB_G.ttf
Normal file
BIN
views/assets/fonts/LinBiolinum_RB_G.ttf
Normal file
Binary file not shown.
BIN
views/assets/fonts/LinBiolinum_RI_G.ttf
Normal file
BIN
views/assets/fonts/LinBiolinum_RI_G.ttf
Normal file
Binary file not shown.
BIN
views/assets/fonts/LinBiolinum_R_G.ttf
Normal file
BIN
views/assets/fonts/LinBiolinum_R_G.ttf
Normal file
Binary file not shown.
BIN
views/assets/fonts/LinLibertine_DR_G.ttf
Normal file
BIN
views/assets/fonts/LinLibertine_DR_G.ttf
Normal file
Binary file not shown.
BIN
views/assets/fonts/LinLibertine_RBI_G.ttf
Normal file
BIN
views/assets/fonts/LinLibertine_RBI_G.ttf
Normal file
Binary file not shown.
BIN
views/assets/fonts/LinLibertine_RB_G.ttf
Normal file
BIN
views/assets/fonts/LinLibertine_RB_G.ttf
Normal file
Binary file not shown.
BIN
views/assets/fonts/LinLibertine_RI_G.ttf
Normal file
BIN
views/assets/fonts/LinLibertine_RI_G.ttf
Normal file
Binary file not shown.
BIN
views/assets/fonts/LinLibertine_RZI_G.ttf
Normal file
BIN
views/assets/fonts/LinLibertine_RZI_G.ttf
Normal file
Binary file not shown.
BIN
views/assets/fonts/LinLibertine_RZ_G.ttf
Normal file
BIN
views/assets/fonts/LinLibertine_RZ_G.ttf
Normal file
Binary file not shown.
BIN
views/assets/fonts/LinLibertine_R_G.ttf
Normal file
BIN
views/assets/fonts/LinLibertine_R_G.ttf
Normal file
Binary file not shown.
5
views/assets/js/alpine.min.js
vendored
Normal file
5
views/assets/js/alpine.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
97
views/assets/js/class-tools.js
Normal file
97
views/assets/js/class-tools.js
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
(function() {
|
||||||
|
function splitOnWhitespace(trigger) {
|
||||||
|
return trigger.split(/\s+/)
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseClassOperation(trimmedValue) {
|
||||||
|
var split = splitOnWhitespace(trimmedValue)
|
||||||
|
if (split.length > 1) {
|
||||||
|
var operation = split[0]
|
||||||
|
var classDef = split[1].trim()
|
||||||
|
var cssClass
|
||||||
|
var delay
|
||||||
|
if (classDef.indexOf(':') > 0) {
|
||||||
|
var splitCssClass = classDef.split(':')
|
||||||
|
cssClass = splitCssClass[0]
|
||||||
|
delay = htmx.parseInterval(splitCssClass[1])
|
||||||
|
} else {
|
||||||
|
cssClass = classDef
|
||||||
|
delay = 100
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
operation,
|
||||||
|
cssClass,
|
||||||
|
delay
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function performOperation(elt, classOperation, classList, currentRunTime) {
|
||||||
|
setTimeout(function() {
|
||||||
|
elt.classList[classOperation.operation].call(elt.classList, classOperation.cssClass)
|
||||||
|
}, currentRunTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleOperation(elt, classOperation, classList, currentRunTime) {
|
||||||
|
setTimeout(function() {
|
||||||
|
setInterval(function() {
|
||||||
|
elt.classList[classOperation.operation].call(elt.classList, classOperation.cssClass)
|
||||||
|
}, classOperation.delay)
|
||||||
|
}, currentRunTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
function processClassList(elt, classList) {
|
||||||
|
var runs = classList.split('&')
|
||||||
|
for (var i = 0; i < runs.length; i++) {
|
||||||
|
var run = runs[i]
|
||||||
|
var currentRunTime = 0
|
||||||
|
var classOperations = run.split(',')
|
||||||
|
for (var j = 0; j < classOperations.length; j++) {
|
||||||
|
var value = classOperations[j]
|
||||||
|
var trimmedValue = value.trim()
|
||||||
|
var classOperation = parseClassOperation(trimmedValue)
|
||||||
|
if (classOperation) {
|
||||||
|
if (classOperation.operation === 'toggle') {
|
||||||
|
toggleOperation(elt, classOperation, classList, currentRunTime)
|
||||||
|
currentRunTime = currentRunTime + classOperation.delay
|
||||||
|
} else {
|
||||||
|
currentRunTime = currentRunTime + classOperation.delay
|
||||||
|
performOperation(elt, classOperation, classList, currentRunTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function maybeProcessClasses(elt) {
|
||||||
|
if (elt.getAttribute) {
|
||||||
|
var classList = elt.getAttribute('classes') || elt.getAttribute('data-classes')
|
||||||
|
if (classList) {
|
||||||
|
processClassList(elt, classList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
htmx.defineExtension('class-tools', {
|
||||||
|
onEvent: function(name, evt) {
|
||||||
|
if (name === 'htmx:afterProcessNode') {
|
||||||
|
var elt = evt.detail.elt
|
||||||
|
maybeProcessClasses(elt)
|
||||||
|
var classList = elt.getAttribute("apply-parent-classes") || elt.getAttribute("data-apply-parent-classes");
|
||||||
|
if (classList) {
|
||||||
|
var parent = elt.parentElement;
|
||||||
|
parent.removeChild(elt);
|
||||||
|
parent.setAttribute("classes", classList);
|
||||||
|
maybeProcessClasses(parent);
|
||||||
|
} else if (elt.querySelectorAll) {
|
||||||
|
var children = elt.querySelectorAll('[classes], [data-classes]')
|
||||||
|
for (var i = 0; i < children.length; i++) {
|
||||||
|
maybeProcessClasses(children[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})()
|
||||||
96
views/assets/js/client-side-templates.js
Normal file
96
views/assets/js/client-side-templates.js
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
htmx.defineExtension('client-side-templates', {
|
||||||
|
transformResponse: function(text, xhr, elt) {
|
||||||
|
var mustacheTemplate = htmx.closest(elt, '[mustache-template]')
|
||||||
|
if (mustacheTemplate) {
|
||||||
|
var data = JSON.parse(text)
|
||||||
|
var templateId = mustacheTemplate.getAttribute('mustache-template')
|
||||||
|
var template = htmx.find('#' + templateId)
|
||||||
|
if (template) {
|
||||||
|
return Mustache.render(template.innerHTML, data)
|
||||||
|
} else {
|
||||||
|
throw new Error('Unknown mustache template: ' + templateId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var mustacheArrayTemplate = htmx.closest(elt, '[mustache-array-template]')
|
||||||
|
if (mustacheArrayTemplate) {
|
||||||
|
var data = JSON.parse(text)
|
||||||
|
var templateId = mustacheArrayTemplate.getAttribute('mustache-array-template')
|
||||||
|
var template = htmx.find('#' + templateId)
|
||||||
|
if (template) {
|
||||||
|
return Mustache.render(template.innerHTML, { data })
|
||||||
|
} else {
|
||||||
|
throw new Error('Unknown mustache template: ' + templateId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var handlebarsTemplate = htmx.closest(elt, '[handlebars-template]')
|
||||||
|
if (handlebarsTemplate) {
|
||||||
|
var data = JSON.parse(text)
|
||||||
|
var templateId = handlebarsTemplate.getAttribute('handlebars-template')
|
||||||
|
var templateElement = htmx.find('#' + templateId).innerHTML
|
||||||
|
var renderTemplate = Handlebars.compile(templateElement)
|
||||||
|
if (renderTemplate) {
|
||||||
|
return renderTemplate(data)
|
||||||
|
} else {
|
||||||
|
throw new Error('Unknown handlebars template: ' + templateId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var handlebarsArrayTemplate = htmx.closest(elt, '[handlebars-array-template]')
|
||||||
|
if (handlebarsArrayTemplate) {
|
||||||
|
var data = JSON.parse(text)
|
||||||
|
var templateId = handlebarsArrayTemplate.getAttribute('handlebars-array-template')
|
||||||
|
var templateElement = htmx.find('#' + templateId).innerHTML
|
||||||
|
var renderTemplate = Handlebars.compile(templateElement)
|
||||||
|
if (renderTemplate) {
|
||||||
|
return renderTemplate(data)
|
||||||
|
} else {
|
||||||
|
throw new Error('Unknown handlebars template: ' + templateId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var nunjucksTemplate = htmx.closest(elt, '[nunjucks-template]')
|
||||||
|
if (nunjucksTemplate) {
|
||||||
|
var data = JSON.parse(text)
|
||||||
|
var templateName = nunjucksTemplate.getAttribute('nunjucks-template')
|
||||||
|
var template = htmx.find('#' + templateName)
|
||||||
|
if (template) {
|
||||||
|
return nunjucks.renderString(template.innerHTML, data)
|
||||||
|
} else {
|
||||||
|
return nunjucks.render(templateName, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var xsltTemplate = htmx.closest(elt, '[xslt-template]')
|
||||||
|
if (xsltTemplate) {
|
||||||
|
var templateId = xsltTemplate.getAttribute('xslt-template')
|
||||||
|
var template = htmx.find('#' + templateId)
|
||||||
|
if (template) {
|
||||||
|
var content = template.innerHTML
|
||||||
|
? new DOMParser().parseFromString(template.innerHTML, 'application/xml')
|
||||||
|
: template.contentDocument
|
||||||
|
var processor = new XSLTProcessor()
|
||||||
|
processor.importStylesheet(content)
|
||||||
|
var data = new DOMParser().parseFromString(text, 'application/xml')
|
||||||
|
var frag = processor.transformToFragment(data, document)
|
||||||
|
return new XMLSerializer().serializeToString(frag)
|
||||||
|
} else {
|
||||||
|
throw new Error('Unknown XSLT template: ' + templateId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var nunjucksArrayTemplate = htmx.closest(elt, '[nunjucks-array-template]')
|
||||||
|
if (nunjucksArrayTemplate) {
|
||||||
|
var data = JSON.parse(text)
|
||||||
|
var templateName = nunjucksArrayTemplate.getAttribute('nunjucks-array-template')
|
||||||
|
var template = htmx.find('#' + templateName)
|
||||||
|
if (template) {
|
||||||
|
return nunjucks.renderString(template.innerHTML, { data })
|
||||||
|
} else {
|
||||||
|
return nunjucks.render(templateName, { data })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
})
|
||||||
144
views/assets/js/head-support.js
Normal file
144
views/assets/js/head-support.js
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
//==========================================================
|
||||||
|
// head-support.js
|
||||||
|
//
|
||||||
|
// An extension to add head tag merging.
|
||||||
|
//==========================================================
|
||||||
|
(function(){
|
||||||
|
|
||||||
|
var api = null;
|
||||||
|
|
||||||
|
function log() {
|
||||||
|
//console.log(arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeHead(newContent, defaultMergeStrategy) {
|
||||||
|
|
||||||
|
if (newContent && newContent.indexOf('<head') > -1) {
|
||||||
|
const htmlDoc = document.createElement("html");
|
||||||
|
// remove svgs to avoid conflicts
|
||||||
|
var contentWithSvgsRemoved = newContent.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, '');
|
||||||
|
// extract head tag
|
||||||
|
var headTag = contentWithSvgsRemoved.match(/(<head(\s[^>]*>|>)([\s\S]*?)<\/head>)/im);
|
||||||
|
|
||||||
|
// if the head tag exists...
|
||||||
|
if (headTag) {
|
||||||
|
|
||||||
|
var added = []
|
||||||
|
var removed = []
|
||||||
|
var preserved = []
|
||||||
|
var nodesToAppend = []
|
||||||
|
|
||||||
|
htmlDoc.innerHTML = headTag;
|
||||||
|
var newHeadTag = htmlDoc.querySelector("head");
|
||||||
|
var currentHead = document.head;
|
||||||
|
|
||||||
|
if (newHeadTag == null) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// put all new head elements into a Map, by their outerHTML
|
||||||
|
var srcToNewHeadNodes = new Map();
|
||||||
|
for (const newHeadChild of newHeadTag.children) {
|
||||||
|
srcToNewHeadNodes.set(newHeadChild.outerHTML, newHeadChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// determine merge strategy
|
||||||
|
var mergeStrategy = api.getAttributeValue(newHeadTag, "hx-head") || defaultMergeStrategy;
|
||||||
|
|
||||||
|
// get the current head
|
||||||
|
for (const currentHeadElt of currentHead.children) {
|
||||||
|
|
||||||
|
// If the current head element is in the map
|
||||||
|
var inNewContent = srcToNewHeadNodes.has(currentHeadElt.outerHTML);
|
||||||
|
var isReAppended = currentHeadElt.getAttribute("hx-head") === "re-eval";
|
||||||
|
var isPreserved = api.getAttributeValue(currentHeadElt, "hx-preserve") === "true";
|
||||||
|
if (inNewContent || isPreserved) {
|
||||||
|
if (isReAppended) {
|
||||||
|
// remove the current version and let the new version replace it and re-execute
|
||||||
|
removed.push(currentHeadElt);
|
||||||
|
} else {
|
||||||
|
// this element already exists and should not be re-appended, so remove it from
|
||||||
|
// the new content map, preserving it in the DOM
|
||||||
|
srcToNewHeadNodes.delete(currentHeadElt.outerHTML);
|
||||||
|
preserved.push(currentHeadElt);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (mergeStrategy === "append") {
|
||||||
|
// we are appending and this existing element is not new content
|
||||||
|
// so if and only if it is marked for re-append do we do anything
|
||||||
|
if (isReAppended) {
|
||||||
|
removed.push(currentHeadElt);
|
||||||
|
nodesToAppend.push(currentHeadElt);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if this is a merge, we remove this content since it is not in the new head
|
||||||
|
if (api.triggerEvent(document.body, "htmx:removingHeadElement", {headElement: currentHeadElt}) !== false) {
|
||||||
|
removed.push(currentHeadElt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push the tremaining new head elements in the Map into the
|
||||||
|
// nodes to append to the head tag
|
||||||
|
nodesToAppend.push(...srcToNewHeadNodes.values());
|
||||||
|
log("to append: ", nodesToAppend);
|
||||||
|
|
||||||
|
for (const newNode of nodesToAppend) {
|
||||||
|
log("adding: ", newNode);
|
||||||
|
var newElt = document.createRange().createContextualFragment(newNode.outerHTML);
|
||||||
|
log(newElt);
|
||||||
|
if (api.triggerEvent(document.body, "htmx:addingHeadElement", {headElement: newElt}) !== false) {
|
||||||
|
currentHead.appendChild(newElt);
|
||||||
|
added.push(newElt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove all removed elements, after we have appended the new elements to avoid
|
||||||
|
// additional network requests for things like style sheets
|
||||||
|
for (const removedElement of removed) {
|
||||||
|
if (api.triggerEvent(document.body, "htmx:removingHeadElement", {headElement: removedElement}) !== false) {
|
||||||
|
currentHead.removeChild(removedElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
api.triggerEvent(document.body, "htmx:afterHeadMerge", {added: added, kept: preserved, removed: removed});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
htmx.defineExtension("head-support", {
|
||||||
|
init: function(apiRef) {
|
||||||
|
// store a reference to the internal API.
|
||||||
|
api = apiRef;
|
||||||
|
|
||||||
|
htmx.on('htmx:afterSwap', function(evt){
|
||||||
|
let xhr = evt.detail.xhr;
|
||||||
|
if (xhr) {
|
||||||
|
var serverResponse = xhr.response;
|
||||||
|
if (api.triggerEvent(document.body, "htmx:beforeHeadMerge", evt.detail)) {
|
||||||
|
mergeHead(serverResponse, evt.detail.boosted ? "merge" : "append");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
htmx.on('htmx:historyRestore', function(evt){
|
||||||
|
if (api.triggerEvent(document.body, "htmx:beforeHeadMerge", evt.detail)) {
|
||||||
|
if (evt.detail.cacheMiss) {
|
||||||
|
mergeHead(evt.detail.serverResponse, "merge");
|
||||||
|
} else {
|
||||||
|
mergeHead(evt.detail.item.head, "merge");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
htmx.on('htmx:historyItemCreated', function(evt){
|
||||||
|
var historyItem = evt.detail.item;
|
||||||
|
historyItem.head = document.head.outerHTML;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
})()
|
||||||
130
views/assets/js/htmx-response-targets.js
Normal file
130
views/assets/js/htmx-response-targets.js
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
(function(){
|
||||||
|
|
||||||
|
/** @type {import("../htmx").HtmxInternalApi} */
|
||||||
|
var api;
|
||||||
|
|
||||||
|
var attrPrefix = 'hx-target-';
|
||||||
|
|
||||||
|
// IE11 doesn't support string.startsWith
|
||||||
|
function startsWith(str, prefix) {
|
||||||
|
return str.substring(0, prefix.length) === prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {HTMLElement} elt
|
||||||
|
* @param {number} respCode
|
||||||
|
* @returns {HTMLElement | null}
|
||||||
|
*/
|
||||||
|
function getRespCodeTarget(elt, respCodeNumber) {
|
||||||
|
if (!elt || !respCodeNumber) return null;
|
||||||
|
|
||||||
|
var respCode = respCodeNumber.toString();
|
||||||
|
|
||||||
|
// '*' is the original syntax, as the obvious character for a wildcard.
|
||||||
|
// The 'x' alternative was added for maximum compatibility with HTML
|
||||||
|
// templating engines, due to ambiguity around which characters are
|
||||||
|
// supported in HTML attributes.
|
||||||
|
//
|
||||||
|
// Start with the most specific possible attribute and generalize from
|
||||||
|
// there.
|
||||||
|
var attrPossibilities = [
|
||||||
|
respCode,
|
||||||
|
|
||||||
|
respCode.substr(0, 2) + '*',
|
||||||
|
respCode.substr(0, 2) + 'x',
|
||||||
|
|
||||||
|
respCode.substr(0, 1) + '*',
|
||||||
|
respCode.substr(0, 1) + 'x',
|
||||||
|
respCode.substr(0, 1) + '**',
|
||||||
|
respCode.substr(0, 1) + 'xx',
|
||||||
|
|
||||||
|
'*',
|
||||||
|
'x',
|
||||||
|
'***',
|
||||||
|
'xxx',
|
||||||
|
];
|
||||||
|
if (startsWith(respCode, '4') || startsWith(respCode, '5')) {
|
||||||
|
attrPossibilities.push('error');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < attrPossibilities.length; i++) {
|
||||||
|
var attr = attrPrefix + attrPossibilities[i];
|
||||||
|
var attrValue = api.getClosestAttributeValue(elt, attr);
|
||||||
|
if (attrValue) {
|
||||||
|
if (attrValue === "this") {
|
||||||
|
return api.findThisElement(elt, attr);
|
||||||
|
} else {
|
||||||
|
return api.querySelectorExt(elt, attrValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {Event} evt */
|
||||||
|
function handleErrorFlag(evt) {
|
||||||
|
if (evt.detail.isError) {
|
||||||
|
if (htmx.config.responseTargetUnsetsError) {
|
||||||
|
evt.detail.isError = false;
|
||||||
|
}
|
||||||
|
} else if (htmx.config.responseTargetSetsError) {
|
||||||
|
evt.detail.isError = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
htmx.defineExtension('response-targets', {
|
||||||
|
|
||||||
|
/** @param {import("../htmx").HtmxInternalApi} apiRef */
|
||||||
|
init: function (apiRef) {
|
||||||
|
api = apiRef;
|
||||||
|
|
||||||
|
if (htmx.config.responseTargetUnsetsError === undefined) {
|
||||||
|
htmx.config.responseTargetUnsetsError = true;
|
||||||
|
}
|
||||||
|
if (htmx.config.responseTargetSetsError === undefined) {
|
||||||
|
htmx.config.responseTargetSetsError = false;
|
||||||
|
}
|
||||||
|
if (htmx.config.responseTargetPrefersExisting === undefined) {
|
||||||
|
htmx.config.responseTargetPrefersExisting = false;
|
||||||
|
}
|
||||||
|
if (htmx.config.responseTargetPrefersRetargetHeader === undefined) {
|
||||||
|
htmx.config.responseTargetPrefersRetargetHeader = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} name
|
||||||
|
* @param {Event} evt
|
||||||
|
*/
|
||||||
|
onEvent: function (name, evt) {
|
||||||
|
if (name === "htmx:beforeSwap" &&
|
||||||
|
evt.detail.xhr &&
|
||||||
|
evt.detail.xhr.status !== 200) {
|
||||||
|
if (evt.detail.target) {
|
||||||
|
if (htmx.config.responseTargetPrefersExisting) {
|
||||||
|
evt.detail.shouldSwap = true;
|
||||||
|
handleErrorFlag(evt);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (htmx.config.responseTargetPrefersRetargetHeader &&
|
||||||
|
evt.detail.xhr.getAllResponseHeaders().match(/HX-Retarget:/i)) {
|
||||||
|
evt.detail.shouldSwap = true;
|
||||||
|
handleErrorFlag(evt);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!evt.detail.requestConfig) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
var target = getRespCodeTarget(evt.detail.requestConfig.elt, evt.detail.xhr.status);
|
||||||
|
if (target) {
|
||||||
|
handleErrorFlag(evt);
|
||||||
|
evt.detail.shouldSwap = true;
|
||||||
|
evt.detail.target = target;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
1
views/assets/js/htmx.min.js
vendored
Normal file
1
views/assets/js/htmx.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
23
views/assets/js/include-vals.js
Normal file
23
views/assets/js/include-vals.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
(function() {
|
||||||
|
function mergeObjects(obj1, obj2) {
|
||||||
|
for (var key in obj2) {
|
||||||
|
if (obj2.hasOwnProperty(key)) {
|
||||||
|
obj1[key] = obj2[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj1
|
||||||
|
}
|
||||||
|
|
||||||
|
htmx.defineExtension('include-vals', {
|
||||||
|
onEvent: function(name, evt) {
|
||||||
|
if (name === 'htmx:configRequest') {
|
||||||
|
var includeValsElt = htmx.closest(evt.detail.elt, '[include-vals],[data-include-vals]')
|
||||||
|
if (includeValsElt) {
|
||||||
|
var includeVals = includeValsElt.getAttribute('include-vals') || includeValsElt.getAttribute('data-include-vals')
|
||||||
|
var valuesToInclude = eval('({' + includeVals + '})')
|
||||||
|
mergeObjects(evt.detail.parameters, valuesToInclude)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})()
|
||||||
184
views/assets/js/loading-states.js
Normal file
184
views/assets/js/loading-states.js
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
;(function() {
|
||||||
|
const loadingStatesUndoQueue = []
|
||||||
|
|
||||||
|
function loadingStateContainer(target) {
|
||||||
|
return htmx.closest(target, '[data-loading-states]') || document.body
|
||||||
|
}
|
||||||
|
|
||||||
|
function mayProcessUndoCallback(target, callback) {
|
||||||
|
if (document.body.contains(target)) {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mayProcessLoadingStateByPath(elt, requestPath) {
|
||||||
|
const pathElt = htmx.closest(elt, '[data-loading-path]')
|
||||||
|
if (!pathElt) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathElt.getAttribute('data-loading-path') === requestPath
|
||||||
|
}
|
||||||
|
|
||||||
|
function queueLoadingState(sourceElt, targetElt, doCallback, undoCallback) {
|
||||||
|
const delayElt = htmx.closest(sourceElt, '[data-loading-delay]')
|
||||||
|
if (delayElt) {
|
||||||
|
const delayInMilliseconds =
|
||||||
|
delayElt.getAttribute('data-loading-delay') || 200
|
||||||
|
const timeout = setTimeout(function() {
|
||||||
|
doCallback()
|
||||||
|
|
||||||
|
loadingStatesUndoQueue.push(function() {
|
||||||
|
mayProcessUndoCallback(targetElt, undoCallback)
|
||||||
|
})
|
||||||
|
}, delayInMilliseconds)
|
||||||
|
|
||||||
|
loadingStatesUndoQueue.push(function() {
|
||||||
|
mayProcessUndoCallback(targetElt, function() { clearTimeout(timeout) })
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
doCallback()
|
||||||
|
loadingStatesUndoQueue.push(function() {
|
||||||
|
mayProcessUndoCallback(targetElt, undoCallback)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLoadingStateElts(loadingScope, type, path) {
|
||||||
|
return Array.from(htmx.findAll(loadingScope, '[' + type + ']')).filter(
|
||||||
|
function(elt) { return mayProcessLoadingStateByPath(elt, path) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLoadingTarget(elt) {
|
||||||
|
if (elt.getAttribute('data-loading-target')) {
|
||||||
|
return Array.from(
|
||||||
|
htmx.findAll(elt.getAttribute('data-loading-target'))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return [elt]
|
||||||
|
}
|
||||||
|
|
||||||
|
htmx.defineExtension('loading-states', {
|
||||||
|
onEvent: function(name, evt) {
|
||||||
|
if (name === 'htmx:beforeRequest') {
|
||||||
|
const container = loadingStateContainer(evt.target)
|
||||||
|
|
||||||
|
const loadingStateTypes = [
|
||||||
|
'data-loading',
|
||||||
|
'data-loading-class',
|
||||||
|
'data-loading-class-remove',
|
||||||
|
'data-loading-disable',
|
||||||
|
'data-loading-aria-busy'
|
||||||
|
]
|
||||||
|
|
||||||
|
const loadingStateEltsByType = {}
|
||||||
|
|
||||||
|
loadingStateTypes.forEach(function(type) {
|
||||||
|
loadingStateEltsByType[type] = getLoadingStateElts(
|
||||||
|
container,
|
||||||
|
type,
|
||||||
|
evt.detail.pathInfo.requestPath
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
loadingStateEltsByType['data-loading'].forEach(function(sourceElt) {
|
||||||
|
getLoadingTarget(sourceElt).forEach(function(targetElt) {
|
||||||
|
queueLoadingState(
|
||||||
|
sourceElt,
|
||||||
|
targetElt,
|
||||||
|
function() {
|
||||||
|
targetElt.style.display =
|
||||||
|
sourceElt.getAttribute('data-loading') ||
|
||||||
|
'inline-block'
|
||||||
|
},
|
||||||
|
function() { targetElt.style.display = 'none' }
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
loadingStateEltsByType['data-loading-class'].forEach(
|
||||||
|
function(sourceElt) {
|
||||||
|
const classNames = sourceElt
|
||||||
|
.getAttribute('data-loading-class')
|
||||||
|
.split(' ')
|
||||||
|
|
||||||
|
getLoadingTarget(sourceElt).forEach(function(targetElt) {
|
||||||
|
queueLoadingState(
|
||||||
|
sourceElt,
|
||||||
|
targetElt,
|
||||||
|
function() {
|
||||||
|
classNames.forEach(function(className) {
|
||||||
|
targetElt.classList.add(className)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
classNames.forEach(function(className) {
|
||||||
|
targetElt.classList.remove(className)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
loadingStateEltsByType['data-loading-class-remove'].forEach(
|
||||||
|
function(sourceElt) {
|
||||||
|
const classNames = sourceElt
|
||||||
|
.getAttribute('data-loading-class-remove')
|
||||||
|
.split(' ')
|
||||||
|
|
||||||
|
getLoadingTarget(sourceElt).forEach(function(targetElt) {
|
||||||
|
queueLoadingState(
|
||||||
|
sourceElt,
|
||||||
|
targetElt,
|
||||||
|
function() {
|
||||||
|
classNames.forEach(function(className) {
|
||||||
|
targetElt.classList.remove(className)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
classNames.forEach(function(className) {
|
||||||
|
targetElt.classList.add(className)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
loadingStateEltsByType['data-loading-disable'].forEach(
|
||||||
|
function(sourceElt) {
|
||||||
|
getLoadingTarget(sourceElt).forEach(function(targetElt) {
|
||||||
|
queueLoadingState(
|
||||||
|
sourceElt,
|
||||||
|
targetElt,
|
||||||
|
function() { targetElt.disabled = true },
|
||||||
|
function() { targetElt.disabled = false }
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
loadingStateEltsByType['data-loading-aria-busy'].forEach(
|
||||||
|
function(sourceElt) {
|
||||||
|
getLoadingTarget(sourceElt).forEach(function(targetElt) {
|
||||||
|
queueLoadingState(
|
||||||
|
sourceElt,
|
||||||
|
targetElt,
|
||||||
|
function() { targetElt.setAttribute('aria-busy', 'true') },
|
||||||
|
function() { targetElt.removeAttribute('aria-busy') }
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name === 'htmx:beforeOnLoad') {
|
||||||
|
while (loadingStatesUndoQueue.length > 0) {
|
||||||
|
loadingStatesUndoQueue.shift()()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})()
|
||||||
13
views/assets/js/mark.min.js
vendored
Normal file
13
views/assets/js/mark.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
44
views/assets/js/multi-swap.js
Normal file
44
views/assets/js/multi-swap.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
(function() {
|
||||||
|
/** @type {import("../htmx").HtmxInternalApi} */
|
||||||
|
var api
|
||||||
|
|
||||||
|
htmx.defineExtension('multi-swap', {
|
||||||
|
init: function(apiRef) {
|
||||||
|
api = apiRef
|
||||||
|
},
|
||||||
|
isInlineSwap: function(swapStyle) {
|
||||||
|
return swapStyle.indexOf('multi:') === 0
|
||||||
|
},
|
||||||
|
handleSwap: function(swapStyle, target, fragment, settleInfo) {
|
||||||
|
if (swapStyle.indexOf('multi:') === 0) {
|
||||||
|
var selectorToSwapStyle = {}
|
||||||
|
var elements = swapStyle.replace(/^multi\s*:\s*/, '').split(/\s*,\s*/)
|
||||||
|
|
||||||
|
elements.forEach(function(element) {
|
||||||
|
var split = element.split(/\s*:\s*/)
|
||||||
|
var elementSelector = split[0]
|
||||||
|
var elementSwapStyle = typeof (split[1]) !== 'undefined' ? split[1] : 'innerHTML'
|
||||||
|
|
||||||
|
if (elementSelector.charAt(0) !== '#') {
|
||||||
|
console.error("HTMX multi-swap: unsupported selector '" + elementSelector + "'. Only ID selectors starting with '#' are supported.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
selectorToSwapStyle[elementSelector] = elementSwapStyle
|
||||||
|
})
|
||||||
|
|
||||||
|
for (var selector in selectorToSwapStyle) {
|
||||||
|
var swapStyle = selectorToSwapStyle[selector]
|
||||||
|
var elementToSwap = fragment.querySelector(selector)
|
||||||
|
if (elementToSwap) {
|
||||||
|
api.oobSwap(swapStyle, elementToSwap, settleInfo)
|
||||||
|
} else {
|
||||||
|
console.warn("HTMX multi-swap: selector '" + selector + "' not found in source content.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})()
|
||||||
11
views/assets/js/path-params.js
Normal file
11
views/assets/js/path-params.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
htmx.defineExtension('path-params', {
|
||||||
|
onEvent: function(name, evt) {
|
||||||
|
if (name === 'htmx:configRequest') {
|
||||||
|
evt.detail.path = evt.detail.path.replace(/{([^}]+)}/g, function(_, param) {
|
||||||
|
var val = evt.detail.parameters[param]
|
||||||
|
delete evt.detail.parameters[param]
|
||||||
|
return val === undefined ? '{' + param + '}' : encodeURIComponent(val)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
388
views/assets/js/preload.js
Normal file
388
views/assets/js/preload.js
Normal file
@@ -0,0 +1,388 @@
|
|||||||
|
(function() {
|
||||||
|
/**
|
||||||
|
* This adds the "preload" extension to htmx. The extension will
|
||||||
|
* preload the targets of elements with "preload" attribute if:
|
||||||
|
* - they also have `href`, `hx-get` or `data-hx-get` attributes
|
||||||
|
* - they are radio buttons, checkboxes, select elements and submit
|
||||||
|
* buttons of forms with `method="get"` or `hx-get` attributes
|
||||||
|
* The extension relies on browser cache and for it to work
|
||||||
|
* server response must include `Cache-Control` header
|
||||||
|
* e.g. `Cache-Control: private, max-age=60`.
|
||||||
|
* For more details @see https://htmx.org/extensions/preload/
|
||||||
|
*/
|
||||||
|
|
||||||
|
htmx.defineExtension('preload', {
|
||||||
|
onEvent: function(name, event) {
|
||||||
|
// Process preload attributes on `htmx:afterProcessNode`
|
||||||
|
if (name === 'htmx:afterProcessNode') {
|
||||||
|
// Initialize all nodes with `preload` attribute
|
||||||
|
const parent = event.target || event.detail.elt;
|
||||||
|
const preloadNodes = [
|
||||||
|
...parent.hasAttribute("preload") ? [parent] : [],
|
||||||
|
...parent.querySelectorAll("[preload]")]
|
||||||
|
preloadNodes.forEach(function(node) {
|
||||||
|
// Initialize the node with the `preload` attribute
|
||||||
|
init(node)
|
||||||
|
|
||||||
|
// Initialize all child elements which has
|
||||||
|
// `href`, `hx-get` or `data-hx-get` attributes
|
||||||
|
node.querySelectorAll('[href],[hx-get],[data-hx-get]').forEach(init)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intercept HTMX preload requests on `htmx:beforeRequest` and
|
||||||
|
// send them as XHR requests instead to avoid side-effects,
|
||||||
|
// such as showing loading indicators while preloading data.
|
||||||
|
if (name === 'htmx:beforeRequest') {
|
||||||
|
const requestHeaders = event.detail.requestConfig.headers
|
||||||
|
if (!("HX-Preloaded" in requestHeaders
|
||||||
|
&& requestHeaders["HX-Preloaded"] === "true")) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault()
|
||||||
|
// Reuse XHR created by HTMX with replaced callbacks
|
||||||
|
const xhr = event.detail.xhr
|
||||||
|
xhr.onload = function() {
|
||||||
|
processResponse(event.detail.elt, xhr.responseText)
|
||||||
|
}
|
||||||
|
xhr.onerror = null
|
||||||
|
xhr.onabort = null
|
||||||
|
xhr.ontimeout = null
|
||||||
|
xhr.send()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize `node`, set up event handlers based on own or inherited
|
||||||
|
* `preload` attributes and set `node.preloadState` to `READY`.
|
||||||
|
*
|
||||||
|
* `node.preloadState` can have these values:
|
||||||
|
* - `READY` - event handlers have been set up and node is ready to preload
|
||||||
|
* - `TIMEOUT` - a triggering event has been fired, but `node` is not
|
||||||
|
* yet being loaded because some time need to pass first e.g. user
|
||||||
|
* has to keep hovering over an element for 100ms for preload to start
|
||||||
|
* - `LOADING` means that `node` is in the process of being preloaded
|
||||||
|
* - `DONE` means that the preloading process is complete and `node`
|
||||||
|
* doesn't need a repeated preload (indicated by preload="always")
|
||||||
|
* @param {Node} node
|
||||||
|
*/
|
||||||
|
function init(node) {
|
||||||
|
// Guarantee that each node is initialized only once
|
||||||
|
if (node.preloadState !== undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isValidNodeForPreloading(node)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize form element preloading
|
||||||
|
if (node instanceof HTMLFormElement) {
|
||||||
|
const form = node
|
||||||
|
// Only initialize forms with `method="get"` or `hx-get` attributes
|
||||||
|
if (!((form.hasAttribute('method') && form.method === 'get')
|
||||||
|
|| form.hasAttribute('hx-get') || form.hasAttribute('hx-data-get'))) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (let i = 0; i < form.elements.length; i++) {
|
||||||
|
const element = form.elements.item(i);
|
||||||
|
init(element);
|
||||||
|
element.labels.forEach(init);
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process node configuration from preload attribute
|
||||||
|
let preloadAttr = getClosestAttribute(node, 'preload');
|
||||||
|
node.preloadAlways = preloadAttr && preloadAttr.includes('always');
|
||||||
|
if (node.preloadAlways) {
|
||||||
|
preloadAttr = preloadAttr.replace('always', '').trim();
|
||||||
|
}
|
||||||
|
let triggerEventName = preloadAttr || 'mousedown';
|
||||||
|
|
||||||
|
// Set up event handlers listening for triggering events
|
||||||
|
const needsTimeout = triggerEventName === 'mouseover'
|
||||||
|
node.addEventListener(triggerEventName, getEventHandler(node, needsTimeout))
|
||||||
|
|
||||||
|
// Add `touchstart` listener for touchscreen support
|
||||||
|
// if `mousedown` or `mouseover` is used
|
||||||
|
if (triggerEventName === 'mousedown' || triggerEventName === 'mouseover') {
|
||||||
|
node.addEventListener('touchstart', getEventHandler(node))
|
||||||
|
}
|
||||||
|
|
||||||
|
// If `mouseover` is used, set up `mouseout` listener,
|
||||||
|
// which will abort preloading if user moves mouse outside
|
||||||
|
// the element in less than 100ms after hovering over it
|
||||||
|
if (triggerEventName === 'mouseover') {
|
||||||
|
node.addEventListener('mouseout', function(evt) {
|
||||||
|
if ((evt.target === node) && (node.preloadState === 'TIMEOUT')) {
|
||||||
|
node.preloadState = 'READY'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark the node as ready to be preloaded
|
||||||
|
node.preloadState = 'READY'
|
||||||
|
|
||||||
|
// This event can be used to load content immediately
|
||||||
|
htmx.trigger(node, 'preload:init')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return event handler which can be called by event listener to start
|
||||||
|
* the preloading process of `node` with or without a timeout
|
||||||
|
* @param {Node} node
|
||||||
|
* @param {boolean=} needsTimeout
|
||||||
|
* @returns {function(): void}
|
||||||
|
*/
|
||||||
|
function getEventHandler(node, needsTimeout = false) {
|
||||||
|
return function() {
|
||||||
|
// Do not preload uninitialized nodes, nodes which are in process
|
||||||
|
// of being preloaded or have been preloaded and don't need repeat
|
||||||
|
if (node.preloadState !== 'READY') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needsTimeout) {
|
||||||
|
node.preloadState = 'TIMEOUT'
|
||||||
|
const timeoutMs = 100
|
||||||
|
window.setTimeout(function() {
|
||||||
|
if (node.preloadState === 'TIMEOUT') {
|
||||||
|
node.preloadState = 'READY'
|
||||||
|
load(node)
|
||||||
|
}
|
||||||
|
}, timeoutMs)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
load(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preload the target of node, which can be:
|
||||||
|
* - hx-get or data-hx-get attribute
|
||||||
|
* - href or form action attribute
|
||||||
|
* @param {Node} node
|
||||||
|
*/
|
||||||
|
function load(node) {
|
||||||
|
// Do not preload uninitialized nodes, nodes which are in process
|
||||||
|
// of being preloaded or have been preloaded and don't need repeat
|
||||||
|
if (node.preloadState !== 'READY') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
node.preloadState = 'LOADING'
|
||||||
|
|
||||||
|
// Load nodes with `hx-get` or `data-hx-get` attribute
|
||||||
|
// Forms don't reach this because only their elements are initialized
|
||||||
|
const hxGet = node.getAttribute('hx-get') || node.getAttribute('data-hx-get')
|
||||||
|
if (hxGet) {
|
||||||
|
sendHxGetRequest(hxGet, node);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load nodes with `href` attribute
|
||||||
|
const hxBoost = getClosestAttribute(node, "hx-boost") === "true"
|
||||||
|
if (node.hasAttribute('href')) {
|
||||||
|
const url = node.getAttribute('href');
|
||||||
|
if (hxBoost) {
|
||||||
|
sendHxGetRequest(url, node);
|
||||||
|
} else {
|
||||||
|
sendXmlGetRequest(url, node);
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load form elements
|
||||||
|
if (isPreloadableFormElement(node)) {
|
||||||
|
const url = node.form.getAttribute('action')
|
||||||
|
|| node.form.getAttribute('hx-get')
|
||||||
|
|| node.form.getAttribute('data-hx-get');
|
||||||
|
const formData = htmx.values(node.form);
|
||||||
|
const isStandardForm = !(node.form.getAttribute('hx-get')
|
||||||
|
|| node.form.getAttribute('data-hx-get')
|
||||||
|
|| hxBoost);
|
||||||
|
const sendGetRequest = isStandardForm ? sendXmlGetRequest : sendHxGetRequest
|
||||||
|
|
||||||
|
// submit button
|
||||||
|
if (node.type === 'submit') {
|
||||||
|
sendGetRequest(url, node.form, formData)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// select
|
||||||
|
const inputName = node.name || node.control.name;
|
||||||
|
if (node.tagName === 'SELECT') {
|
||||||
|
Array.from(node.options).forEach(option => {
|
||||||
|
if (option.selected) return;
|
||||||
|
formData.set(inputName, option.value);
|
||||||
|
const formDataOrdered = forceFormDataInOrder(node.form, formData);
|
||||||
|
sendGetRequest(url, node.form, formDataOrdered)
|
||||||
|
});
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// radio and checkbox
|
||||||
|
const inputType = node.getAttribute("type") || node.control.getAttribute("type");
|
||||||
|
const nodeValue = node.value || node.control?.value;
|
||||||
|
if (inputType === 'radio') {
|
||||||
|
formData.set(inputName, nodeValue);
|
||||||
|
} else if (inputType === 'checkbox'){
|
||||||
|
const inputValues = formData.getAll(inputName);
|
||||||
|
if (inputValues.includes(nodeValue)) {
|
||||||
|
formData[inputName] = inputValues.filter(value => value !== nodeValue);
|
||||||
|
} else {
|
||||||
|
formData.append(inputName, nodeValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const formDataOrdered = forceFormDataInOrder(node.form, formData);
|
||||||
|
sendGetRequest(url, node.form, formDataOrdered)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force formData values to be in the order of form elements.
|
||||||
|
* This is useful to apply after alternating formData values
|
||||||
|
* and before passing them to a HTTP request because cache is
|
||||||
|
* sensitive to GET parameter order e.g., cached `/link?a=1&b=2`
|
||||||
|
* will not be used for `/link?b=2&a=1`.
|
||||||
|
* @param {HTMLFormElement} form
|
||||||
|
* @param {FormData} formData
|
||||||
|
* @returns {FormData}
|
||||||
|
*/
|
||||||
|
function forceFormDataInOrder(form, formData) {
|
||||||
|
const formElements = form.elements;
|
||||||
|
const orderedFormData = new FormData();
|
||||||
|
for(let i = 0; i < formElements.length; i++) {
|
||||||
|
const element = formElements.item(i);
|
||||||
|
if (formData.has(element.name) && element.tagName === 'SELECT') {
|
||||||
|
orderedFormData.append(
|
||||||
|
element.name, formData.get(element.name));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (formData.has(element.name) && formData.getAll(element.name)
|
||||||
|
.includes(element.value)) {
|
||||||
|
orderedFormData.append(element.name, element.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return orderedFormData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send GET request with `hx-request` headers as if `sourceNode`
|
||||||
|
* target was loaded. Send alternated values if `formData` is set.
|
||||||
|
*
|
||||||
|
* Note that this request is intercepted and sent as XMLHttpRequest.
|
||||||
|
* It is necessary to use `htmx.ajax` to acquire correct headers which
|
||||||
|
* HTMX and extensions add based on `sourceNode`. But it cannot be used
|
||||||
|
* to perform the request due to side-effects e.g. loading indicators.
|
||||||
|
* @param {string} url
|
||||||
|
* @param {Node} sourceNode
|
||||||
|
* @param {FormData=} formData
|
||||||
|
*/
|
||||||
|
function sendHxGetRequest(url, sourceNode, formData = undefined) {
|
||||||
|
htmx.ajax('GET', url, {
|
||||||
|
source: sourceNode,
|
||||||
|
values: formData,
|
||||||
|
headers: {"HX-Preloaded": "true"}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send XML GET request to `url`. Send `formData` as URL params if set.
|
||||||
|
* @param {string} url
|
||||||
|
* @param {Node} sourceNode
|
||||||
|
* @param {FormData=} formData
|
||||||
|
*/
|
||||||
|
function sendXmlGetRequest(url, sourceNode, formData = undefined) {
|
||||||
|
const xhr = new XMLHttpRequest()
|
||||||
|
if (formData) {
|
||||||
|
url += '?' + new URLSearchParams(formData.entries()).toString()
|
||||||
|
}
|
||||||
|
xhr.open('GET', url);
|
||||||
|
xhr.setRequestHeader("HX-Preloaded", "true")
|
||||||
|
xhr.onload = function() { processResponse(sourceNode, xhr.responseText) }
|
||||||
|
xhr.send()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process request response by marking node `DONE` to prevent repeated
|
||||||
|
* requests, except if preload attribute contains `always`,
|
||||||
|
* and load linked resources (e.g. images) returned in the response
|
||||||
|
* if `preload-images` attribute is `true`
|
||||||
|
* @param {Node} node
|
||||||
|
* @param {string} responseText
|
||||||
|
*/
|
||||||
|
function processResponse(node, responseText) {
|
||||||
|
node.preloadState = node.preloadAlways ? 'READY' : 'DONE'
|
||||||
|
|
||||||
|
if (getClosestAttribute(node, 'preload-images') === 'true') {
|
||||||
|
// Load linked resources
|
||||||
|
document.createElement('div').innerHTML = responseText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets attribute value from node or one of its parents
|
||||||
|
* @param {Node} node
|
||||||
|
* @param {string} attribute
|
||||||
|
* @returns { string | undefined }
|
||||||
|
*/
|
||||||
|
function getClosestAttribute(node, attribute) {
|
||||||
|
if (node == undefined) { return undefined }
|
||||||
|
return node.getAttribute(attribute)
|
||||||
|
|| node.getAttribute('data-' + attribute)
|
||||||
|
|| getClosestAttribute(node.parentElement, attribute)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if node is valid for preloading and should be
|
||||||
|
* initialized by setting up event listeners and handlers
|
||||||
|
* @param {Node} node
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function isValidNodeForPreloading(node) {
|
||||||
|
// Add listeners only to nodes which include "GET" transactions
|
||||||
|
// or preloadable "GET" form elements
|
||||||
|
const getReqAttrs = ['href', 'hx-get', 'data-hx-get'];
|
||||||
|
const includesGetRequest = node => getReqAttrs.some(a => node.hasAttribute(a))
|
||||||
|
|| node.method === 'get';
|
||||||
|
const isPreloadableGetFormElement = node.form instanceof HTMLFormElement
|
||||||
|
&& includesGetRequest(node.form)
|
||||||
|
&& isPreloadableFormElement(node)
|
||||||
|
if (!includesGetRequest(node) && !isPreloadableGetFormElement) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't preload <input> elements contained in <label>
|
||||||
|
// to prevent sending two requests. Interaction on <input> in a
|
||||||
|
// <label><input></input></label> situation activates <label> too.
|
||||||
|
if (node instanceof HTMLInputElement && node.closest('label')) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if node is a form element which can be preloaded,
|
||||||
|
* i.e., `radio`, `checkbox`, `select` or `submit` button
|
||||||
|
* or a `label` of a form element which can be preloaded.
|
||||||
|
* @param {Node} node
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function isPreloadableFormElement(node) {
|
||||||
|
if (node instanceof HTMLInputElement || node instanceof HTMLButtonElement) {
|
||||||
|
const type = node.getAttribute('type');
|
||||||
|
return ['checkbox', 'radio', 'submit'].includes(type);
|
||||||
|
}
|
||||||
|
if (node instanceof HTMLLabelElement) {
|
||||||
|
return node.control && isPreloadableFormElement(node.control);
|
||||||
|
}
|
||||||
|
return node instanceof HTMLSelectElement;
|
||||||
|
}
|
||||||
|
})()
|
||||||
471
views/assets/js/ws.js
Normal file
471
views/assets/js/ws.js
Normal file
@@ -0,0 +1,471 @@
|
|||||||
|
/*
|
||||||
|
WebSockets Extension
|
||||||
|
============================
|
||||||
|
This extension adds support for WebSockets to htmx. See /www/extensions/ws.md for usage instructions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
/** @type {import("../htmx").HtmxInternalApi} */
|
||||||
|
var api
|
||||||
|
|
||||||
|
htmx.defineExtension('ws', {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* init is called once, when this extension is first registered.
|
||||||
|
* @param {import("../htmx").HtmxInternalApi} apiRef
|
||||||
|
*/
|
||||||
|
init: function(apiRef) {
|
||||||
|
// Store reference to internal API
|
||||||
|
api = apiRef
|
||||||
|
|
||||||
|
// Default function for creating new EventSource objects
|
||||||
|
if (!htmx.createWebSocket) {
|
||||||
|
htmx.createWebSocket = createWebSocket
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default setting for reconnect delay
|
||||||
|
if (!htmx.config.wsReconnectDelay) {
|
||||||
|
htmx.config.wsReconnectDelay = 'full-jitter'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* onEvent handles all events passed to this extension.
|
||||||
|
*
|
||||||
|
* @param {string} name
|
||||||
|
* @param {Event} evt
|
||||||
|
*/
|
||||||
|
onEvent: function(name, evt) {
|
||||||
|
var parent = evt.target || evt.detail.elt
|
||||||
|
switch (name) {
|
||||||
|
// Try to close the socket when elements are removed
|
||||||
|
case 'htmx:beforeCleanupElement':
|
||||||
|
|
||||||
|
var internalData = api.getInternalData(parent)
|
||||||
|
|
||||||
|
if (internalData.webSocket) {
|
||||||
|
internalData.webSocket.close()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
|
||||||
|
// Try to create websockets when elements are processed
|
||||||
|
case 'htmx:beforeProcessNode':
|
||||||
|
|
||||||
|
forEach(queryAttributeOnThisOrChildren(parent, 'ws-connect'), function(child) {
|
||||||
|
ensureWebSocket(child)
|
||||||
|
})
|
||||||
|
forEach(queryAttributeOnThisOrChildren(parent, 'ws-send'), function(child) {
|
||||||
|
ensureWebSocketSend(child)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function splitOnWhitespace(trigger) {
|
||||||
|
return trigger.trim().split(/\s+/)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLegacyWebsocketURL(elt) {
|
||||||
|
var legacySSEValue = api.getAttributeValue(elt, 'hx-ws')
|
||||||
|
if (legacySSEValue) {
|
||||||
|
var values = splitOnWhitespace(legacySSEValue)
|
||||||
|
for (var i = 0; i < values.length; i++) {
|
||||||
|
var value = values[i].split(/:(.+)/)
|
||||||
|
if (value[0] === 'connect') {
|
||||||
|
return value[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ensureWebSocket creates a new WebSocket on the designated element, using
|
||||||
|
* the element's "ws-connect" attribute.
|
||||||
|
* @param {HTMLElement} socketElt
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function ensureWebSocket(socketElt) {
|
||||||
|
// If the element containing the WebSocket connection no longer exists, then
|
||||||
|
// do not connect/reconnect the WebSocket.
|
||||||
|
if (!api.bodyContains(socketElt)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the source straight from the element's value
|
||||||
|
var wssSource = api.getAttributeValue(socketElt, 'ws-connect')
|
||||||
|
|
||||||
|
if (wssSource == null || wssSource === '') {
|
||||||
|
var legacySource = getLegacyWebsocketURL(socketElt)
|
||||||
|
if (legacySource == null) {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
wssSource = legacySource
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Guarantee that the wssSource value is a fully qualified URL
|
||||||
|
if (wssSource.indexOf('/') === 0) {
|
||||||
|
var base_part = location.hostname + (location.port ? ':' + location.port : '')
|
||||||
|
if (location.protocol === 'https:') {
|
||||||
|
wssSource = 'wss://' + base_part + wssSource
|
||||||
|
} else if (location.protocol === 'http:') {
|
||||||
|
wssSource = 'ws://' + base_part + wssSource
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var socketWrapper = createWebsocketWrapper(socketElt, function() {
|
||||||
|
return htmx.createWebSocket(wssSource)
|
||||||
|
})
|
||||||
|
|
||||||
|
socketWrapper.addEventListener('message', function(event) {
|
||||||
|
if (maybeCloseWebSocketSource(socketElt)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var response = event.data
|
||||||
|
if (!api.triggerEvent(socketElt, 'htmx:wsBeforeMessage', {
|
||||||
|
message: response,
|
||||||
|
socketWrapper: socketWrapper.publicInterface
|
||||||
|
})) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
api.withExtensions(socketElt, function(extension) {
|
||||||
|
response = extension.transformResponse(response, null, socketElt)
|
||||||
|
})
|
||||||
|
|
||||||
|
var settleInfo = api.makeSettleInfo(socketElt)
|
||||||
|
var fragment = api.makeFragment(response)
|
||||||
|
|
||||||
|
if (fragment.children.length) {
|
||||||
|
var children = Array.from(fragment.children)
|
||||||
|
for (var i = 0; i < children.length; i++) {
|
||||||
|
api.oobSwap(api.getAttributeValue(children[i], 'hx-swap-oob') || 'true', children[i], settleInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
api.settleImmediately(settleInfo.tasks)
|
||||||
|
api.triggerEvent(socketElt, 'htmx:wsAfterMessage', { message: response, socketWrapper: socketWrapper.publicInterface })
|
||||||
|
})
|
||||||
|
|
||||||
|
// Put the WebSocket into the HTML Element's custom data.
|
||||||
|
api.getInternalData(socketElt).webSocket = socketWrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} WebSocketWrapper
|
||||||
|
* @property {WebSocket} socket
|
||||||
|
* @property {Array<{message: string, sendElt: Element}>} messageQueue
|
||||||
|
* @property {number} retryCount
|
||||||
|
* @property {(message: string, sendElt: Element) => void} sendImmediately sendImmediately sends message regardless of websocket connection state
|
||||||
|
* @property {(message: string, sendElt: Element) => void} send
|
||||||
|
* @property {(event: string, handler: Function) => void} addEventListener
|
||||||
|
* @property {() => void} handleQueuedMessages
|
||||||
|
* @property {() => void} init
|
||||||
|
* @property {() => void} close
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param socketElt
|
||||||
|
* @param socketFunc
|
||||||
|
* @returns {WebSocketWrapper}
|
||||||
|
*/
|
||||||
|
function createWebsocketWrapper(socketElt, socketFunc) {
|
||||||
|
var wrapper = {
|
||||||
|
socket: null,
|
||||||
|
messageQueue: [],
|
||||||
|
retryCount: 0,
|
||||||
|
|
||||||
|
/** @type {Object<string, Function[]>} */
|
||||||
|
events: {},
|
||||||
|
|
||||||
|
addEventListener: function(event, handler) {
|
||||||
|
if (this.socket) {
|
||||||
|
this.socket.addEventListener(event, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.events[event]) {
|
||||||
|
this.events[event] = []
|
||||||
|
}
|
||||||
|
|
||||||
|
this.events[event].push(handler)
|
||||||
|
},
|
||||||
|
|
||||||
|
sendImmediately: function(message, sendElt) {
|
||||||
|
if (!this.socket) {
|
||||||
|
api.triggerErrorEvent()
|
||||||
|
}
|
||||||
|
if (!sendElt || api.triggerEvent(sendElt, 'htmx:wsBeforeSend', {
|
||||||
|
message,
|
||||||
|
socketWrapper: this.publicInterface
|
||||||
|
})) {
|
||||||
|
this.socket.send(message)
|
||||||
|
sendElt && api.triggerEvent(sendElt, 'htmx:wsAfterSend', {
|
||||||
|
message,
|
||||||
|
socketWrapper: this.publicInterface
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
send: function(message, sendElt) {
|
||||||
|
if (this.socket.readyState !== this.socket.OPEN) {
|
||||||
|
this.messageQueue.push({ message, sendElt })
|
||||||
|
} else {
|
||||||
|
this.sendImmediately(message, sendElt)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handleQueuedMessages: function() {
|
||||||
|
while (this.messageQueue.length > 0) {
|
||||||
|
var queuedItem = this.messageQueue[0]
|
||||||
|
if (this.socket.readyState === this.socket.OPEN) {
|
||||||
|
this.sendImmediately(queuedItem.message, queuedItem.sendElt)
|
||||||
|
this.messageQueue.shift()
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
init: function() {
|
||||||
|
if (this.socket && this.socket.readyState === this.socket.OPEN) {
|
||||||
|
// Close discarded socket
|
||||||
|
this.socket.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new WebSocket and event handlers
|
||||||
|
/** @type {WebSocket} */
|
||||||
|
var socket = socketFunc()
|
||||||
|
|
||||||
|
// The event.type detail is added for interface conformance with the
|
||||||
|
// other two lifecycle events (open and close) so a single handler method
|
||||||
|
// can handle them polymorphically, if required.
|
||||||
|
api.triggerEvent(socketElt, 'htmx:wsConnecting', { event: { type: 'connecting' } })
|
||||||
|
|
||||||
|
this.socket = socket
|
||||||
|
|
||||||
|
socket.onopen = function(e) {
|
||||||
|
wrapper.retryCount = 0
|
||||||
|
api.triggerEvent(socketElt, 'htmx:wsOpen', { event: e, socketWrapper: wrapper.publicInterface })
|
||||||
|
wrapper.handleQueuedMessages()
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.onclose = function(e) {
|
||||||
|
// If socket should not be connected, stop further attempts to establish connection
|
||||||
|
// If Abnormal Closure/Service Restart/Try Again Later, then set a timer to reconnect after a pause.
|
||||||
|
if (!maybeCloseWebSocketSource(socketElt) && [1006, 1012, 1013].indexOf(e.code) >= 0) {
|
||||||
|
var delay = getWebSocketReconnectDelay(wrapper.retryCount)
|
||||||
|
setTimeout(function() {
|
||||||
|
wrapper.retryCount += 1
|
||||||
|
wrapper.init()
|
||||||
|
}, delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify client code that connection has been closed. Client code can inspect `event` field
|
||||||
|
// to determine whether closure has been valid or abnormal
|
||||||
|
api.triggerEvent(socketElt, 'htmx:wsClose', { event: e, socketWrapper: wrapper.publicInterface })
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.onerror = function(e) {
|
||||||
|
api.triggerErrorEvent(socketElt, 'htmx:wsError', { error: e, socketWrapper: wrapper })
|
||||||
|
maybeCloseWebSocketSource(socketElt)
|
||||||
|
}
|
||||||
|
|
||||||
|
var events = this.events
|
||||||
|
Object.keys(events).forEach(function(k) {
|
||||||
|
events[k].forEach(function(e) {
|
||||||
|
socket.addEventListener(k, e)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
close: function() {
|
||||||
|
this.socket.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapper.init()
|
||||||
|
|
||||||
|
wrapper.publicInterface = {
|
||||||
|
send: wrapper.send.bind(wrapper),
|
||||||
|
sendImmediately: wrapper.sendImmediately.bind(wrapper),
|
||||||
|
queue: wrapper.messageQueue
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ensureWebSocketSend attaches trigger handles to elements with
|
||||||
|
* "ws-send" attribute
|
||||||
|
* @param {HTMLElement} elt
|
||||||
|
*/
|
||||||
|
function ensureWebSocketSend(elt) {
|
||||||
|
var legacyAttribute = api.getAttributeValue(elt, 'hx-ws')
|
||||||
|
if (legacyAttribute && legacyAttribute !== 'send') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var webSocketParent = api.getClosestMatch(elt, hasWebSocket)
|
||||||
|
processWebSocketSend(webSocketParent, elt)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* hasWebSocket function checks if a node has webSocket instance attached
|
||||||
|
* @param {HTMLElement} node
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function hasWebSocket(node) {
|
||||||
|
return api.getInternalData(node).webSocket != null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* processWebSocketSend adds event listeners to the <form> element so that
|
||||||
|
* messages can be sent to the WebSocket server when the form is submitted.
|
||||||
|
* @param {HTMLElement} socketElt
|
||||||
|
* @param {HTMLElement} sendElt
|
||||||
|
*/
|
||||||
|
function processWebSocketSend(socketElt, sendElt) {
|
||||||
|
var nodeData = api.getInternalData(sendElt)
|
||||||
|
var triggerSpecs = api.getTriggerSpecs(sendElt)
|
||||||
|
triggerSpecs.forEach(function(ts) {
|
||||||
|
api.addTriggerHandler(sendElt, ts, nodeData, function(elt, evt) {
|
||||||
|
if (maybeCloseWebSocketSource(socketElt)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {WebSocketWrapper} */
|
||||||
|
var socketWrapper = api.getInternalData(socketElt).webSocket
|
||||||
|
var headers = api.getHeaders(sendElt, api.getTarget(sendElt))
|
||||||
|
var results = api.getInputValues(sendElt, 'post')
|
||||||
|
var errors = results.errors
|
||||||
|
var rawParameters = Object.assign({}, results.values)
|
||||||
|
var expressionVars = api.getExpressionVars(sendElt)
|
||||||
|
var allParameters = api.mergeObjects(rawParameters, expressionVars)
|
||||||
|
var filteredParameters = api.filterValues(allParameters, sendElt)
|
||||||
|
|
||||||
|
var sendConfig = {
|
||||||
|
parameters: filteredParameters,
|
||||||
|
unfilteredParameters: allParameters,
|
||||||
|
headers,
|
||||||
|
errors,
|
||||||
|
|
||||||
|
triggeringEvent: evt,
|
||||||
|
messageBody: undefined,
|
||||||
|
socketWrapper: socketWrapper.publicInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!api.triggerEvent(elt, 'htmx:wsConfigSend', sendConfig)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors && errors.length > 0) {
|
||||||
|
api.triggerEvent(elt, 'htmx:validation:halted', errors)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var body = sendConfig.messageBody
|
||||||
|
if (body === undefined) {
|
||||||
|
var toSend = Object.assign({}, sendConfig.parameters)
|
||||||
|
if (sendConfig.headers) { toSend.HEADERS = headers }
|
||||||
|
body = JSON.stringify(toSend)
|
||||||
|
}
|
||||||
|
|
||||||
|
socketWrapper.send(body, elt)
|
||||||
|
|
||||||
|
if (evt && api.shouldCancel(evt, elt)) {
|
||||||
|
evt.preventDefault()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* getWebSocketReconnectDelay is the default easing function for WebSocket reconnects.
|
||||||
|
* @param {number} retryCount // The number of retries that have already taken place
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
function getWebSocketReconnectDelay(retryCount) {
|
||||||
|
/** @type {"full-jitter" | ((retryCount:number) => number)} */
|
||||||
|
var delay = htmx.config.wsReconnectDelay
|
||||||
|
if (typeof delay === 'function') {
|
||||||
|
return delay(retryCount)
|
||||||
|
}
|
||||||
|
if (delay === 'full-jitter') {
|
||||||
|
var exp = Math.min(retryCount, 6)
|
||||||
|
var maxDelay = 1000 * Math.pow(2, exp)
|
||||||
|
return maxDelay * Math.random()
|
||||||
|
}
|
||||||
|
|
||||||
|
logError('htmx.config.wsReconnectDelay must either be a function or the string "full-jitter"')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* maybeCloseWebSocketSource checks to the if the element that created the WebSocket
|
||||||
|
* still exists in the DOM. If NOT, then the WebSocket is closed and this function
|
||||||
|
* returns TRUE. If the element DOES EXIST, then no action is taken, and this function
|
||||||
|
* returns FALSE.
|
||||||
|
*
|
||||||
|
* @param {*} elt
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function maybeCloseWebSocketSource(elt) {
|
||||||
|
if (!api.bodyContains(elt)) {
|
||||||
|
var internalData = api.getInternalData(elt)
|
||||||
|
if (internalData.webSocket) {
|
||||||
|
internalData.webSocket.close()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* createWebSocket is the default method for creating new WebSocket objects.
|
||||||
|
* it is hoisted into htmx.createWebSocket to be overridden by the user, if needed.
|
||||||
|
*
|
||||||
|
* @param {string} url
|
||||||
|
* @returns WebSocket
|
||||||
|
*/
|
||||||
|
function createWebSocket(url) {
|
||||||
|
var sock = new WebSocket(url, [])
|
||||||
|
sock.binaryType = htmx.config.wsBinaryType
|
||||||
|
return sock
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* queryAttributeOnThisOrChildren returns all nodes that contain the requested attributeName, INCLUDING THE PROVIDED ROOT ELEMENT.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} elt
|
||||||
|
* @param {string} attributeName
|
||||||
|
*/
|
||||||
|
function queryAttributeOnThisOrChildren(elt, attributeName) {
|
||||||
|
var result = []
|
||||||
|
|
||||||
|
// If the parent element also contains the requested attribute, then add it to the results too.
|
||||||
|
if (api.hasAttribute(elt, attributeName) || api.hasAttribute(elt, 'hx-ws')) {
|
||||||
|
result.push(elt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search all child nodes that match the requested attribute
|
||||||
|
elt.querySelectorAll('[' + attributeName + '], [data-' + attributeName + '], [data-hx-ws], [hx-ws]').forEach(function(node) {
|
||||||
|
result.push(node)
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @param {T[]} arr
|
||||||
|
* @param {(T) => void} func
|
||||||
|
*/
|
||||||
|
function forEach(arr, func) {
|
||||||
|
if (arr) {
|
||||||
|
for (var i = 0; i < arr.length; i++) {
|
||||||
|
func(arr[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})()
|
||||||
BIN
views/assets/logo/dev_favicon.png
Normal file
BIN
views/assets/logo/dev_favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
BIN
views/assets/logo/favicon.png
Normal file
BIN
views/assets/logo/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
762
views/assets/scripts.js
Normal file
762
views/assets/scripts.js
Normal file
@@ -0,0 +1,762 @@
|
|||||||
|
var x = (r) => {
|
||||||
|
throw TypeError(r);
|
||||||
|
};
|
||||||
|
var _ = (r, t, e) => t.has(r) || x("Cannot " + e);
|
||||||
|
var c = (r, t, e) => (_(r, t, "read from private field"), e ? e.call(r) : t.get(r)), n = (r, t, e) => t.has(r) ? x("Cannot add the same private member more than once") : t instanceof WeakSet ? t.add(r) : t.set(r, e), u = (r, t, e, i) => (_(r, t, "write to private field"), i ? i.call(r, e) : t.set(r, e), e), g = (r, t, e) => (_(r, t, "access private method"), e);
|
||||||
|
const S = "script[xslt-onload]", w = "xslt-template", k = "xslt-transformed", M = "filter-list", m = "filter-list-list", C = "filter-list-item", I = "filter-list-input", y = "filter-list-searchable", B = "scroll-button", q = "tool-tip", P = "abbrev-tooltips", H = "int-link", R = "popup-image", $ = "tab-list", N = "filter-pill", F = "image-reel";
|
||||||
|
var d, b, E;
|
||||||
|
class V {
|
||||||
|
constructor() {
|
||||||
|
n(this, b);
|
||||||
|
n(this, d);
|
||||||
|
u(this, d, /* @__PURE__ */ new Map());
|
||||||
|
}
|
||||||
|
setup() {
|
||||||
|
let t = htmx.findAll(S);
|
||||||
|
for (let e of t)
|
||||||
|
g(this, b, E).call(this, e);
|
||||||
|
}
|
||||||
|
hookupHTMX() {
|
||||||
|
htmx.on("htmx:load", (t) => {
|
||||||
|
this.setup();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d = new WeakMap(), b = new WeakSet(), E = function(t) {
|
||||||
|
if (t.getAttribute(k) === "true" || !t.hasAttribute(w))
|
||||||
|
return;
|
||||||
|
let e = "#" + t.getAttribute(w), i = c(this, d).get(e);
|
||||||
|
if (!i) {
|
||||||
|
let h = htmx.find(e);
|
||||||
|
if (h) {
|
||||||
|
let A = h.innerHTML ? new DOMParser().parseFromString(h.innerHTML, "application/xml") : h.contentDocument;
|
||||||
|
i = new XSLTProcessor(), i.importStylesheet(A), c(this, d).set(e, i);
|
||||||
|
} else
|
||||||
|
throw new Error("Unknown XSLT template: " + e);
|
||||||
|
}
|
||||||
|
let s = new DOMParser().parseFromString(t.innerHTML, "application/xml"), a = i.transformToFragment(s, document), l = new XMLSerializer().serializeToString(a);
|
||||||
|
t.outerHTML = l;
|
||||||
|
};
|
||||||
|
class O extends HTMLElement {
|
||||||
|
constructor() {
|
||||||
|
super(), this._value = "", this.render();
|
||||||
|
}
|
||||||
|
static get observedAttributes() {
|
||||||
|
return ["data-text", "data-queryparam", "data-value"];
|
||||||
|
}
|
||||||
|
set value(t) {
|
||||||
|
this.setAttribute("data-value", t);
|
||||||
|
}
|
||||||
|
get value() {
|
||||||
|
return this.getAttribute("data-value") || "";
|
||||||
|
}
|
||||||
|
set text(t) {
|
||||||
|
this.setAttribute("data-text", t);
|
||||||
|
}
|
||||||
|
get text() {
|
||||||
|
return this.getAttribute("data-text") || "";
|
||||||
|
}
|
||||||
|
set queryparam(t) {
|
||||||
|
this.setAttribute("data-queryparam", t);
|
||||||
|
}
|
||||||
|
get queryparam() {
|
||||||
|
return this.getAttribute("data-queryparam") || "";
|
||||||
|
}
|
||||||
|
connectedCallback() {
|
||||||
|
this._filter = this.text, this._queryparam = this.queryparam, this.render(), htmx.process(this);
|
||||||
|
}
|
||||||
|
attributeChangedCallback(t, e, i) {
|
||||||
|
e !== i && (t === "data-text" && (this._filter = i), t === "data-queryparam" && (this._queryparam = i), t === "data-value" && (this._value = i), this.render());
|
||||||
|
}
|
||||||
|
getURL() {
|
||||||
|
if (this._queryparam) {
|
||||||
|
let t = new URL(window.location), e = new URLSearchParams(t.search);
|
||||||
|
return e.delete(this._queryparam), e.delete("page"), t.search = e.toString(), t.toString();
|
||||||
|
}
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
this.innerHTML = `
|
||||||
|
<a href="${this.getURL()}" class="!no-underline block text-base" hx-target="#searchresults" hx-select="#searchresults" hx-indicator="body" hx-swap="outerHTML show:window:top">
|
||||||
|
<div class="flex flex-row filter-pill rounded-lg bg-orange-100 hover:saturate-50 px-2.5">
|
||||||
|
${this.renderIcon()}
|
||||||
|
<div class="flex flex-row filter-pill-label-value !items-baseline text-slate-700">
|
||||||
|
<div class="filter-pill-label font-bold mr-1.5 align-baseline">${this.text}</div>
|
||||||
|
${this.renderValue()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
renderIcon() {
|
||||||
|
return this.value === "true" || this.value === "false" ? `
|
||||||
|
<div href="${this.getURL()}" class="filter-pill-close no-underline font-bold mr-1 text-orange-900 hover:text-orange-800">
|
||||||
|
<i class="ri-close-circle-line"></i>
|
||||||
|
</div>
|
||||||
|
` : `<div
|
||||||
|
href="${this.getURL()}"
|
||||||
|
class="filter-pill-close no-underline font-bold mr-1 text-orange-900 hover:text-orange-800">
|
||||||
|
<i class="ri-arrow-left-s-line"></i>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
renderValue() {
|
||||||
|
return this.value === "true" || this.value === "false" ? "" : `
|
||||||
|
<div class="filter-pill-value">${this.value}</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var o, T, f, L;
|
||||||
|
class U extends HTMLElement {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
n(this, f);
|
||||||
|
n(this, o, !1);
|
||||||
|
n(this, T, "");
|
||||||
|
this._items = [], this._url = "", this._filterstart = !1, this._placeholder = "Liste filtern...", this._queryparam = "", this._startparams = null, this.render();
|
||||||
|
}
|
||||||
|
static get observedAttributes() {
|
||||||
|
return ["data-url"];
|
||||||
|
}
|
||||||
|
set items(e) {
|
||||||
|
Array.isArray(e) && (this._items = e, this.render());
|
||||||
|
}
|
||||||
|
get items() {
|
||||||
|
return this._items;
|
||||||
|
}
|
||||||
|
connectedCallback() {
|
||||||
|
this._url = this.getAttribute("data-url") || "./", this._filterstart = this.getAttribute("data-filterstart") === "true", this._placeholder = this.getAttribute("data-placeholder") || "Liste filtern...", this._queryparam = this.getAttribute("data-queryparam") || "", this._queryparam, this._filterstart && u(this, o, !0), this.addEventListener("input", this.onInput.bind(this)), this.addEventListener("keydown", this.onEnter.bind(this)), this.addEventListener("focusin", this.onGainFocus.bind(this)), this.addEventListener("focusout", this.onLoseFocus.bind(this));
|
||||||
|
}
|
||||||
|
attributeChangedCallback(e, i, s) {
|
||||||
|
e === "data-url" && i !== s && (this._url = s, this.render()), e === "data-filterstart" && i !== s && (this._filterstart = s === "true", this.render()), e === "data-placeholder" && i !== s && (this._placeholder = s, this.render()), e === "data-queryparam" && i !== s && (this._queryparam = s, this.render());
|
||||||
|
}
|
||||||
|
onInput(e) {
|
||||||
|
e.target && e.target.tagName.toLowerCase() === "input" && (this._filter = e.target.value, this.renderList());
|
||||||
|
}
|
||||||
|
onGainFocus(e) {
|
||||||
|
e.target && e.target.tagName.toLowerCase() === "input" && (u(this, o, !1), this.renderList());
|
||||||
|
}
|
||||||
|
onLoseFocus(e) {
|
||||||
|
let i = this.querySelector("input");
|
||||||
|
if (e.target && e.target === i) {
|
||||||
|
if (relatedElement = e.relatedTarget, relatedElement && this.contains(relatedElement))
|
||||||
|
return;
|
||||||
|
i.value = "", this._filter = "", this._filterstart && u(this, o, !0), this.renderList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onEnter(e) {
|
||||||
|
if (e.target && e.target.tagName.toLowerCase() === "input" && e.key === "Enter") {
|
||||||
|
e.preventDefault();
|
||||||
|
const i = this.querySelector("a");
|
||||||
|
i && i.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mark() {
|
||||||
|
if (typeof Mark != "function")
|
||||||
|
return;
|
||||||
|
let e = this.querySelector("#" + m);
|
||||||
|
if (!e)
|
||||||
|
return;
|
||||||
|
let i = new Mark(e.querySelectorAll("." + y));
|
||||||
|
this._filter && i.mark(this._filter, {
|
||||||
|
separateWordSearch: !0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// INFO: allows for setting a custom HREF of the list item
|
||||||
|
// The function takes the item as parameter fn(item) and should return a string.
|
||||||
|
setHREFFunc(e) {
|
||||||
|
this.getHREF = e, this.render();
|
||||||
|
}
|
||||||
|
// INFO: allows for setting a custom link text of the list item
|
||||||
|
// The function takes the item as parameter fn(item) and should return a string or
|
||||||
|
// an HTML template literal.
|
||||||
|
setLinkTextFunc(e) {
|
||||||
|
this.getLinkText = e, this.render();
|
||||||
|
}
|
||||||
|
// INFO: allows for setting the text that will be filtered for.
|
||||||
|
// The function takes the item as parameter fn(item) and should return a string.
|
||||||
|
setSearchTextFunc(e) {
|
||||||
|
this.getSearchText = e, this.render();
|
||||||
|
}
|
||||||
|
getHREF(e) {
|
||||||
|
if (e) {
|
||||||
|
if (!e.id)
|
||||||
|
return "";
|
||||||
|
} else return "";
|
||||||
|
return e.id;
|
||||||
|
}
|
||||||
|
getHREFEncoded(e) {
|
||||||
|
return encodeURIComponent(this.getHREF(e));
|
||||||
|
}
|
||||||
|
getSearchText(e) {
|
||||||
|
if (e) {
|
||||||
|
if (!e.name)
|
||||||
|
return "";
|
||||||
|
} else return "";
|
||||||
|
return e.name;
|
||||||
|
}
|
||||||
|
getLinkText(e) {
|
||||||
|
let i = this.getSearchText(e);
|
||||||
|
return i === "" ? "" : `<span class="${y}">${i}</span>`;
|
||||||
|
}
|
||||||
|
getURL(e) {
|
||||||
|
if (this._queryparam) {
|
||||||
|
let i = new URL(window.location), s = new URLSearchParams(i.search);
|
||||||
|
return s.set(this._queryparam, this.getHREF(e)), s.delete("page"), i.search = s.toString(), i.toString();
|
||||||
|
}
|
||||||
|
return this._url + this.getHREFEncoded(e);
|
||||||
|
}
|
||||||
|
renderList() {
|
||||||
|
let e = this.querySelector("#" + m);
|
||||||
|
e && (e.outerHTML = this.List()), this.mark();
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
this.innerHTML = `
|
||||||
|
<div class="font-serif text-base shadow-inner border border-stone-100">
|
||||||
|
${this.Input()}
|
||||||
|
${this.List()}
|
||||||
|
</div>
|
||||||
|
`, htmx && htmx.process(this);
|
||||||
|
}
|
||||||
|
ActiveDot(e) {
|
||||||
|
return g(this, f, L).call(this, e), "";
|
||||||
|
}
|
||||||
|
NoItems(e) {
|
||||||
|
return e.length === 0 ? '<div class="px-2 py-0.5 italic text-gray-500">Keine Einträge gefunden</div>' : "";
|
||||||
|
}
|
||||||
|
Input() {
|
||||||
|
return `
|
||||||
|
<div class="flex w-full py-0.5 border-b border-zinc-600 bg-stone-50">
|
||||||
|
<i class="ri-arrow-right-s-line pl-2"></i>
|
||||||
|
<div class="grow">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="${this._placeholder}"
|
||||||
|
class="${I} w-full placeholder:italic px-2 py-0.5" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
List() {
|
||||||
|
let e = this._items;
|
||||||
|
if (this._filter)
|
||||||
|
if (this._filterstart)
|
||||||
|
e = this._items.filter((i) => this.getSearchText(i).toLowerCase().startsWith(this._filter.toLowerCase()));
|
||||||
|
else {
|
||||||
|
let i = this._filter.split(" ");
|
||||||
|
e = this._items.filter((s) => i.every((a) => this.getSearchText(s).toLowerCase().includes(a.toLowerCase())));
|
||||||
|
}
|
||||||
|
return `
|
||||||
|
<div id="${m}" class="${m} pt-1 max-h-60 overflow-auto bg-stone-50 ${c(this, o) ? "hidden" : ""}">
|
||||||
|
${e.map(
|
||||||
|
(i, s) => `
|
||||||
|
<a
|
||||||
|
href="${this.getURL(i)}"
|
||||||
|
hx-indicator="body"
|
||||||
|
hx-swap="outerHTML show:none"
|
||||||
|
hx-select="main"
|
||||||
|
hx-target="main"
|
||||||
|
class="${C} block px-2.5 py-0.5 hover:bg-slate-200 no-underline ${s % 2 === 0 ? "bg-stone-100" : "bg-stone-50"}"
|
||||||
|
${g(this, f, L).call(this, i) ? 'aria-current="page"' : ""}>
|
||||||
|
${this.ActiveDot(i)}
|
||||||
|
${this.getLinkText(i)}
|
||||||
|
</a>
|
||||||
|
`
|
||||||
|
).join("")}
|
||||||
|
${this.NoItems(e)}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o = new WeakMap(), T = new WeakMap(), f = new WeakSet(), L = function(e) {
|
||||||
|
if (!e)
|
||||||
|
return !1;
|
||||||
|
let i = this.getHREF(e);
|
||||||
|
return i === "" ? !1 : this._queryparam && (new URLSearchParams(window.location.search).get(this._queryparam) || "") === i ? !0 : !!window.location.href.endsWith(i);
|
||||||
|
};
|
||||||
|
class D extends HTMLElement {
|
||||||
|
constructor() {
|
||||||
|
super(), this.handleScroll = this.handleScroll.bind(this), this.scrollToTop = this.scrollToTop.bind(this);
|
||||||
|
}
|
||||||
|
connectedCallback() {
|
||||||
|
this.innerHTML = `
|
||||||
|
<button
|
||||||
|
class="
|
||||||
|
scroll-to-top
|
||||||
|
fixed bottom-5 right-5
|
||||||
|
hidden
|
||||||
|
bg-gray-800 text-white
|
||||||
|
p-2
|
||||||
|
rounded-md
|
||||||
|
cursor-pointer
|
||||||
|
text-2xl
|
||||||
|
hover:opacity-80
|
||||||
|
transition-opacity
|
||||||
|
border-0
|
||||||
|
"
|
||||||
|
aria-label="Scroll to top"
|
||||||
|
>
|
||||||
|
<i class="ri-arrow-up-double-line"></i>
|
||||||
|
</button>
|
||||||
|
`, this._button = this.querySelector(".scroll-to-top"), window.addEventListener("scroll", this.handleScroll), this._button.addEventListener("click", this.scrollToTop);
|
||||||
|
}
|
||||||
|
disconnectedCallback() {
|
||||||
|
window.removeEventListener("scroll", this.handleScroll), this._button.removeEventListener("click", this.scrollToTop);
|
||||||
|
}
|
||||||
|
handleScroll() {
|
||||||
|
(window.scrollY || document.documentElement.scrollTop) > 300 ? this._button.classList.remove("hidden") : this._button.classList.add("hidden");
|
||||||
|
}
|
||||||
|
scrollToTop() {
|
||||||
|
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class z extends HTMLElement {
|
||||||
|
static get observedAttributes() {
|
||||||
|
return ["position", "timeout"];
|
||||||
|
}
|
||||||
|
constructor() {
|
||||||
|
super(), this._tooltipBox = null, this._timeout = 200, this._hideTimeout = null, this._hiddenTimeout = null;
|
||||||
|
}
|
||||||
|
connectedCallback() {
|
||||||
|
this.classList.add(
|
||||||
|
"w-full",
|
||||||
|
"h-full",
|
||||||
|
"relative",
|
||||||
|
"block",
|
||||||
|
"leading-none",
|
||||||
|
"[&>*]:leading-normal"
|
||||||
|
);
|
||||||
|
const t = this.querySelector(".data-tip"), e = t ? t.innerHTML : "Tooltip";
|
||||||
|
t && t.classList.add("hidden"), this._tooltipBox = document.createElement("div"), this._tooltipBox.innerHTML = e, this._tooltipBox.className = [
|
||||||
|
"opacity-0",
|
||||||
|
"hidden",
|
||||||
|
"absolute",
|
||||||
|
"px-2",
|
||||||
|
"py-1",
|
||||||
|
"text-sm",
|
||||||
|
"text-white",
|
||||||
|
"bg-gray-900",
|
||||||
|
"rounded",
|
||||||
|
"shadow",
|
||||||
|
"z-10",
|
||||||
|
"whitespace-nowrap",
|
||||||
|
"transition-all",
|
||||||
|
"duration-200",
|
||||||
|
"font-sans"
|
||||||
|
].join(" "), this.appendChild(this._tooltipBox), this._updatePosition(), this.addEventListener("mouseenter", () => this._showTooltip()), this.addEventListener("mouseleave", () => this._hideTooltip());
|
||||||
|
}
|
||||||
|
attributeChangedCallback(t, e, i) {
|
||||||
|
t === "position" && this._tooltipBox && this._updatePosition(), t === "timeout" && i && (this._timeout = parseInt(i) || 200);
|
||||||
|
}
|
||||||
|
_showTooltip() {
|
||||||
|
clearTimeout(this._hideTimeout), clearTimeout(this._hiddenTimeout), this._tooltipBox.classList.remove("hidden"), setTimeout(() => {
|
||||||
|
this._tooltipBox.classList.remove("opacity-0"), this._tooltipBox.classList.add("opacity-100");
|
||||||
|
}, 16);
|
||||||
|
}
|
||||||
|
_hideTooltip() {
|
||||||
|
this._hideTimeout = setTimeout(() => {
|
||||||
|
this._tooltipBox.classList.remove("opacity-100"), this._tooltipBox.classList.add("opacity-0"), this._hiddenTimeout = setTimeout(() => {
|
||||||
|
this._tooltipBox.classList.add("hidden");
|
||||||
|
}, this._timeout + 100);
|
||||||
|
}, this._timeout);
|
||||||
|
}
|
||||||
|
_updatePosition() {
|
||||||
|
switch (this._tooltipBox.classList.remove(
|
||||||
|
"bottom-full",
|
||||||
|
"left-1/2",
|
||||||
|
"-translate-x-1/2",
|
||||||
|
"mb-2",
|
||||||
|
// top
|
||||||
|
"top-full",
|
||||||
|
"mt-2",
|
||||||
|
// bottom
|
||||||
|
"right-full",
|
||||||
|
"-translate-y-1/2",
|
||||||
|
"mr-2",
|
||||||
|
"top-1/2",
|
||||||
|
// left
|
||||||
|
"left-full",
|
||||||
|
"ml-2"
|
||||||
|
// right
|
||||||
|
), this.getAttribute("position") || "top") {
|
||||||
|
case "bottom":
|
||||||
|
this._tooltipBox.classList.add(
|
||||||
|
"top-full",
|
||||||
|
"left-1/2",
|
||||||
|
"transform",
|
||||||
|
"-translate-x-1/2",
|
||||||
|
"mt-0.5"
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "left":
|
||||||
|
this._tooltipBox.classList.add(
|
||||||
|
"right-full",
|
||||||
|
"top-1/2",
|
||||||
|
"transform",
|
||||||
|
"-translate-y-1/2",
|
||||||
|
"mr-0.5"
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "right":
|
||||||
|
this._tooltipBox.classList.add(
|
||||||
|
"left-full",
|
||||||
|
"top-1/2",
|
||||||
|
"transform",
|
||||||
|
"-translate-y-1/2",
|
||||||
|
"ml-0.5"
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "top":
|
||||||
|
default:
|
||||||
|
this._tooltipBox.classList.add(
|
||||||
|
"bottom-full",
|
||||||
|
"left-1/2",
|
||||||
|
"transform",
|
||||||
|
"-translate-x-1/2",
|
||||||
|
"mb-0.5"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class G extends HTMLElement {
|
||||||
|
constructor() {
|
||||||
|
super(), this.overlay = null, this._preview = null, this._description = null, this._imageURL = "", this._hideDLButton = !1;
|
||||||
|
}
|
||||||
|
connectedCallback() {
|
||||||
|
this.classList.add("cursor-pointer"), this.classList.add("select-none"), this._imageURL = this.getAttribute("data-image-url") || "", this._hideDLButton = this.getAttribute("data-hide-dl-button") || !1, this._preview = this.querySelector("img"), this._description = this.querySelector(".image-description"), this._preview && this._preview.addEventListener("click", () => {
|
||||||
|
this.showOverlay();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
disconnectedCallback() {
|
||||||
|
this.overlay && this.overlay.parentNode && this.overlay.parentNode.removeChild(this.overlay);
|
||||||
|
}
|
||||||
|
showOverlay() {
|
||||||
|
this.overlay = document.createElement("div"), this.overlay.classList.add(
|
||||||
|
"fixed",
|
||||||
|
"inset-0",
|
||||||
|
"z-50",
|
||||||
|
"bg-black/70",
|
||||||
|
"flex",
|
||||||
|
"items-center",
|
||||||
|
"justify-center",
|
||||||
|
"p-4"
|
||||||
|
), this.overlay.innerHTML = `
|
||||||
|
<div class="relative w-max max-w-dvw max-h-dvh shadow-lg flex flex-col items-center justify-center gap-4">
|
||||||
|
<div>
|
||||||
|
<div class="absolute -right-16 text-white text-4xl flex flex-col">
|
||||||
|
<button class="hover:text-gray-300 cursor-pointer focus:outline-none" aria-label="Close popup">
|
||||||
|
<i class="ri-close-fill text-4xl"></i>
|
||||||
|
</button>
|
||||||
|
${this.downloadButton()}
|
||||||
|
</div>
|
||||||
|
<img
|
||||||
|
src="${this._imageURL}"
|
||||||
|
alt="Popup Image"
|
||||||
|
class="full max-h-[80vh] max-w-[80vw] object-contain block relative ${this.descriptionImgClass()}"
|
||||||
|
/>
|
||||||
|
${this.description()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
const t = this.overlay.querySelector("button");
|
||||||
|
t && t.addEventListener("click", () => {
|
||||||
|
this.hideOverlay();
|
||||||
|
}), this.overlay.addEventListener("click", (e) => {
|
||||||
|
e.target === this.overlay && this.hideOverlay();
|
||||||
|
}), document.body.appendChild(this.overlay);
|
||||||
|
}
|
||||||
|
descriptionImgClass() {
|
||||||
|
return this.description ? "" : "0";
|
||||||
|
}
|
||||||
|
description() {
|
||||||
|
return this._description ? `
|
||||||
|
<div class="font-serif text-left description-content mt-3 text-slate-900 ">
|
||||||
|
<div class="max-w-[80ch] hyphens-auto px-6 py-2 bg-stone-50 shadow-lg">
|
||||||
|
${this._description.innerHTML}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
` : "";
|
||||||
|
}
|
||||||
|
downloadButton() {
|
||||||
|
return this._hideDLButton ? "" : `
|
||||||
|
<tool-tip position="right">
|
||||||
|
<a href="${this._imageURL}" target="_blank" class="text-white no-underline hover:text-gray-300"><i class="ri-file-download-line"></i></a>
|
||||||
|
<div class="data-tip">Bild herunterladen</div>
|
||||||
|
</tool-tip>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
hideOverlay() {
|
||||||
|
this.overlay.parentNode.removeChild(this.overlay), this.overlay = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class j extends HTMLElement {
|
||||||
|
static get observedAttributes() {
|
||||||
|
}
|
||||||
|
constructor() {
|
||||||
|
super(), this._showall = !1, this.shown = -1, this._headings = [], this._contents = [];
|
||||||
|
}
|
||||||
|
connectedCallback() {
|
||||||
|
this._headings = Array.from(this.querySelectorAll(".tab-list-head")), this._contents = Array.from(this.querySelectorAll(".tab-list-panel")), this.hookupEvtHandlers(), this.hideDependent(), this._headings.length === 1 && this.expand(0);
|
||||||
|
}
|
||||||
|
expand(t) {
|
||||||
|
t < 0 || t >= this._headings.length || (this.shown = t, this._contents.forEach((e, i) => {
|
||||||
|
i === t ? (e.classList.remove("hidden"), this._headings[i].setAttribute("aria-pressed", "true")) : (e.classList.add("hidden"), this._headings[i].setAttribute("aria-pressed", "false"));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
hookupEvtHandlers() {
|
||||||
|
for (let t of this._headings)
|
||||||
|
t.addEventListener("click", this.handleTabClick.bind(this)), t.classList.add("cursor-pointer"), t.classList.add("select-none"), t.setAttribute("role", "button"), t.setAttribute("aria-pressed", "false"), t.setAttribute("tabindex", "0");
|
||||||
|
for (let t of this._contents)
|
||||||
|
t.classList.add("hidden");
|
||||||
|
}
|
||||||
|
restore() {
|
||||||
|
for (let t of this._headings)
|
||||||
|
t.classList.add("cursor-pointer"), t.classList.add("select-none"), t.setAttribute("role", "button"), t.setAttribute("aria-pressed", "false"), t.setAttribute("tabindex", "0"), t.classList.remove("pointer-events-none"), t.classList.remove("!text-slate-900");
|
||||||
|
for (let t of this._contents)
|
||||||
|
t.classList.add("hidden");
|
||||||
|
}
|
||||||
|
disable() {
|
||||||
|
for (let t of this._headings)
|
||||||
|
t.classList.remove("cursor-pointer"), t.classList.remove("select-none"), t.removeAttribute("role"), t.removeAttribute("aria-pressed"), t.removeAttribute("tabindex"), t.classList.add("pointer-events-none"), t.classList.add("!text-slate-900");
|
||||||
|
}
|
||||||
|
showAll() {
|
||||||
|
this._showall = !0, this.shown = -1, this.disable(), this._contents.forEach((t, e) => {
|
||||||
|
t.classList.remove("hidden");
|
||||||
|
let i = this._headings[e], s = i.querySelectorAll(".show-opened");
|
||||||
|
for (let l of s)
|
||||||
|
l.classList.add("hidden");
|
||||||
|
let a = i.querySelectorAll(".show-closed");
|
||||||
|
for (let l of a)
|
||||||
|
l.classList.add("hidden");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
default() {
|
||||||
|
this._showall = !1, this.restore(), this.hideDependent();
|
||||||
|
}
|
||||||
|
hideDependent() {
|
||||||
|
if (this.shown < 0)
|
||||||
|
for (const t of this._headings)
|
||||||
|
this._hideAllDep(t, !1);
|
||||||
|
else
|
||||||
|
this._headings.forEach((t, e) => {
|
||||||
|
this._hideAllDep(t, e === this.shown);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_hideAllDep(t, e) {
|
||||||
|
const i = t.querySelectorAll(".show-closed");
|
||||||
|
for (let a of i)
|
||||||
|
e ? a.classList.add("hidden") : a.classList.remove("hidden");
|
||||||
|
const s = Array.from(t.querySelectorAll(".show-opened"));
|
||||||
|
for (let a of s)
|
||||||
|
e ? a.classList.remove("hidden") : a.classList.add("hidden");
|
||||||
|
}
|
||||||
|
handleTabClick(t) {
|
||||||
|
if (!t.target) {
|
||||||
|
console.warn("Invalid event target");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const e = this.findParentWithClass(t.target, "tab-list-head");
|
||||||
|
if (!e) {
|
||||||
|
console.warn("No parent found with class 'tab-list-head'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const i = this._headings.indexOf(e);
|
||||||
|
i === this.shown ? (this._contents[i].classList.toggle("hidden"), this._headings[i].setAttribute("aria-pressed", "false"), this.shown = -1) : this.expand(i), this.hideDependent();
|
||||||
|
}
|
||||||
|
findParentWithClass(t, e) {
|
||||||
|
for (; t; ) {
|
||||||
|
if (t.classList && t.classList.contains(e))
|
||||||
|
return t;
|
||||||
|
t = t.parentElement;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class p extends HTMLElement {
|
||||||
|
static get observedAttributes() {
|
||||||
|
return ["data-text", "data-abbrevmap"];
|
||||||
|
}
|
||||||
|
static get defaultAbbrevMap() {
|
||||||
|
return {
|
||||||
|
"#": "Hinweis auf weitere Informationen in der Anmerkung.",
|
||||||
|
$: "vermutlich",
|
||||||
|
"+++": "Inhalte aus mehreren Almanachen interpoliert",
|
||||||
|
B: "Blatt",
|
||||||
|
BB: "Blätter",
|
||||||
|
C: "Corrigenda",
|
||||||
|
Diagr: "Diagramm",
|
||||||
|
G: "Graphik",
|
||||||
|
"G-Verz": "Verzeichnis der Kupfer u. ä.",
|
||||||
|
GG: "Graphiken",
|
||||||
|
Hrsg: "Herausgeber",
|
||||||
|
"I-Verz": "Inhaltsverzeichnis",
|
||||||
|
Kal: "Kalendarium",
|
||||||
|
Kr: "Karte",
|
||||||
|
MusB: "Musikbeigabe",
|
||||||
|
MusBB: "Musikbeigaben",
|
||||||
|
S: "Seite",
|
||||||
|
SS: "Seiten",
|
||||||
|
Sp: "Spiegel",
|
||||||
|
T: "Titel",
|
||||||
|
TG: "Titelgraphik, Titelportrait etc",
|
||||||
|
"TG r": "Titelgraphik, Titelportrait etc recto",
|
||||||
|
"TG v": "Titelgraphik, Titelportrait etc verso",
|
||||||
|
Tab: "Tabelle",
|
||||||
|
UG: "Umschlaggraphik",
|
||||||
|
"UG r": "Umschlaggraphik recto",
|
||||||
|
"UG v": "Umschlaggraphik verso",
|
||||||
|
VB: "Vorsatzblatt",
|
||||||
|
Vf: "Verfasser",
|
||||||
|
VrlgM: "Verlagsmitteilung",
|
||||||
|
Vrwrt: "Vorwort",
|
||||||
|
ar: "arabische Paginierung",
|
||||||
|
ar1: "erste arabische Paginierung",
|
||||||
|
ar2: "zweite arabische Paginierung",
|
||||||
|
ar3: "dritte arabische Paginierung",
|
||||||
|
ar4: "vierte arabische Paginierung",
|
||||||
|
ar5: "fünfte arabische Paginierung",
|
||||||
|
ar6: "sechste arabische Paginierung",
|
||||||
|
ar7: "siebte arabische Paginierung",
|
||||||
|
gA: "graphische Anleitung",
|
||||||
|
gT: "graphischer Titel",
|
||||||
|
gTzA: "graphische Tanzanleitung",
|
||||||
|
nT: "Nachtitel",
|
||||||
|
röm: "römische Paginierung",
|
||||||
|
röm1: "erste römische Paginierung",
|
||||||
|
röm2: "zweite römische Paginierung",
|
||||||
|
röm3: "dritte römische Paginierung",
|
||||||
|
röm4: "vierte römische Paginierung",
|
||||||
|
röm5: "fünfte römische Paginierung",
|
||||||
|
röm6: "sechste römische Paginierung",
|
||||||
|
röm7: "siebte römische Paginierung",
|
||||||
|
vT: "Vortitel",
|
||||||
|
zT: "Zwischentitel",
|
||||||
|
"§§": "Hinweis auf Mängel im Almanach (Beschädigungen, fehlende Graphiken, unvollständige Sammlungen etc) in der Anmerkung"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
constructor() {
|
||||||
|
super(), this._abbrevMap = p.defaultAbbrevMap;
|
||||||
|
}
|
||||||
|
connectedCallback() {
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
attributeChangedCallback(t, e, i) {
|
||||||
|
e !== i && (t === "data-abbrevmap" && this._parseAndSetAbbrevMap(i), this.render());
|
||||||
|
}
|
||||||
|
_parseAndSetAbbrevMap(t) {
|
||||||
|
if (!t) {
|
||||||
|
this._abbrevMap = p.defaultAbbrevMap;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this._abbrevMap = JSON.parse(t);
|
||||||
|
} catch {
|
||||||
|
this._abbrevMap = p.defaultAbbrevMap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setAbbrevMap(t) {
|
||||||
|
typeof t == "object" && t !== null && (this._abbrevMap = t, this.render());
|
||||||
|
}
|
||||||
|
get text() {
|
||||||
|
return this.getAttribute("data-text") || "";
|
||||||
|
}
|
||||||
|
set text(t) {
|
||||||
|
this.setAttribute("data-text", t);
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
this.innerHTML = this.transformText(this.text, this._abbrevMap);
|
||||||
|
}
|
||||||
|
transformText(t, e) {
|
||||||
|
let i = "", s = 0;
|
||||||
|
for (; s < t.length; ) {
|
||||||
|
if (s > 0 && !this.isSpaceOrPunct(t[s - 1])) {
|
||||||
|
i += t[s], s++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const a = this.findLongestAbbrevAt(t, s, e);
|
||||||
|
if (a) {
|
||||||
|
const { match: l, meaning: h } = a;
|
||||||
|
i += `
|
||||||
|
<tool-tip position="top" class="!inline" timeout="300">
|
||||||
|
<div class="data-tip p-2 text-sm text-white bg-gray-700 rounded shadow">
|
||||||
|
${h}
|
||||||
|
</div>
|
||||||
|
<span class="cursor-help text-blue-900 hover:text-slate-800">
|
||||||
|
${l}
|
||||||
|
</span>
|
||||||
|
</tool-tip>
|
||||||
|
`, s += l.length;
|
||||||
|
} else
|
||||||
|
i += t[s], s++;
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
findLongestAbbrevAt(t, e, i) {
|
||||||
|
let s = null, a = 0;
|
||||||
|
for (const l of Object.keys(i))
|
||||||
|
t.startsWith(l, e) && l.length > a && (s = l, a = l.length);
|
||||||
|
return s ? { match: s, meaning: i[s] } : null;
|
||||||
|
}
|
||||||
|
isSpaceOrPunct(t) {
|
||||||
|
return /\s|[.,;:!?]/.test(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class K extends HTMLElement {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
connectedCallback() {
|
||||||
|
this.style.cursor = "pointer", this.addEventListener("click", this.handleClick);
|
||||||
|
}
|
||||||
|
disconnectedCallback() {
|
||||||
|
this.removeEventListener("click", this.handleClick);
|
||||||
|
}
|
||||||
|
handleClick(t) {
|
||||||
|
const e = this.getAttribute("data-jump");
|
||||||
|
if (e) {
|
||||||
|
const i = document.querySelector(e);
|
||||||
|
i ? i.scrollIntoView({ behavior: "smooth" }) : console.warn(`No element found for selector: ${e}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var v;
|
||||||
|
class X extends HTMLElement {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
n(this, v, 176);
|
||||||
|
this._images = [];
|
||||||
|
}
|
||||||
|
connectedCallback() {
|
||||||
|
this._images = Array.from(this.querySelectorAll(".primages")), this.calculateShownImages();
|
||||||
|
const e = new ResizeObserver((i, s) => {
|
||||||
|
this.calculateShownImages();
|
||||||
|
});
|
||||||
|
this._resizeObserver = e, e.observe(this);
|
||||||
|
}
|
||||||
|
disconnectedCallback() {
|
||||||
|
this._resizeObserver.unobserve(this);
|
||||||
|
}
|
||||||
|
calculateShownImages() {
|
||||||
|
const e = this.getBoundingClientRect();
|
||||||
|
console.log(e);
|
||||||
|
const i = Math.floor(e.width / (c(this, v) + 10));
|
||||||
|
for (let s = 0; s < this._images.length; s++)
|
||||||
|
s < i - 1 ? this._images[s].classList.remove("hidden") : this._images[s].classList.add("hidden");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v = new WeakMap();
|
||||||
|
customElements.define(H, K);
|
||||||
|
customElements.define(P, p);
|
||||||
|
customElements.define(M, U);
|
||||||
|
customElements.define(B, D);
|
||||||
|
customElements.define(q, z);
|
||||||
|
customElements.define(R, G);
|
||||||
|
customElements.define($, j);
|
||||||
|
customElements.define(N, O);
|
||||||
|
customElements.define(F, X);
|
||||||
|
export {
|
||||||
|
p as AbbreviationTooltips,
|
||||||
|
U as FilterList,
|
||||||
|
D as ScrollButton,
|
||||||
|
V as XSLTParseProcess
|
||||||
|
};
|
||||||
1
views/assets/style.css
Normal file
1
views/assets/style.css
Normal file
File diff suppressed because one or more lines are too long
13
views/assets/xslt/transform-citation.xsl
Normal file
13
views/assets/xslt/transform-citation.xsl
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
||||||
|
<xsl:output method="html" indent="yes" />
|
||||||
|
<xsl:template match="title">
|
||||||
|
<em>
|
||||||
|
<xsl:apply-templates />
|
||||||
|
</em>
|
||||||
|
</xsl:template>
|
||||||
|
<xsl:template match="year">
|
||||||
|
<span class="">
|
||||||
|
<xsl:apply-templates />
|
||||||
|
</span>
|
||||||
|
</xsl:template>
|
||||||
|
</xsl:stylesheet>
|
||||||
2383
views/package-lock.json
generated
Normal file
2383
views/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ type IXMLItem interface {
|
|||||||
// INFO:
|
// INFO:
|
||||||
// - Keys should be unique
|
// - Keys should be unique
|
||||||
// - Keys[0] has the special meaning of the primary key (for FTS etc.)
|
// - Keys[0] has the special meaning of the primary key (for FTS etc.)
|
||||||
Keys() []string
|
Keys() []any
|
||||||
Type() string
|
Type() string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,12 +9,12 @@ import (
|
|||||||
|
|
||||||
type Resolver[T IXMLItem] struct {
|
type Resolver[T IXMLItem] struct {
|
||||||
// INFO: map[type][ID]
|
// INFO: map[type][ID]
|
||||||
index map[string]map[string][]Resolved[T]
|
index map[string]map[any][]Resolved[T]
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewResolver[T IXMLItem]() *Resolver[T] {
|
func NewResolver[T IXMLItem]() *Resolver[T] {
|
||||||
return &Resolver[T]{index: make(map[string]map[string][]Resolved[T])}
|
return &Resolver[T]{index: make(map[string]map[any][]Resolved[T])}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resolver[T]) Add(typeName, refID string, item Resolved[T]) {
|
func (r *Resolver[T]) Add(typeName, refID string, item Resolved[T]) {
|
||||||
@@ -22,12 +22,12 @@ func (r *Resolver[T]) Add(typeName, refID string, item Resolved[T]) {
|
|||||||
defer r.mu.Unlock()
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
if _, exists := r.index[typeName]; !exists {
|
if _, exists := r.index[typeName]; !exists {
|
||||||
r.index[typeName] = make(map[string][]Resolved[T])
|
r.index[typeName] = make(map[any][]Resolved[T])
|
||||||
}
|
}
|
||||||
r.index[typeName][refID] = append(r.index[typeName][refID], item)
|
r.index[typeName][refID] = append(r.index[typeName][refID], item)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resolver[T]) Get(typeName, refID string) ([]Resolved[T], error) {
|
func (r *Resolver[T]) Get(typeName string, refID any) ([]Resolved[T], error) {
|
||||||
r.mu.RLock()
|
r.mu.RLock()
|
||||||
defer r.mu.RUnlock()
|
defer r.mu.RUnlock()
|
||||||
|
|
||||||
@@ -44,5 +44,5 @@ func (r *Resolver[T]) Clear() {
|
|||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
defer r.mu.Unlock()
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
r.index = make(map[string]map[string][]Resolved[T])
|
r.index = make(map[string]map[any][]Resolved[T])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,8 +121,6 @@ func (p *XMLParser[T]) Cleanup(latest ParseMeta) {
|
|||||||
p.Array = append(p.Array, *item)
|
p.Array = append(p.Array, *item)
|
||||||
p.addResolvable(*item)
|
p.addResolvable(*item)
|
||||||
}
|
}
|
||||||
|
|
||||||
slices.SortFunc(p.Array, Sort)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *XMLParser[T]) addResolvable(item T) {
|
func (p *XMLParser[T]) addResolvable(item T) {
|
||||||
|
|||||||
@@ -1,77 +1 @@
|
|||||||
package xmlparsing
|
package xmlparsing
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Sort[T IXMLItem](i, j T) int {
|
|
||||||
|
|
||||||
keys_a := i.Keys()
|
|
||||||
keys_b := j.Keys()
|
|
||||||
|
|
||||||
if len(keys_a) == 0 && len(keys_b) == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(keys_a) == 0 && len(keys_b) > 0 {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(keys_a) > 0 && len(keys_b) == 0 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
sort_a := strings.Split(keys_a[0], "-")
|
|
||||||
sort_b := strings.Split(keys_b[0], "-")
|
|
||||||
|
|
||||||
for i, item := range sort_a {
|
|
||||||
if i >= len(sort_b) {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// INFO: this is a bit lazy since
|
|
||||||
// - we are comparing bit values not unicode code points
|
|
||||||
// - the comparison is case sensitive
|
|
||||||
int_a, err := strconv.Atoi(item)
|
|
||||||
if err != nil {
|
|
||||||
if item < sort_b[i] {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
if item > sort_b[i] {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
int_b, err := strconv.Atoi(sort_b[i])
|
|
||||||
if err != nil {
|
|
||||||
|
|
||||||
if item < sort_b[i] {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
if item > sort_b[i] {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if int_a < int_b {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
if int_a > int_b {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(sort_b) > len(sort_a) {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package xmlmodels
|
package xmlmodels
|
||||||
|
|
||||||
import "encoding/xml"
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/xml"
|
||||||
|
)
|
||||||
|
|
||||||
type Letter struct {
|
type Letter struct {
|
||||||
XMLName xml.Name `xml:"letterText"`
|
XMLName xml.Name `xml:"letterText"`
|
||||||
@@ -10,6 +13,22 @@ type Letter struct {
|
|||||||
Content string `xml:",innerxml"`
|
Content string `xml:",innerxml"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l Letter) Keys() []any {
|
||||||
|
return []any{l.Letter}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Letter) Type() string {
|
||||||
|
return LETTER
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Letter) String() string {
|
||||||
|
json, err := json.Marshal(l)
|
||||||
|
if err != nil {
|
||||||
|
return "Cant marshal to json, Letter: " + err.Error()
|
||||||
|
}
|
||||||
|
return string(json)
|
||||||
|
}
|
||||||
|
|
||||||
type Page struct {
|
type Page struct {
|
||||||
XMLName xml.Name `xml:"page"`
|
XMLName xml.Name `xml:"page"`
|
||||||
Index int `xml:"index,attr"`
|
Index int `xml:"index,attr"`
|
||||||
|
|||||||
191
xmlmodels/library.go
Normal file
191
xmlmodels/library.go
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
package xmlmodels
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
xmlparsing "github.com/Theodor-Springmann-Stiftung/lenz-web/xml"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
REFERENCES_PATH = "data/xml/akteure.xml"
|
||||||
|
LETTERS_PATH = "data/xml/briefe.xml"
|
||||||
|
META_PATH = "data/xml/meta.xml"
|
||||||
|
TRADITIONS_PATH = "data/xml/traditions.xml"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Library struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
xmlparsing.Library
|
||||||
|
|
||||||
|
Persons *xmlparsing.XMLParser[PersonDef]
|
||||||
|
Places *xmlparsing.XMLParser[LocationDef]
|
||||||
|
AppDefs *xmlparsing.XMLParser[AppDef]
|
||||||
|
|
||||||
|
Letters *xmlparsing.XMLParser[Letter]
|
||||||
|
Traditions *xmlparsing.XMLParser[Tradition]
|
||||||
|
Metas *xmlparsing.XMLParser[Meta]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Library) String() string {
|
||||||
|
// TODO:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// INFO: this is the only place where the providers are created. There is no need for locking on access.
|
||||||
|
func NewLibrary() *Library {
|
||||||
|
return &Library{
|
||||||
|
Persons: xmlparsing.NewXMLParser[PersonDef](),
|
||||||
|
Places: xmlparsing.NewXMLParser[LocationDef](),
|
||||||
|
AppDefs: xmlparsing.NewXMLParser[AppDef](),
|
||||||
|
Letters: xmlparsing.NewXMLParser[Letter](),
|
||||||
|
Traditions: xmlparsing.NewXMLParser[Tradition](),
|
||||||
|
Metas: xmlparsing.NewXMLParser[Meta](),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Library) Parse(source xmlparsing.ParseSource, baseDir, commit string) error {
|
||||||
|
// INFO: this lock prevents multiple parses from happening at the same time.
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
meta := xmlparsing.ParseMeta{
|
||||||
|
Source: source,
|
||||||
|
BaseDir: baseDir,
|
||||||
|
Commit: commit,
|
||||||
|
Date: time.Now(),
|
||||||
|
}
|
||||||
|
metamu := sync.Mutex{}
|
||||||
|
|
||||||
|
l.prepare()
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
err := l.Persons.Serialize(&PersonDefs{}, filepath.Join(meta.BaseDir, REFERENCES_PATH), meta)
|
||||||
|
if err != nil {
|
||||||
|
metamu.Lock()
|
||||||
|
meta.FailedPaths = append(meta.FailedPaths, filepath.Join(meta.BaseDir, REFERENCES_PATH))
|
||||||
|
metamu.Unlock()
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
err := l.Places.Serialize(&LocationDefs{}, filepath.Join(meta.BaseDir, REFERENCES_PATH), meta)
|
||||||
|
if err != nil {
|
||||||
|
metamu.Lock()
|
||||||
|
meta.FailedPaths = append(meta.FailedPaths, filepath.Join(meta.BaseDir, REFERENCES_PATH))
|
||||||
|
metamu.Unlock()
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
err := l.AppDefs.Serialize(&AppDefs{}, filepath.Join(meta.BaseDir, REFERENCES_PATH), meta)
|
||||||
|
if err != nil {
|
||||||
|
metamu.Lock()
|
||||||
|
meta.FailedPaths = append(meta.FailedPaths, filepath.Join(meta.BaseDir, REFERENCES_PATH))
|
||||||
|
metamu.Unlock()
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
err := l.Letters.Serialize(&DocumentsRoot{}, filepath.Join(meta.BaseDir, LETTERS_PATH), meta)
|
||||||
|
if err != nil {
|
||||||
|
metamu.Lock()
|
||||||
|
meta.FailedPaths = append(meta.FailedPaths, filepath.Join(meta.BaseDir, LETTERS_PATH))
|
||||||
|
metamu.Unlock()
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
err := l.Traditions.Serialize(&TraditionsRoot{}, filepath.Join(meta.BaseDir, TRADITIONS_PATH), meta)
|
||||||
|
if err != nil {
|
||||||
|
metamu.Lock()
|
||||||
|
meta.FailedPaths = append(meta.FailedPaths, filepath.Join(meta.BaseDir, TRADITIONS_PATH))
|
||||||
|
metamu.Unlock()
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
err := l.Metas.Serialize(&MetaRoot{}, filepath.Join(meta.BaseDir, META_PATH), meta)
|
||||||
|
if err != nil {
|
||||||
|
metamu.Lock()
|
||||||
|
meta.FailedPaths = append(meta.FailedPaths, filepath.Join(meta.BaseDir, META_PATH))
|
||||||
|
metamu.Unlock()
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
l.cleanup(meta)
|
||||||
|
l.Parses = append(l.Parses, meta)
|
||||||
|
|
||||||
|
var errors []string
|
||||||
|
if len(meta.FailedPaths) > 0 {
|
||||||
|
errors = append(errors, fmt.Sprintf("Failed paths: %v", meta.FailedPaths))
|
||||||
|
}
|
||||||
|
if len(errors) > 0 {
|
||||||
|
return fmt.Errorf("Parsing encountered errors: %v", strings.Join(errors, "; "))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Library) prepare() {
|
||||||
|
l.Persons.Prepare()
|
||||||
|
l.Places.Prepare()
|
||||||
|
l.AppDefs.Prepare()
|
||||||
|
l.Letters.Prepare()
|
||||||
|
l.Traditions.Prepare()
|
||||||
|
l.Metas.Prepare()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Library) cleanup(meta xmlparsing.ParseMeta) {
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
wg.Add(6)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
l.Persons.Cleanup(meta)
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
l.Places.Cleanup(meta)
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
l.AppDefs.Cleanup(meta)
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
l.Letters.Cleanup(meta)
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
l.Traditions.Cleanup(meta)
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
l.Metas.Cleanup(meta)
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package xmlmodels
|
package xmlmodels
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
|
||||||
xmlparsing "github.com/Theodor-Springmann-Stiftung/lenz-web/xml"
|
xmlparsing "github.com/Theodor-Springmann-Stiftung/lenz-web/xml"
|
||||||
@@ -16,6 +17,22 @@ type Meta struct {
|
|||||||
Recieved []Action `xml:"recieved"`
|
Recieved []Action `xml:"recieved"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m Meta) Keys() []any {
|
||||||
|
return []any{m.Letter}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Meta) Type() string {
|
||||||
|
return META
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Meta) String() string {
|
||||||
|
json, err := json.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
return "Cant marshal to json, Meta: " + err.Error()
|
||||||
|
}
|
||||||
|
return string(json)
|
||||||
|
}
|
||||||
|
|
||||||
type Action struct {
|
type Action struct {
|
||||||
Dates []Date `xml:"date,attr"`
|
Dates []Date `xml:"date,attr"`
|
||||||
Places []RefElement `xml:"place"`
|
Places []RefElement `xml:"place"`
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package xmlmodels
|
package xmlmodels
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
type PersonDef struct {
|
type PersonDef struct {
|
||||||
Index int `xml:"index,attr"`
|
Index int `xml:"index,attr"`
|
||||||
Name string `xml:"name,attr"`
|
Name string `xml:"name,attr"`
|
||||||
@@ -9,14 +11,62 @@ type PersonDef struct {
|
|||||||
Comment string `xml:"komm,attr"`
|
Comment string `xml:"komm,attr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p PersonDef) Keys() []any {
|
||||||
|
return []any{p.Index}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p PersonDef) Type() string {
|
||||||
|
return PERSONREF
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p PersonDef) String() string {
|
||||||
|
data, err := json.Marshal(p)
|
||||||
|
if err != nil {
|
||||||
|
return "Cant marshal to json, PersonDef: " + err.Error()
|
||||||
|
}
|
||||||
|
return string(data)
|
||||||
|
}
|
||||||
|
|
||||||
type LocationDef struct {
|
type LocationDef struct {
|
||||||
Index int `xml:"index,attr"`
|
Index int `xml:"index,attr"`
|
||||||
Name string `xml:"name,attr"`
|
Name string `xml:"name,attr"`
|
||||||
Ref string `xml:"ref,attr"`
|
Ref string `xml:"ref,attr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l LocationDef) Keys() []any {
|
||||||
|
return []any{l.Index}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l LocationDef) Type() string {
|
||||||
|
return LOCATIONREF
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l LocationDef) String() string {
|
||||||
|
data, err := json.Marshal(l)
|
||||||
|
if err != nil {
|
||||||
|
return "Cant marshal to json, LocationDef: " + err.Error()
|
||||||
|
}
|
||||||
|
return string(data)
|
||||||
|
}
|
||||||
|
|
||||||
type AppDef struct {
|
type AppDef struct {
|
||||||
Index int `xml:"index,attr"`
|
Index int `xml:"index,attr"`
|
||||||
Name string `xml:"name,attr"`
|
Name string `xml:"name,attr"`
|
||||||
Category string `xml:"category,attr"`
|
Category string `xml:"category,attr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a AppDef) Keys() []any {
|
||||||
|
return []any{a.Index}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AppDef) Type() string {
|
||||||
|
return APPREF
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AppDef) String() string {
|
||||||
|
data, err := json.Marshal(a)
|
||||||
|
if err != nil {
|
||||||
|
return "Cant marshal to json, AppDef: " + err.Error()
|
||||||
|
}
|
||||||
|
return string(data)
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,20 +22,40 @@ type PersonDefs struct {
|
|||||||
Persons []PersonDef `xml:"personDef"`
|
Persons []PersonDef `xml:"personDef"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p PersonDefs) Children() []PersonDef {
|
||||||
|
return p.Persons
|
||||||
|
}
|
||||||
|
|
||||||
type LocationDefs struct {
|
type LocationDefs struct {
|
||||||
Locations []LocationDef `xml:"locationDef"`
|
Locations []LocationDef `xml:"locationDef"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l LocationDefs) Children() []LocationDef {
|
||||||
|
return l.Locations
|
||||||
|
}
|
||||||
|
|
||||||
type AppDefs struct {
|
type AppDefs struct {
|
||||||
Apps []AppDef `xml:"appDef"`
|
Apps []AppDef `xml:"appDef"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a AppDefs) Children() []AppDef {
|
||||||
|
return a.Apps
|
||||||
|
}
|
||||||
|
|
||||||
type TraditionsRoot struct {
|
type TraditionsRoot struct {
|
||||||
XMLName xml.Name `xml:"traditions"`
|
XMLName xml.Name `xml:"traditions"`
|
||||||
Traditions []Tradition `xml:"tradition"`
|
Traditions []Tradition `xml:"tradition"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t TraditionsRoot) Children() []Tradition {
|
||||||
|
return t.Traditions
|
||||||
|
}
|
||||||
|
|
||||||
type DocumentsRoot struct {
|
type DocumentsRoot struct {
|
||||||
XMLName xml.Name `xml:"document"`
|
XMLName xml.Name `xml:"document"`
|
||||||
Documents []Letter `xml:"letterText"`
|
Documents []Letter `xml:"letterText"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d DocumentsRoot) Children() []Letter {
|
||||||
|
return d.Documents
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package xmlmodels
|
package xmlmodels
|
||||||
|
|
||||||
import "encoding/xml"
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/xml"
|
||||||
|
)
|
||||||
|
|
||||||
type Tradition struct {
|
type Tradition struct {
|
||||||
XMLName xml.Name `xml:"letterTradition"`
|
XMLName xml.Name `xml:"letterTradition"`
|
||||||
@@ -8,6 +11,22 @@ type Tradition struct {
|
|||||||
Apps []App `xml:"app"`
|
Apps []App `xml:"app"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t Tradition) Keys() []any {
|
||||||
|
return []any{t.Letter}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Tradition) Type() string {
|
||||||
|
return TRADITION
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Tradition) String() string {
|
||||||
|
data, err := json.Marshal(t)
|
||||||
|
if err != nil {
|
||||||
|
return "Cant marshal to json, Tradition: " + err.Error()
|
||||||
|
}
|
||||||
|
return string(data)
|
||||||
|
}
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
Reference int `xml:"ref,attr"`
|
Reference int `xml:"ref,attr"`
|
||||||
Content string `xml:",innerxml"`
|
Content string `xml:",innerxml"`
|
||||||
|
|||||||
Reference in New Issue
Block a user