Compare commits

..
4 Commits
Author SHA1 Message Date
schreifuchs 2a96034fca chore(htmx): upgrade htmx to v4
release.yaml / publish (push) Successful in 3m48s
2026-09-29 11:03:38 +02:00
schreifuchs 812891a205 fix(gallery): use component template
release.yaml / publish (push) Successful in 3m51s
2026-09-29 10:46:45 +02:00
schreifuchs 297efaa4a6 fix(translations): stay on page
release.yaml / publish (push) Successful in 4m0s
2026-09-28 09:52:22 +02:00
schreifuchs c08dd355c0 fix(gallery): faster image loading
release.yaml / publish (push) Successful in 3m56s
2026-09-26 23:35:15 +02:00
24 changed files with 174 additions and 129 deletions

No files matched your search

+8
View File
@@ -27,3 +27,11 @@ jobs:
file: ./Dockerfile file: ./Dockerfile
push: true push: true
tags: "git.schreifuchs.ch/schreifuchs/schreifuchs.ch:${{ github.ref_name }},git.schreifuchs.ch/schreifuchs/schreifuchs.ch:latest" tags: "git.schreifuchs.ch/schreifuchs/schreifuchs.ch:${{ github.ref_name }},git.schreifuchs.ch/schreifuchs/schreifuchs.ch:latest"
- name: Create Gitea Release
uses: https://gitea.com/actions/gitea-release-action@v1
with:
state: "published"
title: "Release ${{ github.ref_name }}"
notes: "Automated release for tag ${{ github.ref_name }}"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+1 -1
View File
@@ -26,7 +26,7 @@ COPY . .
# Copy built frontend assets from the previous stage # Copy built frontend assets from the previous stage
COPY --from=frontend-builder /app/web/static/css/output.css ./web/static/css/ COPY --from=frontend-builder /app/web/static/css/output.css ./web/static/css/
COPY --from=frontend-builder /app/web/static/js/htmx.min.js ./web/static/js/ COPY --from=frontend-builder /app/web/static/js/ ./web/static/js/
# Generate templ files using the tool defined in go.mod # Generate templ files using the tool defined in go.mod
RUN go tool templ generate RUN go tool templ generate
+1 -1
View File
@@ -5,6 +5,7 @@ go 1.26
tool github.com/a-h/templ/cmd/templ tool github.com/a-h/templ/cmd/templ
require ( require (
github.com/InfinityTools/go-binpack2d v1.0.0
github.com/a-h/templ v0.3.977 github.com/a-h/templ v0.3.977
github.com/alicebob/miniredis/v2 v2.36.1 github.com/alicebob/miniredis/v2 v2.36.1
github.com/gomarkdown/markdown v0.0.0-20260217112301-37c66b85d6ab github.com/gomarkdown/markdown v0.0.0-20260217112301-37c66b85d6ab
@@ -18,7 +19,6 @@ require (
) )
require ( require (
github.com/InfinityTools/go-binpack2d v1.0.0 // indirect
github.com/a-h/parse v0.0.0-20250122154542-74294addb73e // indirect github.com/a-h/parse v0.0.0-20250122154542-74294addb73e // indirect
github.com/andybalholm/brotli v1.1.0 // indirect github.com/andybalholm/brotli v1.1.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect
+8
View File
@@ -53,13 +53,21 @@ func Path(ctx context.Context, pp ...string) string {
} }
func PathForLang(ctx context.Context, lang string, pp ...string) string { func PathForLang(ctx context.Context, lang string, pp ...string) string {
slog.Debug("computing path for lang", "pp", pp, "lang", lang)
s, ok := ctx.Value(serviceKey).(*Service) s, ok := ctx.Value(serviceKey).(*Service)
if !ok { if !ok {
slog.Error("could not extract translation service from context for translating path", "ctx", ctx, "path", pp) slog.Error("could not extract translation service from context for translating path", "ctx", ctx, "path", pp)
return path.Join(pp...) return path.Join(pp...)
} }
// remove leading ""
for len(pp) > 0 && pp[0] == "" {
pp = pp[1:]
}
if len(pp) > 0 { if len(pp) > 0 {
slog.Debug("computing path for lang", "pp", pp, "lang", lang, "pp[0]", pp[0], "translations", s.translations)
if _, ok := s.translations[pp[0]]; ok { if _, ok := s.translations[pp[0]]; ok {
pp = pp[1:] pp = pp[1:]
} }
+2 -2
View File
@@ -27,7 +27,7 @@ func (h *Handler) gallery(w http.ResponseWriter, r *http.Request) {
ctx = templer.AppendHeader(ctx, fmt.Sprintf(`<link rel="preload" href="%s?w=800" as="image" />`, image.URL())) ctx = templer.AppendHeader(ctx, fmt.Sprintf(`<link rel="preload" href="%s?w=800" as="image" />`, image.URL()))
} }
h.r.Render(ctx, w, r, pages.GallerySet(imgs)) h.r.RenderPage(ctx, w, r, pages.GallerySet(imgs))
} }
func (h *Handler) image(w http.ResponseWriter, r *http.Request) { func (h *Handler) image(w http.ResponseWriter, r *http.Request) {
@@ -41,5 +41,5 @@ func (h *Handler) image(w http.ResponseWriter, r *http.Request) {
return return
} }
h.r.Render(r.Context(), w, r, pages.GalleryImage(uid, next, prev)) h.r.RenderComponent(r.Context(), w, r, pages.GalleryImage(uid, next, prev))
} }
+2 -1
View File
@@ -40,5 +40,6 @@ type galleryService interface {
GetPrevAndNext(ctx context.Context, uid string) (previous *gallery.Image, next *gallery.Image, err error) GetPrevAndNext(ctx context.Context, uid string) (previous *gallery.Image, next *gallery.Image, err error)
} }
type renderer interface { type renderer interface {
Render(ctx context.Context, w io.Writer, r *http.Request, component templ.Component) RenderPage(ctx context.Context, w io.Writer, r *http.Request, component templ.Component)
RenderComponent(ctx context.Context, w io.Writer, r *http.Request, component templ.Component)
} }
+2 -2
View File
@@ -17,7 +17,7 @@ func (h *Handler) home(w http.ResponseWriter, r *http.Request) {
return return
} }
h.r.Render(r.Context(), w, r, pages.Home(pageHeaders)) h.r.RenderPage(r.Context(), w, r, pages.Home(pageHeaders))
} }
func (h *Handler) page(w http.ResponseWriter, r *http.Request) { func (h *Handler) page(w http.ResponseWriter, r *http.Request) {
@@ -43,5 +43,5 @@ func (h *Handler) page(w http.ResponseWriter, r *http.Request) {
ctx = templer.PageTitle(ctx, page.Title) ctx = templer.PageTitle(ctx, page.Title)
h.r.Render(ctx, w, r, pages.ContentPage(page)) h.r.RenderPage(ctx, w, r, pages.ContentPage(page))
} }
+1 -1
View File
@@ -33,5 +33,5 @@ type pageService interface {
GetPage(ctx context.Context, uid string) (page.Page, error) GetPage(ctx context.Context, uid string) (page.Page, error)
} }
type renderer interface { type renderer interface {
Render(ctx context.Context, w io.Writer, r *http.Request, component templ.Component) RenderPage(ctx context.Context, w io.Writer, r *http.Request, component templ.Component)
} }
+1 -3
View File
@@ -21,8 +21,6 @@
"tailwindcss": "^4.1.18" "tailwindcss": "^4.1.18"
}, },
"dependencies": { "dependencies": {
"htmx-ext-head-support": "^2.0.5", "htmx.org": "^4.0.0"
"htmx-ext-preload": "^2.1.2",
"htmx.org": "^2.0.8"
} }
} }
+6 -25
View File
@@ -8,15 +8,9 @@ importers:
.: .:
dependencies: dependencies:
htmx-ext-head-support:
specifier: ^2.0.5
version: 2.0.5
htmx-ext-preload:
specifier: ^2.1.2
version: 2.1.2
htmx.org: htmx.org:
specifier: ^2.0.8 specifier: ^4.0.0
version: 2.0.8 version: 4.0.0
devDependencies: devDependencies:
'@tailwindcss/cli': '@tailwindcss/cli':
specifier: ^4.1.18 specifier: ^4.1.18
@@ -447,14 +441,9 @@ packages:
graceful-fs@4.2.11: graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
htmx-ext-head-support@2.0.5: htmx.org@4.0.0:
resolution: {integrity: sha512-fTzqnCw7OnMijiS48CCBFBHTuOMvycbX0nEFRL0lkRxyu5DFWX90IV1SwXpQ92fJf02IzkXcRNyjXn3j0QtYxA==} resolution: {integrity: sha512-T/171FUY93Kdfp8t+DnHdk45QvKRiBhVhhrwSzrXgUi4pHKvhp77dUA/qg8FAjsFWPIHNbmUuIdCrcVHuiZWng==}
hasBin: true
htmx-ext-preload@2.1.2:
resolution: {integrity: sha512-foD06XAITUUQApCUQnPexyJ6B7+4vFGMQ+t42unTVTGRu/CYHdAAt6oHg7eekPHT7NRnbM5hjnGVKtClryPabg==}
htmx.org@2.0.8:
resolution: {integrity: sha512-fm297iru0iWsNJlBrjvtN7V9zjaxd+69Oqjh4F/Vq9Wwi2kFisLcrLCiv5oBX0KLfOX/zG8AUo9ROMU5XUB44Q==}
is-extglob@2.1.1: is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
@@ -900,15 +889,7 @@ snapshots:
graceful-fs@4.2.11: {} graceful-fs@4.2.11: {}
htmx-ext-head-support@2.0.5: htmx.org@4.0.0: {}
dependencies:
htmx.org: 2.0.8
htmx-ext-preload@2.1.2:
dependencies:
htmx.org: 2.0.8
htmx.org@2.0.8: {}
is-extglob@2.1.1: {} is-extglob@2.1.1: {}
+2 -2
View File
@@ -4,8 +4,8 @@ import path from "node:path";
const filesToCopy = [ const filesToCopy = [
"node_modules/htmx.org/dist/htmx.min.js", "node_modules/htmx.org/dist/htmx.min.js",
"node_modules/htmx-ext-preload/dist/preload.min.js", "node_modules/htmx.org/dist/ext/hx-preload.min.js",
"node_modules/htmx-ext-head-support/dist/head-support.min.js", "node_modules/htmx.org/dist/ext/hx-head.min.js",
]; ];
const filesToMinifyRoot = "web/src/js"; const filesToMinifyRoot = "web/src/js";
+8 -1
View File
@@ -6,11 +6,18 @@ package components
// innerHTML would replace the wrapper below (centering + close button) and // innerHTML would replace the wrapper below (centering + close button) and
// break the layout. Content is swapped into the inner element with id // break the layout. Content is swapped into the inner element with id
// "<id>-content" instead. // "<id>-content" instead.
//
// In htmx 4 "htmx:after:swap" fires on the requesting element, which lives
// outside this dialog (e.g. gallery grid buttons). The listener is therefore
// attached document-wide via the bare hx-on form with "from:body" (the
// hx-on:<event> form cannot carry trigger specs: attribute names may not
// contain spaces) and guarded by ctx.target so only swaps into this dialog's
// content open it.
templ Modal(id string) { templ Modal(id string) {
<dialog <dialog
id={ id } id={ id }
class="m-auto backdrop:bg-black/90 bg-transparent p-0 w-full h-full max-w-none max-h-none" class="m-auto backdrop:bg-black/90 bg-transparent p-0 w-full h-full max-w-none max-h-none"
hx-on::after-swap="if (!this.open) this.showModal()" hx-on="htmx:after:swap from:body -> if (!this.open && this.contains(ctx.target)) this.showModal()"
> >
<div class="relative w-full h-full flex items-center justify-center overflow-hidden"> <div class="relative w-full h-full flex items-center justify-center overflow-hidden">
<button <button
+46 -26
View File
@@ -7,51 +7,71 @@ import (
"strings" "strings"
) )
templ Head(cfg config.Cfg, title string) { templ head(cfg config.Cfg, title string) {
<head> <head>
<meta charset="UTF-8"/> <meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta name="htmx-config" content={ `{"noSwap": [204, 304, "4xx", "5xx"], "preload": {"boostEvent": "mouseover"}}` }/>
<title>{ title }</title> <title>{ title }</title>
<link rel="icon" type="image/png" href="/favicon.png"/> <link rel="icon" type="image/png" href="/favicon.png"/>
<link fetchpriority="high" href="/css/output.css" rel="stylesheet"/> <link fetchpriority="high" href="/css/output.css" rel="stylesheet"/>
<script src="/js/htmx.min.js"></script> <script src="/js/htmx.min.js"></script>
<script src="/js/head-support.min.js"></script> <script src="/js/hx-head.min.js"></script>
<script src="/js/preload.min.js"></script> <script src="/js/hx-preload.min.js"></script>
<script src="/js/preload-links.min.js"></script> <script src="/js/preload-links.min.js"></script>
<script src={ cfg.Tracking.ScriptSrc } data-website-id={ cfg.Tracking.WebsiteID }></script> <script src={ cfg.Tracking.ScriptSrc } data-website-id={ cfg.Tracking.WebsiteID }></script>
for _, h := range templer.GetHeader(ctx) { for _, h := range templer.GetHeader(ctx) {
@templ.Raw(h) @templ.Raw(h)
} }
</head> </head>
{ children... }
} }
templ Base(cfg config.Cfg, title string) { templ Base(cfg config.Cfg, title string) {
<!DOCTYPE html> <!DOCTYPE html>
<html lang={ translate.Language(ctx) }> <html lang={ translate.Language(ctx) }>
@Head(cfg, title) @head(cfg, title)
<body class="bg-black dark" hx-boost="true" hx-target="main" hx-ext="head-support,preload,preload-links"> <body class="bg-black dark" hx-boost:inherited="true" hx-target:inherited="body">
<header @body() {
class="fixed z-50 mix-blend-difference p-1 w-full flex justify-between items-start"
>
<a class="text-white text-2xl" href={ translate.Path(ctx, "") } preload="mouseover" preload-images="true">schreifuchs.ch</a>
<nav class="flex gap-2 p-2">
for _, lang := range translate.SupportedLanguages(ctx) {
<a
href={ translate.PathForLang(ctx, lang, strings.Split(templer.Path(ctx), "/")...) }
hreflang={ lang }
data-no-translate
preload="mouseover"
class="text-white hover:underline uppercase"
>
{ lang }
</a>
}
</nav>
</header>
<main class="text-white">
{ children... } { children... }
</main> }
</body> </body>
</html> </html>
} }
templ Page(cfg config.Cfg, title string) {
@head(cfg, title)
@body() {
{ children... }
}
}
templ Component() {
for _, h := range templer.GetHeader(ctx) {
@templ.Raw(h)
}
{ children... }
}
templ body() {
<header
class="fixed z-50 mix-blend-difference p-1 w-full flex justify-between items-start"
>
<a class="text-white text-2xl" href={ translate.Path(ctx, "") } hx-preload="mouseover" preload-images="true">schreifuchs.ch</a>
<nav class="flex gap-2 p-2">
for _, lang := range translate.SupportedLanguages(ctx) {
<a
href={ translate.PathForLang(ctx, lang, strings.Split(templer.Path(ctx), "/")...) }
hreflang={ lang }
data-no-translate
hx-preload="mouseover"
class="text-white hover:underline uppercase"
>
{ lang }
</a>
}
</nav>
</header>
<main class="text-white">
{ children... }
</main>
}
+13 -7
View File
@@ -2,7 +2,6 @@ package layouts
import ( import (
"context" "context"
"fmt"
"io" "io"
"log/slog" "log/slog"
"net/http" "net/http"
@@ -22,12 +21,9 @@ func New(cfg config.Cfg) *Layouts {
} }
} }
// Render renders a page. If the query paramenter "c" is set // RenderPage renders a page. If the query paramenter HX-Request is set
// the component gets rendered without the layout // the component gets rendered without the layout
func (l *Layouts) Render(ctx context.Context, w io.Writer, r *http.Request, component templ.Component) { func (l *Layouts) RenderPage(ctx context.Context, w io.Writer, r *http.Request, component templ.Component) {
withLayout := !r.URL.Query().Has("c")
fmt.Println("layout: ", withLayout)
title := templer.GetTitle(ctx, "Schreifuchs") title := templer.GetTitle(ctx, "Schreifuchs")
slog.Debug("HX-Request", "value", r.Header.Get("HX-Request")) slog.Debug("HX-Request", "value", r.Header.Get("HX-Request"))
@@ -36,10 +32,20 @@ func (l *Layouts) Render(ctx context.Context, w io.Writer, r *http.Request, comp
component = Base(l.cfg, title) component = Base(l.cfg, title)
} else { } else {
ctx = templ.WithChildren(ctx, component) ctx = templ.WithChildren(ctx, component)
component = Head(l.cfg, title) component = Page(l.cfg, title)
} }
if err := component.Render(ctx, w); err != nil { if err := component.Render(ctx, w); err != nil {
slog.Error("error while rendering component", "err", err, "ctx", ctx) slog.Error("error while rendering component", "err", err, "ctx", ctx)
} }
} }
// RenderComponent renders a component alone.
func (l *Layouts) RenderComponent(ctx context.Context, w io.Writer, r *http.Request, component templ.Component) {
ctx = templ.WithChildren(ctx, component)
component = Component()
if err := component.Render(ctx, w); err != nil {
slog.Error("error while rendering component", "err", err, "ctx", ctx)
}
}
+6 -2
View File
@@ -54,7 +54,9 @@ templ GallerySet(g gallery.Set) {
</div> </div>
} }
// Define a templ script to hold complex post-load behavior
templ GalleryImage(image string, next *gallery.Image, prev *gallery.Image) { templ GalleryImage(image string, next *gallery.Image, prev *gallery.Image) {
<link rel="preload" href={ "/images/" + image + "?w=800" } as="image"/>
<div class="relative w-10/12 h-10/12 flex items-center justify-center overflow-hidden "> <div class="relative w-10/12 h-10/12 flex items-center justify-center overflow-hidden ">
<!-- 1. Preloaded Base Image (w=800) --> <!-- 1. Preloaded Base Image (w=800) -->
<img <img
@@ -68,8 +70,8 @@ templ GalleryImage(image string, next *gallery.Image, prev *gallery.Image) {
srcset={ "/images/" + image + "?w=400 400w, /images/" + image + "?w=800 800w, /images/" + image + "?w=1200 1200w, /images/" + image + "?w=1600 1600w" } srcset={ "/images/" + image + "?w=400 400w, /images/" + image + "?w=800 800w, /images/" + image + "?w=1200 1200w, /images/" + image + "?w=1600 1600w" }
sizes="(max-width: 600px) 400px, (max-width: 1200px) 800px, 1200px" sizes="(max-width: 600px) 400px, (max-width: 1200px) 800px, 1200px"
alt="Image description" alt="Image description"
onload="this.classList.remove('opacity-0')" onload="this.decode().then(() => {this.classList.remove('opacity-0')}).catch(() => {})"
class="absolute inset-0 w-full h-full object-contain transition-opacity duration-300" class="absolute inset-0 w-full h-full object-contain transition-opacity duration-900 opacity-0"
/> />
<!-- 3. Navigation Controls --> <!-- 3. Navigation Controls -->
if prev != nil { if prev != nil {
@@ -77,6 +79,7 @@ templ GalleryImage(image string, next *gallery.Image, prev *gallery.Image) {
hx-get={ templer.RelativePath(ctx, "../"+prev.UID) } hx-get={ templer.RelativePath(ctx, "../"+prev.UID) }
hx-target={ "#" + detailsContentID } hx-target={ "#" + detailsContentID }
hx-swap="innerHTML" hx-swap="innerHTML"
hx-preload="mousedown"
class="absolute left-4 top-1/2 -translate-y-1/2 z-10 p-2 text-white" class="absolute left-4 top-1/2 -translate-y-1/2 z-10 p-2 text-white"
> >
&lt; &lt;
@@ -87,6 +90,7 @@ templ GalleryImage(image string, next *gallery.Image, prev *gallery.Image) {
hx-get={ templer.RelativePath(ctx, "../"+next.UID) } hx-get={ templer.RelativePath(ctx, "../"+next.UID) }
hx-target={ "#" + detailsContentID } hx-target={ "#" + detailsContentID }
hx-swap="innerHTML" hx-swap="innerHTML"
hx-preload="mouseover"
class="absolute right-4 top-1/2 -translate-y-1/2 z-10 p-2 text-white" class="absolute right-4 top-1/2 -translate-y-1/2 z-10 p-2 text-white"
> >
&gt; &gt;
+1 -2
View File
@@ -12,8 +12,7 @@ templ Home(pages []page.PageHeader) {
<a <a
class="flex items-center justify-center h-full " class="flex items-center justify-center h-full "
href={ translate.Path(ctx, "/"+page.UID) } href={ translate.Path(ctx, "/"+page.UID) }
preload="mouseover" hx-preload="mouseover"
hx-load-links="true"
> >
<h2 <h2
class="text-3xl font-bold text-white hover:underline text-shadow-lg" class="text-3xl font-bold text-white hover:underline text-shadow-lg"
+61 -48
View File
@@ -1,63 +1,76 @@
/** /**
* @file preload-links.js * @file preload-links.js
* @description HTMX extension to extract <link rel="preload"> from preloaded HTML responses. * @description HTMX helper to extract <link rel=preload> and image hints from fetched HTML responses.
* @generated By opencode (AI Agent) * @generated By opencode (AI Agent)
* @model gemini-3-flash-preview * @model gemini-3-flash-preview
* @date Thu Mar 19 2026 * @date Tue Sep 29 2026
*/ */
(function () { (function () {
// Save the original XHR methods const originalFetch = window.fetch;
const originalOpen = XMLHttpRequest.prototype.open;
const originalSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
const originalSend = XMLHttpRequest.prototype.send;
// We need to track which XHRs are preloads window.fetch = function (input, init) {
XMLHttpRequest.prototype.open = function () { const responsePromise = originalFetch.apply(this, arguments);
this._isHtmxPreload = false;
return originalOpen.apply(this, arguments);
};
XMLHttpRequest.prototype.setRequestHeader = function (header, value) { let method = init?.method;
if (header.toLowerCase() === "hx-preloaded" && value === "true") { if (!method && input instanceof Request) method = input.method;
this._isHtmxPreload = true; method = (method || "GET").toUpperCase();
}
return originalSetRequestHeader.apply(this, arguments);
};
XMLHttpRequest.prototype.send = function () { responsePromise
if (this._isHtmxPreload) { .then((response) => {
this.addEventListener("load", function () { const contentType = response.headers.get("content-type") || "";
// Parse the response text as HTML if (method !== "GET" || !contentType.includes("text/html")) return;
const parser = new DOMParser();
const doc = parser.parseFromString(this.responseText, "text/html");
// Find all preload links return response
const links = doc.querySelectorAll('link[rel="preload"]'); .clone()
.text()
.then((html) => {
const doc = new DOMParser().parseFromString(html, "text/html");
links.forEach((link) => { doc.querySelectorAll('link[rel="preload"]').forEach((link) => {
const href = link.getAttribute("href"); const href = link.getAttribute("href");
// Only inject if the current document doesn't already have it if (!href) return;
if ( if (
href && document.head.querySelector(
!document.querySelector(`link[rel="preload"][href="${href}"]`) `link[rel="preload"][href="${href}"]`,
) { )
const newLink = document.createElement("link"); )
newLink.rel = "preload"; return;
newLink.href = href;
// Copy relevant attributes const newLink = document.createElement("link");
["as", "type", "media", "fetchpriority", "crossorigin"].forEach( newLink.rel = "preload";
(attr) => { newLink.href = href;
const val = link.getAttribute(attr);
if (val) newLink.setAttribute(attr, val);
},
);
document.head.appendChild(newLink); ["as", "type", "media", "fetchpriority", "crossorigin"].forEach(
} (attr) => {
}); const val = link.getAttribute(attr);
}); if (val) newLink.setAttribute(attr, val);
} },
return originalSend.apply(this, arguments); );
document.head.appendChild(newLink);
});
doc.querySelectorAll("img[src]").forEach((img) => {
const href = img.getAttribute("src");
if (!href) return;
if (
document.head.querySelector(
`link[rel="preload"][href="${href}"][as="image"]`,
)
)
return;
const newLink = document.createElement("link");
newLink.rel = "preload";
newLink.as = "image";
newLink.href = href;
document.head.appendChild(newLink);
});
});
})
.catch(() => {});
return responsePromise;
}; };
})(); })();
+1 -1
View File
@@ -1,2 +1,2 @@
/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */ /*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-duration:initial;--tw-text-shadow-color:initial;--tw-text-shadow-alpha:100%}}}@layer theme{:root,:host{--font-sans:"Outfit","outfit",sans-serif;--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-6xl:72rem;--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--font-weight-bold:700;--text-shadow-lg:0px 0px 20px #000;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components{.prose img{max-height:90vh;margin-left:auto;margin-right:auto;display:block}}@layer utilities{.pLine truncated @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-duration:initial;--tw-text-shadow-color:initial;--tw-text-shadow-alpha:100%}}}@layer theme{:root,:host{--font-sans:"Outfit","outfit",sans-serif;--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-6xl:72rem;--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--font-weight-bold:700;--text-shadow-lg:0px 0px 20px #000;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components{.prose img{max-height:90vh;margin-left:auto;margin-right:auto;display:block}}@layer utilities{.pLine truncated
-1
View File
@@ -1 +0,0 @@
(function(){var H=null;function M(){}function a(e,t){if(e&&e.indexOf("<head")>-1){const g=document.createElement("html");var r=e.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim,"");var a=r.match(/(<head(\s[^>]*>|>)([\s\S]*?)<\/head>)/im);if(a){var n=[];var d=[];var o=[];var i=[];g.innerHTML=a;var h=g.querySelector("head");var m=document.head;if(h==null){return}else{var s=new Map;for(const p of h.children){s.set(p.outerHTML,p)}}var u=H.getAttributeValue(h,"hx-head")||t;for(const x of m.children){var l=s.has(x.outerHTML);var f=x.getAttribute("hx-head")==="re-eval";var v=H.getAttributeValue(x,"hx-preserve")==="true";if(l||v){if(f){d.push(x)}else{s.delete(x.outerHTML);o.push(x)}}else{if(u==="append"){if(f){d.push(x);i.push(x)}}else{if(H.triggerEvent(document.body,"htmx:removingHeadElement",{headElement:x})!==false){d.push(x)}}}}i.push(...s.values());M("to append: ",i);for(const E of i){M("adding: ",E);var c=document.createRange().createContextualFragment(E.outerHTML);M(c);if(H.triggerEvent(document.body,"htmx:addingHeadElement",{headElement:c})!==false){m.appendChild(c);n.push(c)}}for(const b of d){if(H.triggerEvent(document.body,"htmx:removingHeadElement",{headElement:b})!==false){m.removeChild(b)}}H.triggerEvent(document.body,"htmx:afterHeadMerge",{added:n,kept:o,removed:d})}}}htmx.defineExtension("head-support",{init:function(e){H=e;htmx.on("htmx:afterSwap",function(e){let t=e.detail.xhr;if(t){var r=t.response;if(H.triggerEvent(document.body,"htmx:beforeHeadMerge",e.detail)){a(r,e.detail.boosted?"merge":"append")}}});htmx.on("htmx:historyRestore",function(e){if(H.triggerEvent(document.body,"htmx:beforeHeadMerge",e.detail)){if(e.detail.cacheMiss){a(e.detail.serverResponse,"merge")}else{a(e.detail.item.head,"merge")}}});htmx.on("htmx:historyItemCreated",function(e){var t=e.detail.item;t.head=document.head.outerHTML})}})})();
+1 -1
View File
@@ -1 +1 @@
var htmx=function(){"use strict";const Q={onLoad:null,process:null,on:null,off:null,trigger:null,ajax:null,find:null,findAll:null,closest:null,values:function(e,t){const n=dn(e,t||"post");return n.values},remove:null,addClass:null,removeClass:null,toggleClass:null,takeClass:null,swap:null,defineExtension:null,removeExtension:null,logAll:null,logNone:null,logger:null,config:{historyEnabled:true,historyCacheSize:10,refreshOnHistoryMiss:false,defaultSwapStyle:"innerHTML",defaultSwapDelay:0,defaultSettleDelay:20,includeIndicatorStyles:true,indicatorClass:"htmx-indicator",requestClass:"htmx-request",addedClass:"htmx-added",settlingClass:"htmx-settling",swappingClass:"htmx-swapping",allowEval:true,allowScriptTags:true,inlineScriptNonce:"",inlineStyleNonce:"",attributesToSettle:["class","style","width","height"],withCredentials:false,timeout:0,wsReconnectDelay:"full-jitter",wsBinaryType:"blob",disableSelector:"[hx-disable], [data-hx-disable]",scrollBehavior:"instant",defaultFocusScroll:false,getCacheBusterParam:false,globalViewTransitions:false,methodsThatUseUrlParams:["get","delete"],selfRequestsOnly:true,ignoreTitle:false,scrollIntoViewOnBoost:true,triggerSpecsCache:null,disableInheritance:false,responseHandling:[{code:"204",swap:false},{code:"[23]..",swap:true},{code:"[45]..",swap:false,error:true}],allowNestedOobSwaps:true,historyRestoreAsHxRequest:true,reportValidityOfForms:false},parseInterval:null,location:location,_:null,version:"2.0.8"};Q.onLoad=V;Q.process=Ft;Q.on=xe;Q.off=be;Q.trigger=ae;Q.ajax=Ln;Q.find=f;Q.findAll=x;Q.closest=g;Q.remove=_;Q.addClass=K;Q.removeClass=G;Q.toggleClass=W;Q.takeClass=Z;Q.swap=ze;Q.defineExtension=_n;Q.removeExtension=zn;Q.logAll=j;Q.logNone=$;Q.parseInterval=d;Q._=e;const n={addTriggerHandler:St,bodyContains:se,canAccessLocalStorage:X,findThisElement:Se,filterValues:yn,swap:ze,hasAttribute:s,getAttributeValue:a,getClosestAttributeValue:ne,getClosestMatch:q,getExpressionVars:Rn,getHeaders:mn,getInputValues:dn,getInternalData:oe,getSwapSpecification:bn,getTriggerSpecs:st,getTarget:Ee,makeFragment:D,mergeObjects:le,makeSettleInfo:Sn,oobSwap:Te,querySelectorExt:ue,settleImmediately:Yt,shouldCancel:ht,triggerEvent:ae,triggerErrorEvent:fe,withExtensions:Vt};const de=["get","post","put","delete","patch"];const R=de.map(function(e){return"[hx-"+e+"], [data-hx-"+e+"]"}).join(", ");function d(e){if(e==undefined){return undefined}let t=NaN;if(e.slice(-2)=="ms"){t=parseFloat(e.slice(0,-2))}else if(e.slice(-1)=="s"){t=parseFloat(e.slice(0,-1))*1e3}else if(e.slice(-1)=="m"){t=parseFloat(e.slice(0,-1))*1e3*60}else{t=parseFloat(e)}return isNaN(t)?undefined:t}function ee(e,t){return e instanceof Element&&e.getAttribute(t)}function s(e,t){return!!e.hasAttribute&&(e.hasAttribute(t)||e.hasAttribute("data-"+t))}function a(e,t){return ee(e,t)||ee(e,"data-"+t)}function u(e){const t=e.parentElement;if(!t&&e.parentNode instanceof ShadowRoot)return e.parentNode;return t}function te(){return document}function y(e,t){return e.getRootNode?e.getRootNode({composed:t}):te()}function q(e,t){while(e&&!t(e)){e=u(e)}return e||null}function o(e,t,n){const r=a(t,n);const o=a(t,"hx-disinherit");var i=a(t,"hx-inherit");if(e!==t){if(Q.config.disableInheritance){if(i&&(i==="*"||i.split(" ").indexOf(n)>=0)){return r}else{return null}}if(o&&(o==="*"||o.split(" ").indexOf(n)>=0)){return"unset"}}return r}function ne(t,n){let r=null;q(t,function(e){return!!(r=o(t,ce(e),n))});if(r!=="unset"){return r}}function h(e,t){return e instanceof Element&&e.matches(t)}function A(e){const t=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i;const n=t.exec(e);if(n){return n[1].toLowerCase()}else{return""}}function L(e){if("parseHTMLUnsafe"in Document){return Document.parseHTMLUnsafe(e)}const t=new DOMParser;return t.parseFromString(e,"text/html")}function N(e,t){while(t.childNodes.length>0){e.append(t.childNodes[0])}}function r(e){const t=te().createElement("script");ie(e.attributes,function(e){t.setAttribute(e.name,e.value)});t.textContent=e.textContent;t.async=false;if(Q.config.inlineScriptNonce){t.nonce=Q.config.inlineScriptNonce}return t}function i(e){return e.matches("script")&&(e.type==="text/javascript"||e.type==="module"||e.type==="")}function I(e){Array.from(e.querySelectorAll("script")).forEach(e=>{if(i(e)){const t=r(e);const n=e.parentNode;try{n.insertBefore(t,e)}catch(e){H(e)}finally{e.remove()}}})}function D(e){const t=e.replace(/<head(\s[^>]*)?>[\s\S]*?<\/head>/i,"");const n=A(t);let r;if(n==="html"){r=new DocumentFragment;const i=L(e);N(r,i.body);r.title=i.title}else if(n==="body"){r=new DocumentFragment;const i=L(t);N(r,i.body);r.title=i.title}else{const i=L('<body><template class="internal-htmx-wrapper">'+t+"</template></body>");r=i.querySelector("template").content;r.title=i.title;var o=r.querySelector("title");if(o&&o.parentNode===r){o.remove();r.title=o.innerText}}if(r){if(Q.config.allowScriptTags){I(r)}else{r.querySelectorAll("script").forEach(e=>e.remove())}}return r}function re(e){if(e){e()}}function t(e,t){return Object.protoLine truncated var htmx=(()=>{const e={parse(t){if(!t)return{};if(t.startsWith("{"))return JSON.parse(t);let r=/(?:"([^"]+)"|'([^']+)'|([^\s,:]+))(?:\s*:\s*(?:"([^"]*)"|'([^']*)'|<((?:[^/]|\/(?!>))+)\/>|([^\s,]+)))?(?=\s|,|$)/g,i={};for(let s of t.matchAll(r)){let[,t,r,n,o,a,l,h]=s,c=t??r??n,u=(o??a??l??h??"true").trim();try{u=JSON.parse(u)}catch{}let d=n?.includes("."),f=d?c.split(".").reduceRight((e,t)=>({[t]:e}),u):{[c]:u};e.merge(f,i)}return i},split:e=>e.split(/,(?![^\[]*\])(?![^(]*\))(?![^<]*\/>)(?=(?:[^"']|"[^"]*"|'[^']*')*$)/),merge(t,r){"string"==typeof t&&(t=e.parse(t));for(let[i,s]of Object.entries(t)){if(["__proto__","constructor","prototype"].includes(i))continue;let t=s?.constructor===Object,n=r[i]?.constructor===Object;t&&n?e.merge(s,r[i]):r[i]=s}return r}};class t{#e=null;#t=[];admit(e,t,r){if(!this.#e)return this.#e={strategy:e,abort:r},"run";if("replace"===e||"abort"!==e&&"abort"===this.#e.strategy)return this.#t=[],this.#e.abort?.(),this.#e={strategy:e,abort:r},"run";if("queue all"===e)this.#t.push(t);else if("queue last"===e)this.#t=[t];else{if("abort"===e||"drop"===e||0!==this.#t.length)return"dropped";this.#t.push(t)}return"queued"}continue(){this.#e=null,this.#t.shift()?.()}abort(){this.#e?.abort?.()}}return new class{#r=e;#i=new Map;#s="";#n=new Set;_loc=window.location;#o;#a=Function;#l=Object.getPrototypeOf(async function(){}).constructor;#h={createHTML:e=>e,createScript:e=>e};#c;#u="a,form";#d=["get","post","put","patch","delete","query"];#f;#m;#p;#g;#x;constructor(){this.#b(),this.#y(),this.#c=this.#S("[hx-action],[hx-get],[hx-post],[hx-put],[hx-patch],[hx-delete],[hx-query]"),this.#f=(new XPathEvaluator).createExpression(`.//*[@*[${this.#v("hx-on").map(e=>`starts-with(name(), "${e}")`).join(" or ")}]]`),this.#o={HCON:e,attributeValue:this.#E.bind(this),parseTriggerSpecs:this.#w.bind(this),determineMethodAndAction:this.#A.bind(this),createRequestContext:this.#T.bind(this),collectFormData:this.#C.bind(this),getAttributeObject:this.#q.bind(this),insertContent:this.#N.bind(this),morph:this.#I.bind(this),isSoftMatch:this.#M.bind(this),initSecurity:(e,t,r)=>{e&&(this.#h=e),t&&(this.#a=t),r&&(this.#l=r)},onTrigger:this.#O.bind(this),htmxProp:this.#H.bind(this),triggerHtmxEvent:this.#L.bind(this),executeJavaScript:this.#k.bind(this)};let t=()=>this.initialize();"loading"===document.readyState?document.addEventListener("DOMContentLoaded",t):setTimeout(t)}#b(){this.version="4.0.0",this.config={logAll:!1,prefix:"data-hx-",transitions:!1,history:!0,mode:"same-origin",defaultSwap:"innerHTML",defaultFocusScroll:!1,indicatorClass:"htmx-indicator",requestClass:"htmx-request",includeIndicatorCSS:!0,defaultTimeout:6e4,extensions:"",morphIgnore:["data-htmx-powered"],morphSkip:"[hx-morph-skip]",morphSkipChildren:"[hx-morph-skip-children]",morphScanLimit:10,noSwap:[204,304],implicitInheritance:!1,defaultSettleDelay:1,allowEmptySwapAfterOOB:!1};let t=document.querySelector('meta[name="htmx-config"]');t&&e.merge(t.content,this.config),this.#s=this.config.extensions}#y(){if(!1!==this.config.includeIndicatorCSS){let e=this.config.indicatorClass,t=this.config.requestClass,r=new CSSStyleSheet;r.replaceSync(`.${e}{opacity:0;visibility: hidden} .${t} .${e}, .${t}.${e}{opacity:1;visibility: visible;transition: opacity 200ms ease-in}`),document.adoptedStyleSheets=[...document.adoptedStyleSheets,r]}}registerExtension(e,t){return!(this.#s&&!this.#s.split(/,\s*/).includes(e))&&(!this.#n.has(e)&&(this.#n.add(e),t.init&&t.init(this.#o),void Object.entries(t).forEach(([e,t])=>{this.#i.get(e)?.push(t)||this.#i.set(e,[t])})))}#V(e){let t=this.config.prefix;return!e.closest||null!=e.closest("[hx-ignore]")||t&&null!=e.closest(`[${t}ignore]`)}#P(e,t){let r=this.config.prefix;return e.getAttribute(t)??(r?e.getAttribute(t.replace("hx-",r)):null)}#R(e,t){let r=this.config.prefix&&t.replace("hx-",this.config.prefix);return e.hasAttribute(t)?t:r&&e.hasAttribute(r)?r:null}#S(e){return this.#v(e).join(",")}#v(e){let t=[e];return this.config.prefix&&t.push(e.replaceAll("hx-",this.config.prefix)),t}#j(e,t){let r=[...e.querySelectorAll?.(t)??[]];return e.matches?.(t)&&r.unshift(e),r}#B(e){return"before"===e?"beforebegin":"after"===e?"afterend":"prepend"===e?"afterbegin":"append"===e?"beforeend":e}#F(e,t){let r=[];return this.#E(e,t,void 0,(e,t)=>{e?.split(/\s*[,:]\s*/).includes("this")&&r.push(t)}),r}#E(e,t,r,i){t=this.#z(t);let s=this.#z(":inherited"),n=this.#z(":append"),o=this.#P(e,t)??this.#P(e,t+s);if(null!=o)return i?i(o,e):o;let a=CSS.escape(this.config.implicitInheritance?t:t+s),l=CSS.escape(t+s+n),h=this.#S(`[${a}],[${l}]`),c=this.#R(e,t+n)??this.#R(e,t+s+n);if(c){let r=e.getAttribute(c),s=e.parentNode?.closest?.(h);if(i&&i(r,e),s){let e=this.#E(s,t,void 0,i);return e?(e+","+r).replace(/[{}]/g,""):r}return r}let u=e.parentNode?.closest?.(h);return u?(o=this.#E(u,t,void 0,i),!i&&o&&this.config.implicitInheritance&&this.#_(e,"htmx:after:implicitInheritance",{elt:e,name:t,parent:u}),o):r}#w(t){return e.split(t).flatMap(t=>{let[,r,i]=t.maLine truncated
+1
View File
@@ -0,0 +1 @@
!function(){let e;function t(e){let t=document.createElement(e.tagName);for(const r of e.attributes)t.setAttribute(r.name,r.value);return t.textContent=e.textContent,"LINK"===e.tagName&&"stylesheet"===e.rel?new Promise(e=>{t.onload=e,t.onerror=e,document.head.appendChild(t)}):"SCRIPT"!==e.tagName||!e.src||e.async||e.defer?(document.head.appendChild(t),e._preloadHint&&t.addEventListener("load",()=>e._preloadHint.remove(),{once:!0}),null):new Promise((e,r)=>{t.onload=e,t.onerror=r,document.head.appendChild(t)})}async function r(r,d){if(r&&r.indexOf("<head")>-1){const o=document.createElement("html");let a=r.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim,"").match(/(<head(\s[^>]*>|>)([\s\S]*?)<\/head>)/im);if(a){let r=[],n=[],h=[],s=[],m=[];o.innerHTML=a;let l=o.querySelector("head"),i=document.head;if(null==l)return[];let c=new Map;for(const e of l.children)c.set(e.outerHTML,e);let u=e.attributeValue(l,"hx-head")||d;for(const t of i.children){let r=c.has(t.outerHTML),d="re-eval"===t.getAttribute("hx-head"),o="true"===e.attributeValue(t,"hx-preserve");r||o?d?n.push(t):(c.delete(t.outerHTML),h.push(t)):"append"===u?d&&(n.push(t),s.push(t)):!1!==htmx.trigger(document.body,"htmx:head:before:remove",{headElement:t})&&n.push(t)}s.push(...c.values());for(const e of s)if("SCRIPT"===e.tagName&&e.defer){if(m.push(e),e.src){let t=document.createElement("link");t.rel="module"===e.type?"modulepreload":"preload",t.as="script",t.href=e.src,document.head.appendChild(t),e._preloadHint=t}}else!1!==htmx.trigger(document.body,"htmx:head:before:add",{headElement:e})&&(await t(e),r.push(e));for(const e of n)!1!==htmx.trigger(document.body,"htmx:head:before:remove",{headElement:e})&&i.removeChild(e);return htmx.trigger(document.body,"htmx:head:after:merge",{added:r,kept:h,removed:n}),m}}return[]}htmx.registerExtension("hx-head",{init:t=>{e=t},htmx_before_response:(e,t)=>{let d=t.ctx,o=d.target===document.body?"merge":"append";if(htmx.trigger(document.body,"htmx:head:before:merge",t)){let e=d.response.raw.text.bind(d.response.raw);d.response.raw.text=async()=>{let t=await e();return d._deferredHeadScripts=await r(t,o),t}}},htmx_after_swap:(e,r)=>{for(const e of r.ctx._deferredHeadScripts||[])t(e)},htmx_history_cache_before_restore:(e,t)=>{t.head&&(t.ready=r(t.head,"merge").then(e=>{t._deferredHeadScripts=e}))},htmx_history_cache_after_restore:(e,r)=>{for(const e of r._deferredHeadScripts||[])t(e)}})}();
+1
View File
@@ -0,0 +1 @@
(()=>{let e;htmx.registerExtension("preload",{init:t=>{e=t},htmx_after_init:t=>{!function(t){let r=e.attributeValue(t,"hx-preload");if(null==r&&!t._htmx?.boosted)return;let o=[],a=5e3;if(r){let t=e.parseTriggerSpecs(r);if(0===t.length)return;for(const e of t)o.push(e.name),e.timeout&&(a=htmx.parseInterval(e.timeout))}else{let r=t._htmx?.boosted&&"A"===t.tagName,l=null!=e.attributeValue(t,"hx-get");if(!r&&!l)return;if(r&&!1===htmx.config?.preload?.autoBoost)return;htmx.config?.preload?.boostTimeout&&(a=htmx.parseInterval(htmx.config.preload.boostTimeout)),o.push(htmx.config?.preload?.boostEvent||"mousedown"),o.push("touchstart")}let l=async r=>{let{method:o}=e.determineMethodAndAction(t,r);if("GET"!==o)return;if(t._htmx?.preload)return;let l=e.createRequestContext(t,r),n=t.form||t.closest("form"),i=e.collectFormData(t,n,r.submitter),h=e.getAttributeObject(t,"hx-vals",e=>{for(let t in e)i.set(t,e[t])});h&&await h;let s=l.request.action.replace?.(/#.*$/,""),m=new URLSearchParams(i);m.size&&(s+=(/\?/.test(s)?"&":"?")+m);let p=new URL(s,location.href);t._htmx.preload={prefetch:fetch(s,l.request),action:p.origin===location.origin?p.pathname+p.search:p.href,expiresAt:Date.now()+a};try{await t._htmx.preload.prefetch}catch(e){delete t._htmx.preload}};for(let e of o)t.addEventListener(e,l,{passive:!0});t._htmx.preloadListener=l,t._htmx.preloadEvents=o}(t)},htmx_before_request:(e,t)=>{let{ctx:r}=t;if(e._htmx?.preload&&e._htmx.preload.action===r.request.action&&Date.now()<e._htmx.preload.expiresAt){let t=e._htmx.preload.prefetch;r.fetch=()=>t,delete e._htmx.preload}else e._htmx&&delete e._htmx.preload},htmx_before_cleanup:e=>{if(e._htmx?.preloadListener)for(let t of e._htmx.preloadEvents)e.removeEventListener(t,e._htmx.preloadListener)}})})();
+1 -1
View File
@@ -1 +1 @@
(function(){const p=XMLHttpRequest.prototype.open,a=XMLHttpRequest.prototype.setRequestHeader,l=XMLHttpRequest.prototype.send;XMLHttpRequest.prototype.open=function(){return this._isHtmxPreload=!1,p.apply(this,arguments)},XMLHttpRequest.prototype.setRequestHeader=function(r,o){return r.toLowerCase()==="hx-preloaded"&&o==="true"&&(this._isHtmxPreload=!0),a.apply(this,arguments)},XMLHttpRequest.prototype.send=function(){return this._isHtmxPreload&&this.addEventListener("load",function(){new DOMParser().parseFromString(this.responseText,"text/html").querySelectorAll('link[rel="preload"]').forEach(n=>{const t=n.getAttribute("href");if(t&&!document.querySelector(`link[rel="preload"][href="${t}"]`)){const e=document.createElement("link");e.rel="preload",e.href=t,["as","type","media","fetchpriority","crossorigin"].forEach(s=>{const i=n.getAttribute(s);i&&e.setAttribute(s,i)}),document.head.appendChild(e)}})}),l.apply(this,arguments)}})(); (function(){const d=window.fetch;window.fetch=function(o,s){const c=d.apply(this,arguments);let r=s?.method;return!r&&o instanceof Request&&(r=o.method),r=(r||"GET").toUpperCase(),c.then(i=>{const u=i.headers.get("content-type")||"";if(!(r!=="GET"||!u.includes("text/html")))return i.clone().text().then(f=>{const l=new DOMParser().parseFromString(f,"text/html");l.querySelectorAll('link[rel="preload"]').forEach(n=>{const t=n.getAttribute("href");if(!t||document.head.querySelector(`link[rel="preload"][href="${t}"]`))return;const e=document.createElement("link");e.rel="preload",e.href=t,["as","type","media","fetchpriority","crossorigin"].forEach(a=>{const h=n.getAttribute(a);h&&e.setAttribute(a,h)}),document.head.appendChild(e)}),l.querySelectorAll("img[src]").forEach(n=>{const t=n.getAttribute("src");if(!t||document.head.querySelector(`link[rel="preload"][href="${t}"][as="image"]`))return;const e=document.createElement("link");e.rel="preload",e.as="image",e.href=t,document.head.appendChild(e)})})}).catch(()=>{}),c}})();
-1
View File
@@ -1 +0,0 @@
(function(){htmx.defineExtension("preload",{onEvent:function(e,t){if(e==="htmx:afterProcessNode"){const n=t.target||t.detail.elt;const r=[...n.hasAttribute("preload")?[n]:[],...n.querySelectorAll("[preload]")];r.forEach(function(e){i(e);e.querySelectorAll("[href],[hx-get],[data-hx-get]").forEach(i)});return}if(e==="htmx:beforeRequest"){const o=t.detail.requestConfig.headers;if(!("HX-Preloaded"in o&&o["HX-Preloaded"]==="true")){return}t.preventDefault();const a=t.detail.xhr;a.onload=function(){s(t.detail.elt,a.responseText)};a.onerror=null;a.onabort=null;a.ontimeout=null;a.send()}}});function i(t){if(t.preloadState!==undefined){return}if(!l(t)){return}if(t instanceof HTMLFormElement){const o=t;if(!(o.hasAttribute("method")&&o.method==="get"||o.hasAttribute("hx-get")||o.hasAttribute("hx-data-get"))){return}for(let e=0;e<o.elements.length;e++){const a=o.elements.item(e);i(a);if("labels"in a){a.labels.forEach(i)}}return}let e=p(t,"preload");t.preloadAlways=e&&e.includes("always");if(t.preloadAlways){e=e.replace("always","").trim()}let n=e||"mousedown";const r=n==="mouseover";t.addEventListener(n,u(t,r),{passive:true});if(n==="mousedown"||n==="mouseover"){t.addEventListener("touchstart",u(t),{passive:true})}if(n==="mouseover"){t.addEventListener("mouseout",function(e){if(e.target===t&&t.preloadState==="TIMEOUT"){t.preloadState="READY"}},{passive:true})}t.preloadState="READY";htmx.trigger(t,"preload:init")}function u(t,n=false){return function(){if(t.preloadState!=="READY"){return}if(n){t.preloadState="TIMEOUT";const e=100;window.setTimeout(function(){if(t.preloadState==="TIMEOUT"){t.preloadState="READY";r(t)}},e);return}r(t)}}function r(n){if(n.preloadState!=="READY"){return}n.preloadState="LOADING";const e=n.getAttribute("hx-get")||n.getAttribute("data-hx-get");if(e){m(e,n);return}const t=p(n,"hx-boost")==="true";if(n.hasAttribute("href")){const r=n.getAttribute("href");if(t){m(r,n)}else{h(r,n)}return}if(g(n)){const r=n.form.getAttribute("action")||n.form.getAttribute("hx-get")||n.form.getAttribute("data-hx-get");const o=htmx.values(n.form);const a=!(n.form.getAttribute("hx-get")||n.form.getAttribute("data-hx-get")||t);const i=a?h:m;if(n.type==="submit"){i(r,n.form,o);return}const u=n.name||n.control.name;if(n.tagName==="SELECT"){Array.from(n.options).forEach(e=>{if(e.selected)return;o.set(u,e.value);const t=d(n.form,o);i(r,n.form,t)});return}const s=n.getAttribute("type")||n.control.getAttribute("type");const l=n.value||n.control?.value;if(s==="radio"){o.set(u,l)}else if(s==="checkbox"){const c=o.getAll(u);if(c.includes(l)){o[u]=c.filter(e=>e!==l)}else{o.append(u,l)}}const f=d(n.form,o);i(r,n.form,f);return}}function d(e,t){const n=e.elements;const r=new FormData;for(let e=0;e<n.length;e++){const o=n.item(e);if(t.has(o.name)&&o.tagName==="SELECT"){r.append(o.name,t.get(o.name));continue}if(t.has(o.name)&&t.getAll(o.name).includes(o.value)){r.append(o.name,o.value)}}return r}function m(e,t,n=undefined){htmx.ajax("GET",e,{source:t,values:n,headers:{"HX-Preloaded":"true"}})}function h(e,t,n=undefined){const r=new XMLHttpRequest;if(n){e+="?"+new URLSearchParams(n.entries()).toString()}r.open("GET",e);r.setRequestHeader("HX-Preloaded","true");r.onload=function(){s(t,r.responseText)};r.send()}function s(e,t){e.preloadState=e.preloadAlways?"READY":"DONE";if(p(e,"preload-images")==="true"){document.createElement("div").innerHTML=t}}function p(e,t){if(e==undefined){return undefined}return e.getAttribute(t)||e.getAttribute("data-"+t)||p(e.parentElement,t)}function l(e){const n=["href","hx-get","data-hx-get"];const t=t=>n.some(e=>t.hasAttribute(e))||t.method==="get";const r=e.form instanceof HTMLFormElement&&t(e.form)&&g(e);if(!t(e)&&!r){return false}if(e instanceof HTMLInputElement&&e.closest("label")){return false}return true}function g(e){if(e instanceof HTMLInputElement||e instanceof HTMLButtonElement){const t=e.getAttribute("type");return["checkbox","radio","submit"].includes(t)}if(e instanceof HTMLLabelElement){return e.control&&g(e.control)}return e instanceof HTMLSelectElement}})();