Startseite filter

This commit is contained in:
Simon Martens
2025-02-19 21:37:41 +01:00
parent 0c5e19609f
commit aa7c5f4d6c
11 changed files with 529 additions and 218 deletions

4
go.mod
View File

@@ -7,7 +7,7 @@ require (
github.com/kelseyhightower/envconfig v1.4.0
github.com/mattn/go-sqlite3 v1.14.24
github.com/pocketbase/dbx v1.11.0
github.com/pocketbase/pocketbase v0.25.3
github.com/pocketbase/pocketbase v0.25.5
github.com/spf13/cobra v1.8.1
github.com/yalue/merged_fs v1.3.0
golang.org/x/text v0.22.0
@@ -69,5 +69,5 @@ require (
modernc.org/libc v1.61.13 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.8.2 // indirect
modernc.org/sqlite v1.34.5 // indirect
modernc.org/sqlite v1.35.0 // indirect
)

4
go.sum
View File

@@ -159,6 +159,8 @@ github.com/pocketbase/dbx v1.11.0 h1:LpZezioMfT3K4tLrqA55wWFw1EtH1pM4tzSVa7kgszU
github.com/pocketbase/dbx v1.11.0/go.mod h1:xXRCIAKTHMgUCyCKZm55pUOdvFziJjQfXaWKhu2vhMs=
github.com/pocketbase/pocketbase v0.25.3 h1:UaSBgxNc2aX5niwGnqZxkP5QTMJdvaFAk6uOh5qFfR4=
github.com/pocketbase/pocketbase v0.25.3/go.mod h1:A5sg1OTyrlJCvBMIdXU7p61iUYXtGsRqwPRK0OEMZ9I=
github.com/pocketbase/pocketbase v0.25.5 h1:xVI7pi1n4Htq4irVgkGMfL/S1ZuypXoQ1Ykpg0+I3j0=
github.com/pocketbase/pocketbase v0.25.5/go.mod h1:gOnPr+g/GS+iqKh5XYXycdRWVGhiHY4c1H4TGjU9DDw=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
@@ -318,6 +320,8 @@ modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.34.5 h1:Bb6SR13/fjp15jt70CL4f18JIN7p7dnMExd+UFnF15g=
modernc.org/sqlite v1.34.5/go.mod h1:YLuNmX9NKs8wRNK2ko1LW1NGYcc9FkBO69JOt1AR9JE=
modernc.org/sqlite v1.35.0 h1:yQps4fegMnZFdphtzlfQTCNBWtS0CZv48pRpW3RFHRw=
modernc.org/sqlite v1.35.0/go.mod h1:9cr2sicr7jIaWTBKQmAxQLfBv9LL0su4ZTEV+utt3ic=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=

View File

@@ -1,64 +1,154 @@
const f = "script[xslt-onload]", i = "xslt-template", m = "xslt-transformed", c = /* @__PURE__ */ new Map();
function u() {
let t = htmx.findAll(f);
for (let r of t)
p(r);
}
function p(t) {
if (t.getAttribute(m) === "true" || !t.hasAttribute(i))
return;
let r = "#" + t.getAttribute(i), o = c.get(r);
if (!o) {
let s = htmx.find(r);
if (s) {
let l = s.innerHTML ? new DOMParser().parseFromString(s.innerHTML, "application/xml") : s.contentDocument;
o = new XSLTProcessor(), o.importStylesheet(l), c.set(r, o);
} else
throw new Error("Unknown XSLT template: " + r);
}
let a = new DOMParser().parseFromString(t.innerHTML, "application/xml"), e = o.transformToFragment(a, document), n = new XMLSerializer().serializeToString(e);
t.outerHTML = n;
}
function d() {
document.querySelectorAll("template[simple]").forEach((r) => {
let o = r.getAttribute("id"), a = r.content;
customElements.define(
o,
class extends HTMLElement {
constructor() {
super(), this.appendChild(a.cloneNode(!0)), this.slots = this.querySelectorAll("slot");
}
connectedCallback() {
let e = [];
this.slots.forEach((n) => {
let s = n.getAttribute("name"), l = this.querySelector(`[slot="${s}"]`);
l && (n.replaceWith(l.cloneNode(!0)), e.push(l));
}), e.forEach((n) => {
n.remove();
});
}
}
);
});
}
function h() {
u(), htmx.on("htmx:load", function(t) {
u();
}), d();
}
function T(t) {
t || (t = window.location.href);
const r = document.querySelectorAll("nav");
if (r && r.length > 0)
for (const o of r)
o.querySelectorAll("a, [data-url]").forEach((e) => {
if (e.dataset.url && e.dataset.url !== "") {
let n = window.location.origin + e.dataset.url;
t.startsWith(n) ? e.setAttribute("aria-current", "page") : e.removeAttribute("aria-current");
} else e.href && (t.startsWith(e.href) ? e.setAttribute("aria-current", "page") : e.removeAttribute("aria-current"));
});
}
export {
T as setMenuActive,
h as setup
var p = (r) => {
throw TypeError(r);
};
var L = (r, i, t) => i.has(r) || p("Cannot " + t);
var u = (r, i, t) => (L(r, i, "read from private field"), t ? t.call(r) : i.get(r)), o = (r, i, t) => i.has(r) ? p("Cannot add the same private member more than once") : i instanceof WeakSet ? i.add(r) : i.set(r, t), h = (r, i, t, e) => (L(r, i, "write to private field"), e ? e.call(r, t) : i.set(r, t), t), f = (r, i, t) => (L(r, i, "access private method"), t);
const v = "script[xslt-onload]", g = "xslt-template", w = "xslt-transformed", A = "filter-list";
var a, c, T;
class S {
constructor() {
o(this, c);
o(this, a);
h(this, a, /* @__PURE__ */ new Map());
}
setup() {
let i = htmx.findAll(v);
for (let t of i)
f(this, c, T).call(this, t);
}
hookupHTMX() {
htmx.on("htmx:load", (i) => {
this.setup();
});
}
}
a = new WeakMap(), c = new WeakSet(), T = function(i) {
if (i.getAttribute(w) === "true" || !i.hasAttribute(g))
return;
let t = "#" + i.getAttribute(g), e = u(this, a).get(t);
if (!e) {
let d = htmx.find(t);
if (d) {
let E = d.innerHTML ? new DOMParser().parseFromString(d.innerHTML, "application/xml") : d.contentDocument;
e = new XSLTProcessor(), e.importStylesheet(E), u(this, a).set(t, e);
} else
throw new Error("Unknown XSLT template: " + t);
}
let s = new DOMParser().parseFromString(i.innerHTML, "application/xml"), b = e.transformToFragment(s, document), x = new XMLSerializer().serializeToString(b);
i.outerHTML = x;
};
var n, l, _, m;
class k extends HTMLElement {
constructor() {
super();
o(this, l);
o(this, n, !1);
this._items = [], this._url = "", this._filterstart = !1, this._placeholder = "Liste filtern...", this.render();
}
static get observedAttributes() {
return ["data-url"];
}
set items(t) {
Array.isArray(t) && (this._items = t, 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._filterstart && h(this, n, !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(t, e, s) {
t === "data-url" && e !== s && (this._url = s, this.render()), t === "data-filterstart" && e !== s && (this._filterstart = s === "true", this.render()), t === "data-placeholder" && e !== s && (this._placeholder = s, this.render());
}
onInput(t) {
t.target && t.target.tagName.toLowerCase() === "input" && (this._filter = t.target.value, this.renderList());
}
onGainFocus(t) {
t.target && t.target.tagName.toLowerCase() === "input" && (h(this, n, !1), this.renderList());
}
onLoseFocus(t) {
t.target && t.target.tagName.toLowerCase() === "input" && (t.target.value = "", this._filter = "", this._filterstart && h(this, n, !0), this.renderList());
}
onEnter(t) {
if (t.target && t.target.tagName.toLowerCase() === "input" && t.key === "Enter") {
t.preventDefault();
const e = this.querySelector("a");
e && e.click();
}
}
setHREFFunc(t) {
this.getHREF = t, this.render();
}
setLinkTextFunc(t) {
this.getLinkText = t, this.render();
}
getHREF(t) {
if (t) {
if (!t.id)
return "";
} else return "";
return t.id;
}
getLinkText(t) {
if (t) {
if (!t.name)
return "";
} else return "";
return t.name;
}
renderList() {
let t = this.querySelector("#list");
t && (t.outerHTML = this.List());
}
render() {
this.innerHTML = `
<div class="p-4 font-serif text-base">
${this.Input()}
${this.List()}
</div>
`;
}
Input() {
return `
<div class="flex w-full pb-0.5 border-b border-zinc-600">
<i class="ri-arrow-right-s-line mr-1"></i>
<div class="grow">
<input
type="text"
placeholder="${this._placeholder}"
class="w-full placeholder:italic px-2 " />
</div>
</div>
`;
}
List() {
let t = this._items;
return this._filter && (this._filterstart ? t = this._items.filter((e) => this.getLinkText(e).toLowerCase().startsWith(this._filter.toLowerCase())) : t = this._items.filter((e) => this.getLinkText(e).toLowerCase().includes(this._filter.toLowerCase()))), `
<div id="list" class="links min-h-72 max-h-60 overflow-auto border-b border-zinc-300 ${f(this, l, m).call(this)}">
${t.map(
(e, s) => `
<a
href="${this._url}${this.getHREF(e)}"
class="block px-2.5 py-0.5 hover:bg-slate-200 no-underline ${s % 2 === 0 ? "bg-stone-100" : "bg-stone-50"}"
${f(this, l, _).call(this, e)}>
${this.getLinkText(e)}
</a>
`
).join("")}
</div>
`;
}
}
n = new WeakMap(), l = new WeakSet(), _ = function(t) {
if (!t)
return "";
let e = this.getHREF(t);
return e === "" || !window.location.href.endsWith(e) ? "" : "aria-current='page'";
}, m = function() {
return u(this, n) ? "hidden" : "";
};
customElements.define(A, k);
export {
k as FilterList,
S as XSLTParseProcess
};

File diff suppressed because one or more lines are too long

View File

@@ -13,8 +13,7 @@
<nav
class="self-end font-serif font-bold flex flex-row gap-x-4 [&>a]:no-underline
[&>*]:px-1.5 [&>*]:pt-1 [&>*]:-mb-1.5"
hx-boost="false">
[&>*]:px-1.5 [&>*]:pt-1 [&>*]:-mb-1.5">
<a
href="/reihen/"
{{ if and $model.page (HasPrefix $model.page.Path "/reihe") -}}
@@ -93,7 +92,3 @@
>
</nav>
</div>
<script type="module">
import { setMenuActive } from "/assets/scripts.js";
</script>

View File

@@ -20,16 +20,13 @@
<script src="/assets/js/htmx-response-targets.js" defer></script>
<script src="/assets/js/client-side-templates.js" defer></script>
<script type="module" src="/assets/scripts.js"></script>
<link rel="stylesheet" type="text/css" href="/assets/css/fonts.css" />
<link rel="stylesheet" type="text/css" href="/assets/style.css" />
<script type="module">
import { setup } from "/assets/scripts.js";
setup();
</script>
</head>
<body class="w-full" hx-ext="response-targets" hx-boost="false">
<body class="w-full text-lg" hx-ext="response-targets" hx-boost="true">
<div class="container flex flex-col min-h-screen max-w-(--breakpoint-xl) mx-auto px-3 py-3.5">
<header class="" id="header">
{{ block "_menu" . }}
@@ -37,9 +34,7 @@
{{ end }}
</header>
<div></div>
<main class="text-lg">
<main class="">
{{ block "body" . }}
<!-- Default app body... -->
{{ end }}

View File

@@ -1,19 +1,21 @@
{{ $model := . }}
{{ if $model.letters }}
<div class="flex flex-row border-b px-3 border-zinc-300 items-end min-h-14">
<div id="alphabet" class="alphabet flex flex-row items-end" hx-swap-oob="true" hx-boost="false">
<div
class="flex flex-row border-b px-3 border-zinc-300 items-end min-h-14"
x-data="{ search : '{{ $model.search }}' }">
<div id="alphabet" class="alphabet flex flex-row items-end">
{{ range $id, $r := .letters }}
<a
class="odd:bg-stone-100 even:bg-zinc-100 mr-1 border-zinc-300 border-x border-t [&>a[aria-current='page']]:font-bold
px-2 no-underline transition-all duration-75
{{ if not $model.active -}}inactive{{- end -}}"
:class="search ? 'inactive' : 'active'"
href="?letter={{ $r }}"
{{ if eq $model.active $r }}aria-current="page"{{ end }}
hx-get="./?letter={{ $r }}"
hx-push-url="true"
hx-select="#searchcontent"
hx-target="#searchcontent"
hx-select="main"
hx-target="main"
hx-swap="outerHTML scroll:#pageheading:top"
>{{ $r }}</a
>
{{ end }}
@@ -21,28 +23,37 @@
<div class="flex-grow"></div>
<div>
<i class="ri-search-line"></i><i class="-ml-0.5 inline-block ri-arrow-right-s-line"></i>
<input
class="px-1.5 py-0.5 font-serif italic"
type="search"
name="search"
placeholder="Suchbegriff eingeben"
hx-get="./"
hx-trigger="input changed delay:=200ms, keyup[key=='Enter']"
hx-select="#searchcontent"
hx-target="#searchcontent"
autocomplete="off" />
</div>
<div id="permalink" class="font-serif ml-3 py-1" hx-swap-oob="true">
{{ if $model.search }}
<div
class="min-w-[22.5rem] max-w-96 flex flex-row bg-stone-50 relative"
:class="search ?
'activesearch' : ''">
<div class="pb-0">
<i class="ri-search-line"></i><i class="-ml-0.5 inline-block ri-arrow-right-s-line"></i>
</div>
<div class="pb-0 border-b-4 border-zinc-300 grow">
<input
class="px-1.5 font-serif placeholder:italic w-full"
type="search"
name="search"
value="{{ $model.search }}"
x-model="search"
placeholder="Band-ID od. Suchbegriff"
hx-get=""
hx-trigger="input changed delay:=200ms, keyup[key=='Enter']"
hx-select="#searchcontent"
hx-target="#searchcontent"
autocomplete="off" />
</div>
<div id="permalink" class="font-serif ml-3 min-w-7 pb-1">
<a
href="/reihen?search={{ $model.search }}"
:href="'/reihen/?search=' + search"
x-show="search"
class="inline-block px-1
text-white no-underline bg-stone-700 hover:bg-stone-900 rounded">
text-white no-underline bg-stone-700 hover:bg-stone-900 rounded"
hx-boost="false">
<i class="ri-links-line"></i
></a>
{{ end }}
</div>
</div>
</div>
{{ end }}

View File

@@ -0,0 +1,51 @@
{{ $model := . }}
<div class="min-w-96 max-w-96 float-right flex flex-col gap-y-4">
{{ if .agents }}
<filter-list
id="agent-list"
data-url="/reihen/?agent="
data-placeholder="Personen und Körperschaften filtern..."></filter-list>
<script type="module">
let agentList = document.getElementById("agent-list");
if (agentList) agentList.items = {{ .agents }};
</script>
{{ end }}
{{ if .places }}
<filter-list
id="place-list"
data-url="/reihen/?place="
data-placeholder="Erscheinungsorte filtern..."></filter-list>
<script type="module">
let placeList = document.getElementById("place-list");
if (placeList) placeList.items = {{ .places }};
</script>
{{ end }}
{{ if .years }}
<filter-list
id="year-list"
data-url="/reihen/?year="
data-filterstart="true"
data-placeholder="Erscheinungsjahr filtern..."></filter-list>
<script type="module">
let yearList = document.getElementById("year-list");
if (yearList) {
yearList.items = {{ .years }};
yearList.setHREFFunc((item) => {
if (item === 0) return "/reihen?year=0";
return `/reihen?year=${item}`;
});
yearList.setLinkTextFunc((item) => {
if (item === 0) return "ohne Jahr";
return String(item);
});
}
</script>
{{ end }}
</div>

View File

@@ -1,14 +1,18 @@
{{ $model := . }}
<div class="mt-4">
{{ template "_alphabet" Dict "active" .letter "letters" .letters "search" .search }}
<div id="pageheading" class="headingcontainer">
<h1 class="heading">Bände nach Reihentiteln</h1>
<div class="mt-2">
{{ template "_alphabet" Dict "active" .letter "letters" .letters "search" .search }}
</div>
</div>
<div class="mt-3">
{{ template "_reihenfilter" . }}
</div>
<div id="searchcontent" class="pt-4 font-serif">
{{ if .search }}
<div class="mt-8">Reihen für {{ .search }} gefunden.</div>
{{ end }}
{{ if or .series .altseries }}
{{ range $id, $r := .series }}
<div class="mb-1.5">
@@ -33,41 +37,3 @@
</div>
{{ end }}
</div>
<div class="flex flex-row">
{{ if .agents }}
<div class="mt-8">
{{ range $id, $r := .agents }}
<div>
<a href="/reihen?agent={{ $r.Id }}">{{ $r.Name }}</a>
</div>
{{ end }}
</div>
{{ end }}
{{ if .places }}
<div class="mt-8">
{{ range $id, $r := .places }}
<div>
<a href="/reihen?place={{ $r.Id }}">{{ $r.Name }}</a>
</div>
{{ end }}
</div>
{{ end }}
{{ if .years }}
<div class="mt-8">
{{ range $id, $r := .years }}
{{ if eq $r 0 }}
<div>
<a href="/reihen?year=0">ohne Jahr</a>
</div>
{{ else }}
<div>
<a href="/reihen?year={{ $r }}">{{ $r }}</a>
</div>
{{ end }}
{{ end }}
</div>
{{ end }}
</div>

View File

@@ -1,48 +1,66 @@
// INFO: We import this so vite processes the stylesheet
import "./site.css";
const ATTR_XSLT_ONLOAD = "script[xslt-onload]";
const ATTR_XSLT_TEMPLATE = "xslt-template";
const ATTR_XSLT_STATE = "xslt-transformed";
const FILTER_LIST_ELEMENT = "filter-list";
const xslt_processors = new Map();
class XSLTParseProcess {
#processors;
function setup_xslt() {
let els = htmx.findAll(ATTR_XSLT_ONLOAD);
for (let element of els) {
transform_xslt(element);
}
}
function transform_xslt(element) {
if (
element.getAttribute(ATTR_XSLT_STATE) === "true" ||
!element.hasAttribute(ATTR_XSLT_TEMPLATE)
) {
return;
constructor() {
this.#processors = new Map();
}
let templateId = "#" + element.getAttribute(ATTR_XSLT_TEMPLATE);
let processor = xslt_processors.get(templateId);
if (!processor) {
let template = htmx.find(templateId);
if (template) {
let content = template.innerHTML
? new DOMParser().parseFromString(template.innerHTML, "application/xml")
: template.contentDocument;
processor = new XSLTProcessor();
processor.importStylesheet(content);
xslt_processors.set(templateId, processor);
} else {
throw new Error("Unknown XSLT template: " + templateId);
setup() {
let els = htmx.findAll(ATTR_XSLT_ONLOAD);
for (let element of els) {
this.#transform_xslt(element);
}
}
let data = new DOMParser().parseFromString(element.innerHTML, "application/xml");
let frag = processor.transformToFragment(data, document);
let s = new XMLSerializer().serializeToString(frag);
element.outerHTML = s;
hookupHTMX() {
// INFO: We can instead use afterSettle; and also clear the map with
// xslt_processors.clear();
htmx.on("htmx:load", (_) => {
this.setup();
});
}
#transform_xslt(element) {
if (
element.getAttribute(ATTR_XSLT_STATE) === "true" ||
!element.hasAttribute(ATTR_XSLT_TEMPLATE)
) {
return;
}
let templateId = "#" + element.getAttribute(ATTR_XSLT_TEMPLATE);
let processor = this.#processors.get(templateId);
if (!processor) {
let template = htmx.find(templateId);
if (template) {
let content = template.innerHTML
? new DOMParser().parseFromString(template.innerHTML, "application/xml")
: template.contentDocument;
processor = new XSLTProcessor();
processor.importStylesheet(content);
this.#processors.set(templateId, processor);
} else {
throw new Error("Unknown XSLT template: " + templateId);
}
}
let data = new DOMParser().parseFromString(element.innerHTML, "application/xml");
let frag = processor.transformToFragment(data, document);
let s = new XMLSerializer().serializeToString(frag);
element.outerHTML = s;
}
}
// INFO: these is a function to define simple reusable templates which we don't need.
// Since we can include templates server-side.
function setup_templates() {
let templates = document.querySelectorAll("template[simple]");
templates.forEach((template) => {
@@ -77,45 +95,213 @@ function setup_templates() {
});
}
// INFO: This is intended to be callled once on website load
function setup() {
setup_xslt();
htmx.on("htmx:load", function (_) {
// INFO: We can instead use afterSettle; and also clear the map with
// xslt_processors.clear();
setup_xslt();
});
setup_templates();
}
function setMenuActive(url) {
if (!url) {
url = window.location.href;
class FilterList extends HTMLElement {
#hiddenlist = false;
constructor() {
super();
this._items = [];
this._url = "";
this._filterstart = false;
this._placeholder = "Liste filtern...";
this.render();
}
const menus = document.querySelectorAll("nav");
if (menus && menus.length > 0) {
for (const menu of menus) {
const links = menu.querySelectorAll("a, [data-url]");
links.forEach((link) => {
if (link.dataset.url && link.dataset.url !== "") {
let fullurl = window.location.origin + link.dataset.url;
if (url.startsWith(fullurl)) {
link.setAttribute("aria-current", "page");
} else {
link.removeAttribute("aria-current");
}
} else if (link.href) {
if (url.startsWith(link.href)) {
link.setAttribute("aria-current", "page");
} else {
link.removeAttribute("aria-current");
}
}
});
static get observedAttributes() {
return ["data-url"];
}
set items(data) {
if (Array.isArray(data)) {
this._items = data;
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...";
if (this._filterstart) {
this.#hiddenlist = true;
}
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(name, oldValue, newValue) {
if (name === "data-url" && oldValue !== newValue) {
this._url = newValue;
this.render();
}
if (name === "data-filterstart" && oldValue !== newValue) {
this._filterstart = newValue === "true";
this.render();
}
if (name === "data-placeholder" && oldValue !== newValue) {
this._placeholder = newValue;
this.render();
}
}
onInput(e) {
if (e.target && e.target.tagName.toLowerCase() === "input") {
this._filter = e.target.value;
this.renderList();
}
}
onGainFocus(e) {
if (e.target && e.target.tagName.toLowerCase() === "input") {
this.#hiddenlist = false;
this.renderList();
}
}
onLoseFocus(e) {
if (e.target && e.target.tagName.toLowerCase() === "input") {
e.target.value = "";
this._filter = "";
if (this._filterstart) {
this.#hiddenlist = true;
}
this.renderList();
}
}
onEnter(e) {
if (e.target && e.target.tagName.toLowerCase() === "input" && e.key === "Enter") {
e.preventDefault();
const link = this.querySelector("a");
if (link) {
link.click();
}
}
}
setHREFFunc(fn) {
this.getHREF = fn;
this.render();
}
setLinkTextFunc(fn) {
this.getLinkText = fn;
this.render();
}
getHREF(item) {
if (!item) {
return "";
} else if (!item.id) {
return "";
}
return item.id;
}
getLinkText(item) {
if (!item) {
return "";
} else if (!item.name) {
return "";
}
return item.name;
}
#isActive(item) {
if (!item) {
return "";
}
let href = this.getHREF(item);
if (href === "") {
return "";
}
if (!window.location.href.endsWith(href)) {
return "";
}
return "aria-current='page'";
}
#hiddenList() {
if (this.#hiddenlist) {
return "hidden";
}
return "";
}
renderList() {
let list = this.querySelector("#list");
if (list) {
list.outerHTML = this.List();
}
}
render() {
this.innerHTML = `
<div class="p-4 font-serif text-base">
${this.Input()}
${this.List()}
</div>
`;
}
Input() {
return `
<div class="flex w-full pb-0.5 border-b border-zinc-600">
<i class="ri-arrow-right-s-line mr-1"></i>
<div class="grow">
<input
type="text"
placeholder="${this._placeholder}"
class="w-full placeholder:italic px-2 " />
</div>
</div>
`;
}
List() {
let filtereditems = this._items;
if (this._filter) {
if (!this._filterstart)
filtereditems = this._items.filter((item) => {
return this.getLinkText(item).toLowerCase().includes(this._filter.toLowerCase());
});
else
filtereditems = this._items.filter((item) => {
return this.getLinkText(item).toLowerCase().startsWith(this._filter.toLowerCase());
});
}
return `
<div id="list" class="links min-h-72 max-h-60 overflow-auto border-b border-zinc-300 ${this.#hiddenList()}">
${filtereditems
.map(
(item, index) => `
<a
href="${this._url}${this.getHREF(item)}"
class="block px-2.5 py-0.5 hover:bg-slate-200 no-underline ${
index % 2 === 0 ? "bg-stone-100" : "bg-stone-50"
}"
${this.#isActive(item)}>
${this.getLinkText(item)}
</a>
`,
)
.join("")}
</div>
`;
}
}
export { setup, setMenuActive };
customElements.define(FILTER_LIST_ELEMENT, FilterList);
export { XSLTParseProcess, FilterList };

View File

@@ -99,11 +99,24 @@
@apply font-variant-small-caps;
}
.alphabet a[aria-current="page"] {
.alphabet a[aria-current="page"]:not(.inactive) {
@apply pb-3 pt-2 font-bold italic !bg-stone-50 relative -bottom-3 border-b;
}
.alphabet a:hover:not([aria-current="page"]) {
.alphabet a:hover:not([aria-current="page"]:not(.inactive)) {
@apply pb-1 pt-0.5 !bg-stone-50 relative;
}
.headingcontainer:before {
content: "";
@apply bg-zinc-300 w-[50%] absolute top-0 left-[50%] h-[1px];
}
.headingcontainer {
@apply mt-10 border-r border-zinc-300 relative;
}
.headingcontainer h1 {
@apply text-3xl font-bold px-3 bg-stone-50 relative -translate-y-[50%] w-min whitespace-nowrap mx-auto;
}
}