Titelzeile

This commit is contained in:
Simon Martens
2025-02-18 15:12:24 +01:00
parent fc84b3a677
commit 7d732a7ea2
13 changed files with 197 additions and 64 deletions

View File

@@ -53,7 +53,7 @@ time = false
clean_on_exit = true clean_on_exit = true
[proxy] [proxy]
app_port = 8080 app_port = 8090
enabled = true enabled = true
proxy_port = 8081 proxy_port = 8081

View File

@@ -109,7 +109,14 @@ func (app *App) setupTestuser() {
func (app *App) Serve() error { func (app *App) Serve() error {
engine := templating.NewEngine(&views.LayoutFS, &views.RoutesFS) engine := templating.NewEngine(&views.LayoutFS, &views.RoutesFS)
engine.Globals(map[string]interface{}{"isDev": app.MAConfig.Debug}) engine.Globals(map[string]interface{}{
"isDev": app.MAConfig.Debug,
"lang": "de",
"site": map[string]interface{}{
"title": "Musenalm",
"lang": "de",
"desc": "Bibliographie deutscher Almanache des 18. und 19. Jahrhunderts",
}})
// INFO: hot reloading for poor people // INFO: hot reloading for poor people
if app.MAConfig.Debug { if app.MAConfig.Debug {

View File

@@ -2,11 +2,15 @@ package dbmodels
import ( import (
"slices" "slices"
"strings"
"github.com/Theodor-Springmann-Stiftung/musenalm/helpers/datatypes"
"github.com/pocketbase/dbx" "github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/core"
"golang.org/x/text/cases"
"golang.org/x/text/collate" "golang.org/x/text/collate"
"golang.org/x/text/language" "golang.org/x/text/language"
"golang.org/x/text/unicode/norm"
) )
type SeriesEntries map[string][]*REntriesSeries type SeriesEntries map[string][]*REntriesSeries
@@ -19,6 +23,17 @@ func SortSeriessesByTitle(series []*Series) {
} }
func BasicSearchSeries(app core.App, query string) ([]*Series, []*Series, error) { func BasicSearchSeries(app core.App, query string) ([]*Series, []*Series, error) {
query = strings.TrimSpace(query)
query = datatypes.DeleteTags(query)
query = datatypes.NormalizeString(query)
query = datatypes.RemovePunctuation(query)
query = cases.Lower(language.German).String(query)
query = norm.NFKD.String(query)
if query == "" {
return []*Series{}, []*Series{}, nil
}
series, err := TitleSearchSeries(app, query) series, err := TitleSearchSeries(app, query)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@@ -33,8 +48,20 @@ func BasicSearchSeries(app core.App, query string) ([]*Series, []*Series, error)
func TitleSearchSeries(app core.App, query string) ([]*Series, error) { func TitleSearchSeries(app core.App, query string) ([]*Series, error) {
series := []*Series{} series := []*Series{}
err := app.RecordQuery(SERIES_TABLE). queries := strings.Split(query, " ")
Where(dbx.Like(SERIES_TITLE_FIELD, query).Match(true, true)). q := app.RecordQuery(SERIES_TABLE).
Where(dbx.Like(SERIES_TITLE_FIELD, queries[0]).Match(true, true))
if len(queries) > 1 {
for _, que := range queries[1:] {
que = strings.TrimSpace(que)
if que != "" {
q.AndWhere(dbx.Like(SERIES_TITLE_FIELD, que).Match(true, true))
}
}
}
err := q.
OrderBy(SERIES_TITLE_FIELD). OrderBy(SERIES_TITLE_FIELD).
All(&series) All(&series)
if err != nil { if err != nil {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -1,51 +1,64 @@
const p = "script[xslt-onload]", a = "xslt-template", u = "xslt-transformed", c = /* @__PURE__ */ new Map(); const f = "script[xslt-onload]", i = "xslt-template", m = "xslt-transformed", c = /* @__PURE__ */ new Map();
function m() { function u() {
let t = htmx.findAll(p); let t = htmx.findAll(f);
for (let e of t) for (let r of t)
T(e); p(r);
} }
function T(t) { function p(t) {
if (t.getAttribute(u) === "true" || !t.hasAttribute(a)) if (t.getAttribute(m) === "true" || !t.hasAttribute(i))
return; return;
let e = "#" + t.getAttribute(a), o = c.get(e); let r = "#" + t.getAttribute(i), o = c.get(r);
if (!o) { if (!o) {
let n = htmx.find(e); let s = htmx.find(r);
if (n) { if (s) {
let l = n.innerHTML ? new DOMParser().parseFromString(n.innerHTML, "application/xml") : n.contentDocument; let l = s.innerHTML ? new DOMParser().parseFromString(s.innerHTML, "application/xml") : s.contentDocument;
o = new XSLTProcessor(), o.importStylesheet(l), c.set(e, o); o = new XSLTProcessor(), o.importStylesheet(l), c.set(r, o);
} else } else
throw new Error("Unknown XSLT template: " + e); throw new Error("Unknown XSLT template: " + r);
} }
let i = new DOMParser().parseFromString(t.innerHTML, "application/xml"), s = o.transformToFragment(i, document), r = new XMLSerializer().serializeToString(s); let a = new DOMParser().parseFromString(t.innerHTML, "application/xml"), e = o.transformToFragment(a, document), n = new XMLSerializer().serializeToString(e);
t.outerHTML = r; t.outerHTML = n;
} }
function f() { function d() {
document.querySelectorAll("template[simple]").forEach((e) => { document.querySelectorAll("template[simple]").forEach((r) => {
let o = e.getAttribute("id"), i = e.content; let o = r.getAttribute("id"), a = r.content;
customElements.define( customElements.define(
o, o,
class extends HTMLElement { class extends HTMLElement {
constructor() { constructor() {
super(), this.appendChild(i.cloneNode(!0)), this.slots = this.querySelectorAll("slot"); super(), this.appendChild(a.cloneNode(!0)), this.slots = this.querySelectorAll("slot");
} }
connectedCallback() { connectedCallback() {
let s = []; let e = [];
this.slots.forEach((r) => { this.slots.forEach((n) => {
let n = r.getAttribute("name"), l = this.querySelector(`[slot="${n}"]`); let s = n.getAttribute("name"), l = this.querySelector(`[slot="${s}"]`);
l && (r.replaceWith(l.cloneNode(!0)), s.push(l)); l && (n.replaceWith(l.cloneNode(!0)), e.push(l));
}), s.forEach((r) => { }), e.forEach((n) => {
r.remove(); n.remove();
}); });
} }
} }
); );
}); });
} }
function d() { function h() {
m(), htmx.on("htmx:load", function(t) { u(), htmx.on("htmx:load", function(t) {
m(); u();
}), f(); }), 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 { export {
d as setup T as setMenuActive,
h as setup
}; };

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,43 @@
<div class="flex flex-row justify-center mt-8 gap-x-2"> <div
<a href="/reihen">Reihen</a> class="pb-1.5 border-b border-zinc-300"
<a href="/personen/">Personen</a> x-data="{ openeditionmenu: window.location.pathname.startsWith('/edition/')}">
<div class="flex flex-row justify-between">
<div>
<h1 class="font-bold">{{ .site.title }}</h1>
<h2 class="italic">{{ .site.desc }}</h2>
</div>
<nav
class="self-end font-serif font-bold flex flex-row gap-x-6 [&>a]:no-underline
[&>*]:px-1.5 [&>*]:pt-1 [&>*]:-mb-1.5">
<a href="/reihen/">Reihen</a>
<a href="/personen/">Personen</a>
<a href="/suche/">Suche</a>
<button
data-url="/edition/"
class="text-slate-600 hover:text-slate-900 hover:cursor-pointer"
:class="openeditionmenu? 'bg-slate-200' : 'closed'"
@click="openeditionmenu = !openeditionmenu">
<i x-show="!openeditionmenu" class="ri-arrow-right-s-fill"></i>
<i x-show="openeditionmenu" class="ri-arrow-down-s-fill"></i>
Redaktion &amp; Kontakt
</button>
</nav>
</div>
<nav
:class="openeditionmenu? 'open' : 'closed'"
x-show="openeditionmenu"
class="submenu w-full flex flex-row justify-end pt-3 gap-x-6 font-bold font-serif
[&>a]:no-underline [&>*]:-mb-1.5">
<a href="/edition/einfuehrung/">Einführung</a>
<a href="/edition/dokumentation/">Dokumentation</a>
<a href="/edition/literatur/">Literatur</a>
<a href="/edition/danksagungen/">Danksagungen</a>
<a href="/edition/kontakt/">Kontakt</a>
</nav>
</div> </div>
<script type="module">
import { setMenuActive } from "/assets/scripts.js";
setMenuActive();
</script>

View File

@@ -30,18 +30,14 @@
</head> </head>
<body class="w-full" hx-ext="response-targets" hx-boost="true"> <body class="w-full" hx-ext="response-targets" hx-boost="true">
<div class="container flex flex-col min-h-screen max-w-(--breakpoint-2xl) mx-auto"> <div class="container flex flex-col min-h-screen max-w-(--breakpoint-xl) mx-auto">
<header> <header class="px-3 py-2.5" id="header">
{{ block "_header" . }}
<!-- Default app header... -->
{{ end }}
</header>
<div>
{{ block "_menu" . }} {{ block "_menu" . }}
<!-- Default app menu... --> <!-- Default app menu... -->
{{ end }} {{ end }}
</div> </header>
<div></div>
<main class=""> <main class="">
{{ block "body" . }} {{ block "body" . }}

View File

@@ -10,6 +10,7 @@
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@tailwindcss/postcss": "^4.0.0", "@tailwindcss/postcss": "^4.0.0",
"daisyui": "^5.0.0-beta.8",
"postcss": "^8.4.47", "postcss": "^8.4.47",
"postcss-cli": "^11.0.0", "postcss-cli": "^11.0.0",
"prettier": "^3.3.3", "prettier": "^3.3.3",
@@ -1143,6 +1144,16 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/daisyui": {
"version": "5.0.0-beta.8",
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.0.0-beta.8.tgz",
"integrity": "sha512-jSokqm5i+Pv1jG80wliNzMHjmcF+iMx5xRUpk0/QExVoVNyQNWeCsaWJQubPvUq7bt9nzSsQTR2uIZBoyIIoaA==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/saadeghi/daisyui?sponsor=1"
}
},
"node_modules/dependency-graph": { "node_modules/dependency-graph": {
"version": "0.11.0", "version": "0.11.0",
"resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz",

View File

@@ -23,6 +23,7 @@
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@tailwindcss/postcss": "^4.0.0", "@tailwindcss/postcss": "^4.0.0",
"daisyui": "^5.0.0-beta.8",
"postcss": "^8.4.47", "postcss": "^8.4.47",
"postcss-cli": "^11.0.0", "postcss-cli": "^11.0.0",
"prettier": "^3.3.3", "prettier": "^3.3.3",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -90,4 +90,32 @@ function setup() {
setup_templates(); setup_templates();
} }
export { setup }; function setMenuActive(url) {
if (!url) {
url = window.location.href;
}
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");
}
}
});
}
}
}
export { setup, setMenuActive };

View File

@@ -1,9 +1,8 @@
@import 'tailwindcss'; @import "tailwindcss";
@theme { @theme {
--font-script: Rancho, ui-serif; --font-script: Rancho, ui-serif;
--font-sans: 'Source Sans 3', 'Merriweather Sans', ui-sans-serif; --font-sans: "Source Sans 3", "Merriweather Sans", ui-sans-serif;
--font-serif: 'Merriweather', ui-serif; --font-serif: "Merriweather", ui-serif;
} }
/* /*
@@ -15,17 +14,17 @@
color utility to any element that depends on these defaults. color utility to any element that depends on these defaults.
*/ */
@layer base { @layer base {
*, *,
::after, ::after,
::before, ::before,
::backdrop, ::backdrop,
::file-selector-button { ::file-selector-button {
border-color: var(--color-gray-200, currentColor); border-color: var(--color-gray-200, currentColor);
} }
} }
@utility font-variant-small-caps { @utility font-variant-small-caps {
font-variant-caps: small-caps; font-variant-caps: small-caps;
} }
@layer base { @layer base {
@@ -44,7 +43,7 @@
h2, h2,
h3, h3,
h4 { h4 {
@apply font-serif font-bold; @apply font-serif;
} }
a { a {
@@ -59,8 +58,20 @@
@apply ml-14 list-disc; @apply ml-14 list-disc;
} }
a[aria-current="page"] { nav > * {
@apply text-red-500!; @apply border-b-4 border-transparent hover:!border-zinc-200;
}
nav a[aria-current="page"] {
@apply text-blue-400;
}
nav a[aria-current="page"] {
@apply !border-zinc-300;
}
nav.submenu a[aria-current="page"] {
@apply text-blue-400;
} }
main { main {