diff --git a/.gitignore b/.gitignore index 0ee3b86..3cf748e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ pb_data/ +backup/ _tmp/ -import/ +import/Almanach-Bilder/ Almanach-Bilder/ musenalm node_modules diff --git a/app/pb.go b/app/pb.go index 75c1605..ef7cffc 100644 --- a/app/pb.go +++ b/app/pb.go @@ -168,7 +168,8 @@ func (app *App) setWatchers(engine *templating.Engine) { func (app *App) bindPages(engine *templating.Engine) ServeFunc { return func(e *core.ServeEvent) error { - e.Router.GET("/assets/{path...}", apis.Static(views.StaticFS, true)) + r := e.Router.GET("/assets/{path...}", apis.Static(views.StaticFS, true)) + r.Bind(apis.Gzip()) // INFO: Global middleware to get the authenticated user: e.Router.BindFunc(middleware.Authenticated(e.App)) diff --git a/dbmodels/db_data.go b/dbmodels/db_data.go index caacc11..0f4ef77 100644 --- a/dbmodels/db_data.go +++ b/dbmodels/db_data.go @@ -365,6 +365,7 @@ var MUSENALM_MIME_TYPES = []string{ "image/png", "image/vnd.mozilla.apng", "image/jpeg", + "image/jpg", "image/jp2", "image/jpx", "image/jpm", diff --git a/pages/almanach.go b/pages/almanach.go index 4d62c18..d1658a1 100644 --- a/pages/almanach.go +++ b/pages/almanach.go @@ -34,9 +34,14 @@ type AlmanachPage struct { } func (p *AlmanachPage) Setup(router *router.Router[*core.RequestEvent], app core.App, engine *templating.Engine) error { - router.GET(p.URL, func(e *core.RequestEvent) error { + router.GET(p.URL, p.GET(engine, app)) + return nil +} + +func (p *AlmanachPage) GET(engine *templating.Engine, app core.App) HandleFunc { + return func(e *core.RequestEvent) error { id := e.Request.PathValue("id") - data := make(map[string]interface{}) + data := make(map[string]any) filters := NewBeitraegeFilterParameters(e) result, err := NewAlmanachResult(app, id, filters) if err != nil { @@ -51,9 +56,7 @@ func (p *AlmanachPage) Setup(router *router.Router[*core.RequestEvent], app core } return engine.Response200(e, p.Template, data) - }) - - return nil + } } type AlmanachResult struct { diff --git a/pages/almanach_edit.go b/pages/almanach_edit.go index 3c825f1..3320809 100644 --- a/pages/almanach_edit.go +++ b/pages/almanach_edit.go @@ -39,8 +39,21 @@ func (p *AlmanachEditPage) Setup(router *router.Router[*core.RequestEvent], app func (p *AlmanachEditPage) GET(engine *templating.Engine, app core.App) HandleFunc { return func(e *core.RequestEvent) error { + id := e.Request.PathValue("id") data := make(map[string]any) + filters := NewBeitraegeFilterParameters(e) + result, err := NewAlmanachResult(app, id, filters) + if err != nil { + engine.Response404(e, err, nil) + } + data["result"] = result + data["filters"] = filters - return engine.Response200(e, p.Template, data) + abbrs, err := pagemodels.GetAbks(app) + if err == nil { + data["abbrs"] = abbrs + } + + return engine.Response200(e, p.Template, data, p.Layout) } } diff --git a/scripts/build.sh b/scripts/build.sh old mode 100644 new mode 100755 diff --git a/views/assets/js/alpine.ajax.min.js b/views/assets/js/alpine.ajax.min.js new file mode 100644 index 0000000..101b021 --- /dev/null +++ b/views/assets/js/alpine.ajax.min.js @@ -0,0 +1 @@ +(()=>{var g={headers:{},mergeStrategy:"replace",transitions:!1,mapDelimiter:":"},$=()=>{console.error(`You can't use the "morph" merge without first installing the Alpine "morph" plugin here: https://alpinejs.dev/plugins/morph`)};function j(e){e.morph&&($=e.morph),e.addInitSelector(()=>`[${e.prefixed("target")}]`),e.addInitSelector(()=>`[${e.prefixed("target\\.push")}]`),e.addInitSelector(()=>`[${e.prefixed("target\\.replace")}]`),e.directive("target",(a,{value:n,modifiers:i,expression:s},{evaluateLater:r,effect:l})=>{let o=c=>{a._ajax_target=a._ajax_target||{};let w={ids:P(a,c),sync:!0,focus:!i.includes("nofocus"),history:i.includes("push")?"push":i.includes("replace")?"replace":!1},h=i.filter(f=>["back","away","error"].includes(f)||parseInt(f));h=h.length?h:["xxx"],h.forEach(f=>{f.charAt(0)==="3"&&(f="3xx"),a._ajax_target[f]=w})};if(n==="dynamic"){let c=r(s);l(()=>c(o))}else o(s)}),e.directive("headers",(a,{expression:n},{evaluateLater:i,effect:s})=>{let r=i(n||"{}");s(()=>{r(l=>{a._ajax_headers=l})})}),e.addInitSelector(()=>`[${e.prefixed("merge")}]`),e.directive("merge",(a,{value:n,modifiers:i,expression:s},{evaluateLater:r,effect:l})=>{let o=c=>{a._ajax_strategy=c,a._ajax_transition=g.transitions||i.includes("transition")};if(n==="dynamic"){let c=r(s);l(()=>c(o))}else o(s)}),e.magic("ajax",a=>async(n,i={})=>{let s={el:a,target:{xxx:{ids:P(a,i.targets||i.target),sync:!!i.sync,history:"history"in i?i.history:!1,focus:"focus"in i?i.focus:!0}},headers:i.headers||{}},r=i.method?i.method.toUpperCase():"GET",l=i.body;return v(s,n,r,l)});let t=!1;e.ajax={start(){t||(document.addEventListener("submit",R),document.addEventListener("click",D),window.addEventListener("popstate",T),t=!0)},configure:j.configure,stop(){document.removeEventListener("submit",R),document.removeEventListener("click",D),window.removeEventListener("popstate",T),t=!1}},e.ajax.start()}j.configure=e=>(g=Object.assign(g,e),j);var A=j;function T(e){!e.state||!e.state.__ajax||window.location.reload(!0)}async function D(e){if(e.defaultPrevented||e.which>1||e.altKey||e.ctrlKey||e.metaKey||e.shiftKey)return;let t=e?.target.closest("a[href]:not([download]):not([noajax])");if(!t||!t._ajax_target||t.isContentEditable||t.origin!==window.location.origin||t.getAttribute("href").startsWith("#")||t.hash&&M(t,new URL(document.baseURI)))return;e.preventDefault(),e.stopImmediatePropagation();let a={el:t,target:t._ajax_target,headers:t._ajax_headers||{}},n=t.getAttribute("href");try{return await v(a,n)}catch(i){if(i.name==="RenderError"){console.warn(i.message),window.location.href=t.href;return}throw i}}async function R(e){if(e.defaultPrevented)return;let t=e.target,a=e.submitter,n=(a?.getAttribute("formmethod")||t.getAttribute("method")||"GET").toUpperCase();if(!t||!t._ajax_target||n==="DIALOG"||a?.hasAttribute("formnoajax")||a?.hasAttribute("formtarget")||t.hasAttribute("noajax")||t.hasAttribute("target"))return;e.preventDefault(),e.stopImmediatePropagation();let i={el:t,target:t._ajax_target,headers:t._ajax_headers||{}},s=new FormData(t),r=t.getAttribute("enctype"),l=t.getAttribute("action");a&&(r=a.getAttribute("formenctype")||r,l=a.getAttribute("formaction")||l,a.name&&s.append(a.name,a.value));try{return await G(a,()=>v(i,l,n,s,r))}catch(o){if(o.name==="RenderError"){console.warn(o.message),t.setAttribute("noajax","true"),t.requestSubmit(a);return}throw o}}async function G(e,t){if(!e)return await t();let a=i=>i.preventDefault();e.setAttribute("aria-disabled","true"),e.addEventListener("click",a);let n;try{n=await t()}finally{e.removeAttribute("aria-disabled"),e.removeEventListener("click",a)}return n}var m={store:new Map,plan(e,t){if(e.ids.forEach(a=>{let n=a[0],i=["_self","_top","_none"].includes(n)?document.documentElement:document.getElementById(n);if(!i)return console.warn(`Target [#${n}] was not found in current document.`);i._ajax_id=a[1],this.set(i,t)}),e.sync){let a=e.ids.flat();document.querySelectorAll("[x-sync]").forEach(n=>{let i=n.getAttribute("id");if(!i)throw new b(n);a.includes(i)||(n._ajax_id=i,n._ajax_sync=!0,this.set(n,t))})}},purge(e){this.store.forEach((t,a)=>e===t&&this.delete(a))},get(e){let t=[];return this.store.forEach((a,n)=>e===a&&t.push(n)),t},set(e,t){e.querySelectorAll("[aria-busy]").forEach(a=>{this.delete(a)}),e.setAttribute("aria-busy","true"),this.store.set(e,t)},delete(e){e.removeAttribute("aria-busy"),this.store.delete(e)}},x=new Map;async function v(e,t="",a="GET",n=null,i="application/x-www-form-urlencoded"){if(!d(e.el,"ajax:before"))return;let s=e.target.xxx,r={ok:!1,redirected:!1,url:"",status:"",html:"",raw:""};m.plan(s,r);let l=new URL(e.el.closest("[data-source]")?.dataset.source||"",document.baseURI);t=new URL(t||l,document.baseURI),n&&(n=N(n),a==="GET"?(t.search=U(n).toString(),n=null):i!=="multipart/form-data"&&(n=U(n)));let o={action:t.toString(),method:a,body:n,enctype:i,referrer:l.toString(),headers:Object.assign({"X-Alpine-Request":!0,"X-Alpine-Target":m.get(r).map(u=>u._ajax_id).join(" ")},g.headers,e.headers)};d(e.el,"ajax:send",o);let c;if(o.method==="GET"&&x.has(o.action)?c=x.get(o.action):(c=fetch(o.action,o).then(async u=>{let p=await u.text(),_=document.createRange().createContextualFragment(""+p+"");return u.html=_.firstElementChild.content,u.raw=p,u}),x.set(o.action,c)),await c.then(u=>{r.ok=u.ok,r.redirected=u.redirected,r.url=u.url,r.status=u.status,r.html=u.html,r.raw=u.raw}),r.ok?(r.redirected&&(d(e.el,"ajax:redirect",r),x.set(r.url,c),setTimeout(()=>{x.delete(r.url)},5)),d(e.el,"ajax:success",r)):d(e.el,"ajax:error",r),d(e.el,"ajax:sent",r),x.delete(o.action),!r.html){m.purge(r);return}let w=r.redirected?"3xx":r.status.toString(),h=M(new URL(r.url),new URL(o.referrer,document.baseURI)),f=[r.redirected?h?"back":"away":null,w,w.charAt(0)+"xx",r.ok?"xxx":"error","xxx"].find(u=>u in e.target);f!=="xxx"&&(s=e.target[f],(!r.redirected||!h||!s.ids.flat().includes("_self"))&&(m.purge(r),m.plan(s,r))),s.history&&H(s.history,r.url);let y=!s.focus,q=m.get(r).map(async u=>{if(!u.isConnected||u._ajax_id==="_none")return;u===document.documentElement&&(window.location.href=r.url);let p=r.html.getElementById(u._ajax_id);if(!p){if(u._ajax_sync||!d(e.el,"ajax:missing",{target:u,response:r}))return;if(r.ok)return u.remove();throw new E(u,r.status)}let _=u._ajax_strategy||g.mergeStrategy,I=async()=>{if(u=await B(_,u,p),u){u.dataset.source=r.url,m.delete(u);let L=["[x-autofocus]","[autofocus]"];for(;!y&&L.length;){let k=L.shift();u.matches(k)&&(y=C(u)),y=y||Array.from(u.querySelectorAll(k)).some(O=>C(O))}}return d(u,"ajax:merged"),u};if(d(u,"ajax:merge",{strategy:_,content:p,merge:I}))return I()}),S=await Promise.all(q);return d(e.el,"ajax:after",{response:r,render:S}),S}function N(e){if(e instanceof FormData)return e;if(e instanceof HTMLFormElement)return new FormData(e);let t=new FormData;for(let a in e)typeof e[a]=="object"?t.append(a,JSON.stringify(e[a])):t.append(a,e[a]);return t}function U(e){let t=Array.from(e.entries()).filter(([a,n])=>!(n instanceof File));return new URLSearchParams(t)}async function B(e,t,a){let n={before(s,r){return s.before(...r.childNodes),s},replace(s,r){return s.replaceWith(r),r},update(s,r){return s.replaceChildren(...r.childNodes),s},prepend(s,r){return s.prepend(...r.childNodes),s},append(s,r){return s.append(...r.childNodes),s},after(s,r){return s.after(...r.childNodes),s},morph(s,r){return $(s,r),document.getElementById(r.getAttribute("id"))}};return!t._ajax_transition||!document.startViewTransition?n[e](t,a):(await document.startViewTransition(()=>(t=n[e](t,a),Promise.resolve())).updateCallbackDone,t)}function C(e){return!e||!e.getClientRects().length?!1:(setTimeout(()=>{e.hasAttribute("tabindex")||e.setAttribute("tabindex","0"),e.focus()},0),!0)}function H(e,t){return{push:()=>window.history.pushState({__ajax:!0},"",t),replace:()=>window.history.replaceState({__ajax:!0},"",t)}[e]()}function P(e,t=null){let a=e.getAttribute("id"),n=[a];if(t&&(n=Array.isArray(t)?t:t.split(" ")),n=n.filter(i=>i).map(i=>{let s=i.split(g.mapDelimiter).map(r=>r||a);return s[1]=s[1]||s[0],s}),n.length===0)throw new b(e);return n}function d(e,t,a){return e.dispatchEvent(new CustomEvent(t,{detail:a,bubbles:!0,composed:!0,cancelable:!0}))}function M(e,t){return F(e.pathname)===F(t.pathname)}function F(e){return e.replace(/\/$/,"")}var b=class extends DOMException{constructor(t){let a=(t.outerHTML.match(/<[^>]+>/)??[])[0]??"[Element]";super(`${a} is missing an ID to target.`,"IDError")}},E=class extends DOMException{constructor(t,a){let n=t.getAttribute("id");super(`Target [#${n}] was not found in response with status [${a}].`,"RenderError")}};document.addEventListener("alpine:initializing",()=>{A.configure(window.alpineAJAX||{}),A(window.Alpine)});})(); diff --git a/views/assets/scripts.js b/views/assets/scripts.js index 5093f89..11a6828 100644 --- a/views/assets/scripts.js +++ b/views/assets/scripts.js @@ -1,43 +1,9 @@ -var T = (r) => { +var v = (r) => { throw TypeError(r); }; -var _ = (r, t, e) => t.has(r) || T("Cannot " + e); -var c = (r, t, e) => (_(r, t, "read from private field"), e ? e.call(r) : t.get(r)), l = (r, t, e) => t.has(r) ? T("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 k = "script[xslt-onload]", y = "xslt-template", S = "xslt-transformed", M = "filter-list", m = "filter-list-list", B = "filter-list-item", C = "filter-list-input", w = "filter-list-searchable", I = "scroll-button", q = "tool-tip", P = "abbrev-tooltips", R = "int-link", H = "popup-image", $ = "tab-list", O = "filter-pill", N = "image-reel"; -var d, b, E; -class V { - constructor() { - l(this, b); - l(this, d); - u(this, d, /* @__PURE__ */ new Map()); - } - setup() { - let t = htmx.findAll(k); - 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(S) === "true" || !t.hasAttribute(y)) - return; - let e = "#" + t.getAttribute(y), 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), n = new XMLSerializer().serializeToString(a); - t.outerHTML = n; -}; -class F extends HTMLElement { +var f = (r, t, e) => t.has(r) || v("Cannot " + e); +var g = (r, t, e) => (f(r, t, "read from private field"), e ? e.call(r) : t.get(r)), d = (r, t, e) => t.has(r) ? v("Cannot add the same private member more than once") : t instanceof WeakSet ? t.add(r) : t.set(r, e), c = (r, t, e, i) => (f(r, t, "write to private field"), i ? i.call(r, e) : t.set(r, e), e), m = (r, t, e) => (f(r, t, "access private method"), e); +class x extends HTMLElement { constructor() { super(), this._value = "", this.render(); } @@ -106,13 +72,13 @@ class F extends HTMLElement { `; } } -var o, x, f, L; -class U extends HTMLElement { +const u = "filter-list-list", y = "filter-list-item", T = "filter-list-input", _ = "filter-list-searchable"; +var l, h, b; +class w extends HTMLElement { constructor() { super(); - l(this, f); - l(this, o, !1); - l(this, x, ""); + d(this, h); + d(this, l, !1); this._items = [], this._url = "", this._filterstart = !1, this._placeholder = "Liste filtern...", this._queryparam = "", this._startparams = null, this.render(); } static get observedAttributes() { @@ -125,7 +91,7 @@ class U extends HTMLElement { 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)); + 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 && c(this, l, !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()); @@ -134,14 +100,14 @@ class U extends HTMLElement { 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()); + e.target && e.target.tagName.toLowerCase() === "input" && (c(this, l, !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(); + i.value = "", this._filter = "", this._filterstart && c(this, l, !0), this.renderList(); } } onEnter(e) { @@ -154,10 +120,10 @@ class U extends HTMLElement { mark() { if (typeof Mark != "function") return; - let e = this.querySelector("#" + m); + let e = this.querySelector("#" + u); if (!e) return; - let i = new Mark(e.querySelectorAll("." + w)); + let i = new Mark(e.querySelectorAll("." + _)); this._filter && i.mark(this._filter, { separateWordSearch: !0 }); @@ -197,7 +163,7 @@ class U extends HTMLElement { } getLinkText(e) { let i = this.getSearchText(e); - return i === "" ? "" : `${i}`; + return i === "" ? "" : `${i}`; } getURL(e) { if (this._queryparam) { @@ -207,7 +173,7 @@ class U extends HTMLElement { return this._url + this.getHREFEncoded(e); } renderList() { - let e = this.querySelector("#" + m); + let e = this.querySelector("#" + u); e && (e.outerHTML = this.List()), this.mark(); } render() { @@ -219,7 +185,7 @@ class U extends HTMLElement { `, htmx && htmx.process(this); } ActiveDot(e) { - return g(this, f, L).call(this, e), ""; + return m(this, h, b).call(this, e), ""; } NoItems(e) { return e.length === 0 ? '