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
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/Theodor-Springmann-Stiftung/lenz-web/helpers/xsdtime"
|
||||
)
|
||||
|
||||
type Month struct {
|
||||
Full string
|
||||
Short string
|
||||
@@ -54,50 +48,6 @@ var TRANSLD = []Weekday{
|
||||
{"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 {
|
||||
if i > 12 || i < 1 {
|
||||
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:
|
||||
// - Keys should be unique
|
||||
// - Keys[0] has the special meaning of the primary key (for FTS etc.)
|
||||
Keys() []string
|
||||
Keys() []any
|
||||
Type() string
|
||||
}
|
||||
|
||||
|
||||
@@ -9,12 +9,12 @@ import (
|
||||
|
||||
type Resolver[T IXMLItem] struct {
|
||||
// INFO: map[type][ID]
|
||||
index map[string]map[string][]Resolved[T]
|
||||
index map[string]map[any][]Resolved[T]
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
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]) {
|
||||
@@ -22,12 +22,12 @@ func (r *Resolver[T]) Add(typeName, refID string, item Resolved[T]) {
|
||||
defer r.mu.Unlock()
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
@@ -44,5 +44,5 @@ func (r *Resolver[T]) Clear() {
|
||||
r.mu.Lock()
|
||||
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.addResolvable(*item)
|
||||
}
|
||||
|
||||
slices.SortFunc(p.Array, Sort)
|
||||
}
|
||||
|
||||
func (p *XMLParser[T]) addResolvable(item T) {
|
||||
|
||||
@@ -1,77 +1 @@
|
||||
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
|
||||
|
||||
import "encoding/xml"
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
)
|
||||
|
||||
type Letter struct {
|
||||
XMLName xml.Name `xml:"letterText"`
|
||||
@@ -10,6 +13,22 @@ type Letter struct {
|
||||
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 {
|
||||
XMLName xml.Name `xml:"page"`
|
||||
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
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
|
||||
xmlparsing "github.com/Theodor-Springmann-Stiftung/lenz-web/xml"
|
||||
@@ -16,6 +17,22 @@ type Meta struct {
|
||||
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 {
|
||||
Dates []Date `xml:"date,attr"`
|
||||
Places []RefElement `xml:"place"`
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package xmlmodels
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
type PersonDef struct {
|
||||
Index int `xml:"index,attr"`
|
||||
Name string `xml:"name,attr"`
|
||||
@@ -9,14 +11,62 @@ type PersonDef struct {
|
||||
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 {
|
||||
Index int `xml:"index,attr"`
|
||||
Name string `xml:"name,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 {
|
||||
Index int `xml:"index,attr"`
|
||||
Name string `xml:"name,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"`
|
||||
}
|
||||
|
||||
func (p PersonDefs) Children() []PersonDef {
|
||||
return p.Persons
|
||||
}
|
||||
|
||||
type LocationDefs struct {
|
||||
Locations []LocationDef `xml:"locationDef"`
|
||||
}
|
||||
|
||||
func (l LocationDefs) Children() []LocationDef {
|
||||
return l.Locations
|
||||
}
|
||||
|
||||
type AppDefs struct {
|
||||
Apps []AppDef `xml:"appDef"`
|
||||
}
|
||||
|
||||
func (a AppDefs) Children() []AppDef {
|
||||
return a.Apps
|
||||
}
|
||||
|
||||
type TraditionsRoot struct {
|
||||
XMLName xml.Name `xml:"traditions"`
|
||||
Traditions []Tradition `xml:"tradition"`
|
||||
}
|
||||
|
||||
func (t TraditionsRoot) Children() []Tradition {
|
||||
return t.Traditions
|
||||
}
|
||||
|
||||
type DocumentsRoot struct {
|
||||
XMLName xml.Name `xml:"document"`
|
||||
Documents []Letter `xml:"letterText"`
|
||||
}
|
||||
|
||||
func (d DocumentsRoot) Children() []Letter {
|
||||
return d.Documents
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package xmlmodels
|
||||
|
||||
import "encoding/xml"
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
)
|
||||
|
||||
type Tradition struct {
|
||||
XMLName xml.Name `xml:"letterTradition"`
|
||||
@@ -8,6 +11,22 @@ type Tradition struct {
|
||||
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 {
|
||||
Reference int `xml:"ref,attr"`
|
||||
Content string `xml:",innerxml"`
|
||||
|
||||
Reference in New Issue
Block a user