From 93ea651c79218dde6e4f6fde6a56b1728f449a07 Mon Sep 17 00:00:00 2001 From: Simon Martens Date: Thu, 8 Jan 2026 17:46:50 +0100 Subject: [PATCH] Save button --- controllers/almanach_edit.go | 496 ++++++++++++- views/assets/scripts.js | 945 +++++++++++++++---------- views/routes/almanach/edit/body.gohtml | 42 +- views/transform/almanach-edit.js | 394 ++++++++++- views/transform/multi-select-simple.js | 8 + 5 files changed, 1503 insertions(+), 382 deletions(-) diff --git a/controllers/almanach_edit.go b/controllers/almanach_edit.go index 08620c5..db2a387 100644 --- a/controllers/almanach_edit.go +++ b/controllers/almanach_edit.go @@ -1,18 +1,27 @@ package controllers import ( + "fmt" + "net/http" + "slices" + "strings" + "time" + "github.com/Theodor-Springmann-Stiftung/musenalm/app" "github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels" + "github.com/Theodor-Springmann-Stiftung/musenalm/helpers/functions" "github.com/Theodor-Springmann-Stiftung/musenalm/middleware" "github.com/Theodor-Springmann-Stiftung/musenalm/pagemodels" "github.com/Theodor-Springmann-Stiftung/musenalm/templating" "github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/tools/router" + "github.com/pocketbase/pocketbase/tools/types" ) const ( - URL_ALMANACH_EDIT = "edit/" - TEMPLATE_ALMANACH_EDIT = "/almanach/edit/" + URL_ALMANACH_EDIT = "edit/" + TEMPLATE_ALMANACH_EDIT = "/almanach/edit/" + preferredSeriesRelationType = "Bevorzugter Reihentitel" ) func init() { @@ -35,6 +44,7 @@ func (p *AlmanachEditPage) Setup(router *router.Router[*core.RequestEvent], app rg := router.Group(URL_ALMANACH) rg.BindFunc(middleware.IsAdminOrEditor()) rg.GET(URL_ALMANACH_EDIT, p.GET(engine, app)) + rg.POST(URL_ALMANACH_EDIT+"save", p.POSTSave(engine, app)) return nil } @@ -60,6 +70,10 @@ func (p *AlmanachEditPage) GET(engine *templating.Engine, app core.App) HandleFu data["abbrs"] = abbrs } + if msg := e.Request.URL.Query().Get("saved_message"); msg != "" { + data["success"] = msg + } + return engine.Response200(e, p.Template, data, p.Layout) } } @@ -97,3 +111,481 @@ func NewAlmanachEditResult(app core.App, id string, filters BeitraegeFilterParam Prev: prev, }, nil } + +func (p *AlmanachEditPage) POSTSave(engine *templating.Engine, app core.App) HandleFunc { + return func(e *core.RequestEvent) error { + id := e.Request.PathValue("id") + req := templating.NewRequest(e) + + payload := almanachEditPayload{} + if err := e.BindBody(&payload); err != nil { + return e.JSON(http.StatusBadRequest, map[string]any{ + "error": "Ungültige Formulardaten.", + }) + } + + if err := req.CheckCSRF(payload.CSRFToken); err != nil { + return e.JSON(http.StatusBadRequest, map[string]any{ + "error": err.Error(), + }) + } + + entry, err := dbmodels.Entries_MusenalmID(app, id) + if err != nil { + return e.JSON(http.StatusNotFound, map[string]any{ + "error": "Band wurde nicht gefunden.", + }) + } + + if err := payload.Validate(); err != nil { + return e.JSON(http.StatusBadRequest, map[string]any{ + "error": err.Error(), + }) + } + + if payload.LastEdited != "" { + lastEdited, err := types.ParseDateTime(payload.LastEdited) + if err != nil { + return e.JSON(http.StatusBadRequest, map[string]any{ + "error": "Ungültiger Bearbeitungszeitstempel.", + }) + } + if !entry.Updated().Time().Equal(lastEdited.Time()) { + return e.JSON(http.StatusConflict, map[string]any{ + "error": "Der Eintrag wurde inzwischen geändert. Bitte Seite neu laden.", + }) + } + } + + user := req.User() + if err := app.RunInTransaction(func(tx core.App) error { + if err := applyEntryChanges(tx, entry, &payload, user); err != nil { + return err + } + if err := applyItemsChanges(tx, entry, &payload); err != nil { + return err + } + if err := applySeriesRelations(tx, entry, &payload); err != nil { + return err + } + if err := applyAgentRelations(tx, entry, &payload); err != nil { + return err + } + return nil + }); err != nil { + app.Logger().Error("Failed to save almanach entry", "entry_id", entry.Id, "error", err) + return e.JSON(http.StatusInternalServerError, map[string]any{ + "error": "Speichern fehlgeschlagen.", + }) + } + + freshEntry, err := dbmodels.Entries_MusenalmID(app, id) + if err == nil { + entry = freshEntry + } + + updatedInfo := map[string]string{ + "raw": entry.Updated().Time().Format(time.RFC3339Nano), + "date": functions.GermanDate(entry.Updated()), + "time": functions.GermanTime(entry.Updated()), + } + if user != nil && strings.TrimSpace(user.Name) != "" { + updatedInfo["user"] = user.Name + } + + return e.JSON(http.StatusOK, map[string]any{ + "success": true, + "message": "Änderungen gespeichert.", + "updated": updatedInfo, + }) + } +} + +type almanachEditPayload struct { + CSRFToken string `json:"csrf_token"` + LastEdited string `json:"last_edited"` + Entry almanachEntryPayload `json:"entry"` + Languages []string `json:"languages"` + Places []string `json:"places"` + Items []almanachItemPayload `json:"items"` + DeletedItemIDs []string `json:"deleted_item_ids"` + SeriesRelations []almanachRelationPayload `json:"series_relations"` + NewSeriesRelations []almanachNewRelationPayload `json:"new_series_relations"` + DeletedSeriesRelationIDs []string `json:"deleted_series_relation_ids"` + AgentRelations []almanachRelationPayload `json:"agent_relations"` + NewAgentRelations []almanachNewRelationPayload `json:"new_agent_relations"` + DeletedAgentRelationIDs []string `json:"deleted_agent_relation_ids"` +} + +type almanachEntryPayload struct { + PreferredTitle string `json:"preferred_title"` + Title string `json:"title"` + ParallelTitle string `json:"parallel_title"` + Subtitle string `json:"subtitle"` + VariantTitle string `json:"variant_title"` + Incipit string `json:"incipit"` + ResponsibilityStatement string `json:"responsibility_statement"` + PublicationStatement string `json:"publication_statement"` + PlaceStatement string `json:"place_statement"` + Edition string `json:"edition"` + Annotation string `json:"annotation"` + EditComment string `json:"edit_comment"` + Extent string `json:"extent"` + Dimensions string `json:"dimensions"` + References string `json:"references"` + Status string `json:"status"` + Year *int `json:"year"` +} + +type almanachItemPayload struct { + ID string `json:"id"` + Owner string `json:"owner"` + Identifier string `json:"identifier"` + Location string `json:"location"` + Media []string `json:"media"` + Annotation string `json:"annotation"` + URI string `json:"uri"` +} + +type almanachRelationPayload struct { + ID string `json:"id"` + TargetID string `json:"target_id"` + Type string `json:"type"` + Uncertain bool `json:"uncertain"` +} + +type almanachNewRelationPayload struct { + TargetID string `json:"target_id"` + Type string `json:"type"` + Uncertain bool `json:"uncertain"` +} + +func (payload *almanachEditPayload) Validate() error { + payload.Entry.Status = strings.TrimSpace(payload.Entry.Status) + if strings.TrimSpace(payload.Entry.PreferredTitle) == "" { + return fmt.Errorf("Kurztitel ist erforderlich.") + } + if payload.Entry.Year == nil { + return fmt.Errorf("Jahr muss angegeben werden.") + } + if payload.Entry.Status == "" || !slices.Contains(dbmodels.EDITORSTATE_VALUES, payload.Entry.Status) { + return fmt.Errorf("Ungültiger Status.") + } + for _, relation := range payload.SeriesRelations { + if err := validateRelationType(relation.Type, dbmodels.SERIES_RELATIONS); err != nil { + return err + } + } + for _, relation := range payload.NewSeriesRelations { + if err := validateRelationType(relation.Type, dbmodels.SERIES_RELATIONS); err != nil { + return err + } + } + for _, relation := range payload.AgentRelations { + if err := validateRelationType(relation.Type, dbmodels.AGENT_RELATIONS); err != nil { + return err + } + } + for _, relation := range payload.NewAgentRelations { + if err := validateRelationType(relation.Type, dbmodels.AGENT_RELATIONS); err != nil { + return err + } + } + + hasPreferred := false + for _, relation := range payload.SeriesRelations { + if strings.TrimSpace(relation.Type) == preferredSeriesRelationType { + hasPreferred = true + break + } + } + if !hasPreferred { + for _, relation := range payload.NewSeriesRelations { + if strings.TrimSpace(relation.Type) == preferredSeriesRelationType { + hasPreferred = true + break + } + } + } + if !hasPreferred { + return fmt.Errorf("Mindestens ein bevorzugter Reihentitel muss verknüpft sein.") + } + + return nil +} + +func validateRelationType(value string, allowed []string) error { + value = strings.TrimSpace(value) + if value == "" { + return fmt.Errorf("Ungültiger Beziehungstyp.") + } + if !slices.Contains(allowed, value) { + return fmt.Errorf("Ungültiger Beziehungstyp.") + } + return nil +} + +func sanitizeStrings(values []string) []string { + seen := map[string]struct{}{} + cleaned := make([]string, 0, len(values)) + for _, value := range values { + value = strings.TrimSpace(value) + if value == "" { + continue + } + if _, ok := seen[value]; ok { + continue + } + seen[value] = struct{}{} + cleaned = append(cleaned, value) + } + return cleaned +} + +func applyEntryChanges(tx core.App, entry *dbmodels.Entry, payload *almanachEditPayload, user *dbmodels.FixedUser) error { + entry.SetPreferredTitle(strings.TrimSpace(payload.Entry.PreferredTitle)) + entry.SetTitleStmt(strings.TrimSpace(payload.Entry.Title)) + entry.SetParallelTitle(strings.TrimSpace(payload.Entry.ParallelTitle)) + entry.SetSubtitleStmt(strings.TrimSpace(payload.Entry.Subtitle)) + entry.SetVariantTitle(strings.TrimSpace(payload.Entry.VariantTitle)) + entry.SetIncipitStmt(strings.TrimSpace(payload.Entry.Incipit)) + entry.SetResponsibilityStmt(strings.TrimSpace(payload.Entry.ResponsibilityStatement)) + entry.SetPublicationStmt(strings.TrimSpace(payload.Entry.PublicationStatement)) + entry.SetPlaceStmt(strings.TrimSpace(payload.Entry.PlaceStatement)) + entry.SetEdition(strings.TrimSpace(payload.Entry.Edition)) + entry.SetAnnotation(strings.TrimSpace(payload.Entry.Annotation)) + entry.SetComment(strings.TrimSpace(payload.Entry.EditComment)) + entry.SetExtent(strings.TrimSpace(payload.Entry.Extent)) + entry.SetDimensions(strings.TrimSpace(payload.Entry.Dimensions)) + entry.SetReferences(strings.TrimSpace(payload.Entry.References)) + entry.SetYear(*payload.Entry.Year) + entry.SetEditState(payload.Entry.Status) + entry.SetLanguage(sanitizeStrings(payload.Languages)) + entry.SetPlaces(sanitizeStrings(payload.Places)) + if user != nil { + entry.SetEditor(user.Id) + } + return tx.Save(entry) +} + +func applyItemsChanges(tx core.App, entry *dbmodels.Entry, payload *almanachEditPayload) error { + var itemsCollection *core.Collection + getItemsCollection := func() (*core.Collection, error) { + if itemsCollection != nil { + return itemsCollection, nil + } + collection, err := tx.FindCollectionByNameOrId(dbmodels.ITEMS_TABLE) + if err != nil { + return nil, err + } + itemsCollection = collection + return itemsCollection, nil + } + + for _, itemPayload := range payload.Items { + itemID := strings.TrimSpace(itemPayload.ID) + var item *dbmodels.Item + if itemID != "" { + record, err := tx.FindRecordById(dbmodels.ITEMS_TABLE, itemID) + if err != nil { + return err + } + item = dbmodels.NewItem(record) + if item.Entry() != entry.Id { + return fmt.Errorf("Exemplar %s gehört zu einem anderen Eintrag.", itemID) + } + } else { + collection, err := getItemsCollection() + if err != nil { + return err + } + item = dbmodels.NewItem(core.NewRecord(collection)) + } + + item.SetEntry(entry.Id) + item.SetOwner(strings.TrimSpace(itemPayload.Owner)) + item.SetIdentifier(strings.TrimSpace(itemPayload.Identifier)) + item.SetLocation(strings.TrimSpace(itemPayload.Location)) + item.SetAnnotation(strings.TrimSpace(itemPayload.Annotation)) + item.SetUri(strings.TrimSpace(itemPayload.URI)) + item.SetMedia(sanitizeStrings(itemPayload.Media)) + + if err := tx.Save(item); err != nil { + return err + } + } + + for _, id := range payload.DeletedItemIDs { + itemID := strings.TrimSpace(id) + if itemID == "" { + continue + } + record, err := tx.FindRecordById(dbmodels.ITEMS_TABLE, itemID) + if err != nil { + continue + } + item := dbmodels.NewItem(record) + if item.Entry() != entry.Id { + continue + } + if err := tx.Delete(record); err != nil { + return err + } + } + + return nil +} + +func applySeriesRelations(tx core.App, entry *dbmodels.Entry, payload *almanachEditPayload) error { + tableName := dbmodels.RelationTableName(dbmodels.ENTRIES_TABLE, dbmodels.SERIES_TABLE) + var collection *core.Collection + getCollection := func() (*core.Collection, error) { + if collection != nil { + return collection, nil + } + col, err := tx.FindCollectionByNameOrId(tableName) + if err != nil { + return nil, err + } + collection = col + return collection, nil + } + + for _, relation := range payload.SeriesRelations { + relationID := strings.TrimSpace(relation.ID) + if relationID == "" { + continue + } + record, err := tx.FindRecordById(tableName, relationID) + if err != nil { + return err + } + proxy := dbmodels.NewREntriesSeries(record) + if proxy.Entry() != entry.Id { + return fmt.Errorf("Relation %s gehört zu einem anderen Eintrag.", relationID) + } + proxy.SetEntry(entry.Id) + proxy.SetSeries(strings.TrimSpace(relation.TargetID)) + proxy.SetType(strings.TrimSpace(relation.Type)) + proxy.SetUncertain(relation.Uncertain) + if err := tx.Save(proxy); err != nil { + return err + } + } + + for _, relationID := range payload.DeletedSeriesRelationIDs { + relationID = strings.TrimSpace(relationID) + if relationID == "" { + continue + } + record, err := tx.FindRecordById(tableName, relationID) + if err != nil { + continue + } + proxy := dbmodels.NewREntriesSeries(record) + if proxy.Entry() != entry.Id { + continue + } + if err := tx.Delete(record); err != nil { + return err + } + } + + for _, relation := range payload.NewSeriesRelations { + targetID := strings.TrimSpace(relation.TargetID) + if targetID == "" { + continue + } + col, err := getCollection() + if err != nil { + return err + } + proxy := dbmodels.NewREntriesSeries(core.NewRecord(col)) + proxy.SetEntry(entry.Id) + proxy.SetSeries(targetID) + proxy.SetType(strings.TrimSpace(relation.Type)) + proxy.SetUncertain(relation.Uncertain) + if err := tx.Save(proxy); err != nil { + return err + } + } + + return nil +} + +func applyAgentRelations(tx core.App, entry *dbmodels.Entry, payload *almanachEditPayload) error { + tableName := dbmodels.RelationTableName(dbmodels.ENTRIES_TABLE, dbmodels.AGENTS_TABLE) + var collection *core.Collection + getCollection := func() (*core.Collection, error) { + if collection != nil { + return collection, nil + } + col, err := tx.FindCollectionByNameOrId(tableName) + if err != nil { + return nil, err + } + collection = col + return collection, nil + } + + for _, relation := range payload.AgentRelations { + relationID := strings.TrimSpace(relation.ID) + if relationID == "" { + continue + } + record, err := tx.FindRecordById(tableName, relationID) + if err != nil { + return err + } + proxy := dbmodels.NewREntriesAgents(record) + if proxy.Entry() != entry.Id { + return fmt.Errorf("Relation %s gehört zu einem anderen Eintrag.", relationID) + } + proxy.SetEntry(entry.Id) + proxy.SetAgent(strings.TrimSpace(relation.TargetID)) + proxy.SetType(strings.TrimSpace(relation.Type)) + proxy.SetUncertain(relation.Uncertain) + if err := tx.Save(proxy); err != nil { + return err + } + } + + for _, relationID := range payload.DeletedAgentRelationIDs { + relationID = strings.TrimSpace(relationID) + if relationID == "" { + continue + } + record, err := tx.FindRecordById(tableName, relationID) + if err != nil { + continue + } + proxy := dbmodels.NewREntriesAgents(record) + if proxy.Entry() != entry.Id { + continue + } + if err := tx.Delete(record); err != nil { + return err + } + } + + for _, relation := range payload.NewAgentRelations { + targetID := strings.TrimSpace(relation.TargetID) + if targetID == "" { + continue + } + col, err := getCollection() + if err != nil { + return err + } + proxy := dbmodels.NewREntriesAgents(core.NewRecord(col)) + proxy.SetEntry(entry.Id) + proxy.SetAgent(targetID) + proxy.SetType(strings.TrimSpace(relation.Type)) + proxy.SetUncertain(relation.Uncertain) + if err := tx.Save(proxy); err != nil { + return err + } + } + + return nil +} diff --git a/views/assets/scripts.js b/views/assets/scripts.js index 18a40c4..d036cca 100644 --- a/views/assets/scripts.js +++ b/views/assets/scripts.js @@ -1,11 +1,11 @@ -var Ot = Object.defineProperty; -var K = (a) => { - throw TypeError(a); +var $t = Object.defineProperty; +var j = (l) => { + throw TypeError(l); }; -var Mt = (a, i, t) => i in a ? Ot(a, i, { enumerable: !0, configurable: !0, writable: !0, value: t }) : a[i] = t; -var f = (a, i, t) => Mt(a, typeof i != "symbol" ? i + "" : i, t), x = (a, i, t) => i.has(a) || K("Cannot " + t); -var k = (a, i, t) => (x(a, i, "read from private field"), t ? t.call(a) : i.get(a)), g = (a, i, t) => i.has(a) ? K("Cannot add the same private member more than once") : i instanceof WeakSet ? i.add(a) : i.set(a, t), L = (a, i, t, e) => (x(a, i, "write to private field"), e ? e.call(a, t) : i.set(a, t), t), v = (a, i, t) => (x(a, i, "access private method"), t); -class Rt extends HTMLElement { +var Nt = (l, i, t) => i in l ? $t(l, i, { enumerable: !0, configurable: !0, writable: !0, value: t }) : l[i] = t; +var E = (l, i, t) => Nt(l, typeof i != "symbol" ? i + "" : i, t), B = (l, i, t) => i.has(l) || j("Cannot " + t); +var $ = (l, i, t) => (B(l, i, "read from private field"), t ? t.call(l) : i.get(l)), S = (l, i, t) => i.has(l) ? j("Cannot add the same private member more than once") : i instanceof WeakSet ? i.add(l) : i.set(l, t), I = (l, i, t, e) => (B(l, i, "write to private field"), e ? e.call(l, t) : i.set(l, t), t), T = (l, i, t) => (B(l, i, "access private method"), t); +class Pt extends HTMLElement { constructor() { super(), this._value = "", this.render(); } @@ -74,13 +74,13 @@ class Rt extends HTMLElement { `; } } -const A = "filter-list-list", Bt = "filter-list-item", Nt = "filter-list-input", z = "filter-list-searchable"; -var _, S, F; -class $t extends HTMLElement { +const C = "filter-list-list", qt = "filter-list-item", Dt = "filter-list-input", J = "filter-list-searchable"; +var b, y, W; +class Ht extends HTMLElement { constructor() { super(); - g(this, S); - g(this, _, !1); + S(this, y); + S(this, b, !1); this._items = [], this._url = "", this._filterstart = !1, this._placeholder = "Liste filtern...", this._queryparam = "", this._startparams = null, this.render(); } static get observedAttributes() { @@ -93,7 +93,7 @@ class $t 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 && L(this, _, !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 && I(this, b, !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()), t === "data-queryparam" && e !== s && (this._queryparam = s, this.render()); @@ -102,14 +102,14 @@ class $t extends HTMLElement { t.target && t.target.tagName.toLowerCase() === "input" && (this._filter = t.target.value, this.renderList()); } onGainFocus(t) { - t.target && t.target.tagName.toLowerCase() === "input" && (L(this, _, !1), this.renderList()); + t.target && t.target.tagName.toLowerCase() === "input" && (I(this, b, !1), this.renderList()); } onLoseFocus(t) { let e = this.querySelector("input"); if (t.target && t.target === e) { if (relatedElement = t.relatedTarget, relatedElement && this.contains(relatedElement)) return; - e.value = "", this._filter = "", this._filterstart && L(this, _, !0), this.renderList(); + e.value = "", this._filter = "", this._filterstart && I(this, b, !0), this.renderList(); } } onEnter(t) { @@ -122,10 +122,10 @@ class $t extends HTMLElement { mark() { if (typeof Mark != "function") return; - let t = this.querySelector("#" + A); + let t = this.querySelector("#" + C); if (!t) return; - let e = new Mark(t.querySelectorAll("." + z)); + let e = new Mark(t.querySelectorAll("." + J)); this._filter && e.mark(this._filter, { separateWordSearch: !0 }); @@ -165,7 +165,7 @@ class $t extends HTMLElement { } getLinkText(t) { let e = this.getSearchText(t); - return e === "" ? "" : `${e}`; + return e === "" ? "" : `${e}`; } getURL(t) { if (this._queryparam) { @@ -175,7 +175,7 @@ class $t extends HTMLElement { return this._url + this.getHREFEncoded(t); } renderList() { - let t = this.querySelector("#" + A); + let t = this.querySelector("#" + C); t && (t.outerHTML = this.List()), this.mark(); } render() { @@ -187,7 +187,7 @@ class $t extends HTMLElement { `, htmx && htmx.process(this); } ActiveDot(t) { - return v(this, S, F).call(this, t), ""; + return T(this, y, W).call(this, t), ""; } NoItems(t) { return t.length === 0 ? '
Keine Einträge gefunden
' : ""; @@ -200,7 +200,7 @@ class $t extends HTMLElement { + class="${Dt} w-full placeholder:italic px-2 py-0.5" /> `; @@ -215,7 +215,7 @@ class $t extends HTMLElement { t = this._items.filter((s) => e.every((n) => this.getSearchText(s).toLowerCase().includes(n.toLowerCase()))); } return ` -
+
${t.map( (e, s) => ` + class="${qt} block px-2.5 py-0.5 hover:bg-slate-200 no-underline ${s % 2 === 0 ? "bg-stone-100" : "bg-stone-50"}" + ${T(this, y, W).call(this, e) ? 'aria-current="page"' : ""}> ${this.ActiveDot(e)} ${this.getLinkText(e)} @@ -236,13 +236,13 @@ class $t extends HTMLElement { `; } } -_ = new WeakMap(), S = new WeakSet(), F = function(t) { +b = new WeakMap(), y = new WeakSet(), W = function(t) { if (!t) return !1; let e = this.getHREF(t); return e === "" ? !1 : this._queryparam && (new URLSearchParams(window.location.search).get(this._queryparam) || "") === e ? !0 : !!window.location.href.endsWith(e); }; -class Dt extends HTMLElement { +class Ft extends HTMLElement { constructor() { super(), this.handleScroll = this.handleScroll.bind(this), this.scrollToTop = this.scrollToTop.bind(this); } @@ -278,7 +278,7 @@ class Dt extends HTMLElement { window.scrollTo({ top: 0, behavior: "smooth" }); } } -class Pt extends HTMLElement { +class Vt extends HTMLElement { static get observedAttributes() { return ["position", "timeout"]; } @@ -379,7 +379,7 @@ class Pt extends HTMLElement { } } } -class qt extends HTMLElement { +class Ut extends HTMLElement { constructor() { super(), this.overlay = null, this._others = null, this._thisindex = -1, this._preview = null, this._description = null, this._imageURL = "", this._hideDLButton = !1; } @@ -487,7 +487,7 @@ class qt extends HTMLElement { this.overlay.parentNode.removeChild(this.overlay), this.overlay = null; } } -class Ht extends HTMLElement { +class Kt extends HTMLElement { static get observedAttributes() { } constructor() { @@ -526,11 +526,11 @@ class Ht extends HTMLElement { this._showall = !0, this.shown = -1, this.disable(), this._contents.forEach((i, t) => { i.classList.remove("hidden"); let e = this._headings[t], s = e.querySelectorAll(".show-opened"); - for (let l of s) - l.classList.add("hidden"); + for (let a of s) + a.classList.add("hidden"); let n = e.querySelectorAll(".show-closed"); - for (let l of n) - l.classList.add("hidden"); + for (let a of n) + a.classList.add("hidden"); }); } default() { @@ -575,7 +575,7 @@ class Ht extends HTMLElement { return null; } } -class E extends HTMLElement { +class L extends HTMLElement { static get observedAttributes() { return ["data-text", "data-abbrevmap"]; } @@ -638,7 +638,7 @@ class E extends HTMLElement { }; } constructor() { - super(), this._abbrevMap = E.defaultAbbrevMap; + super(), this._abbrevMap = L.defaultAbbrevMap; } connectedCallback() { this.render(); @@ -648,13 +648,13 @@ class E extends HTMLElement { } _parseAndSetAbbrevMap(i) { if (!i) { - this._abbrevMap = E.defaultAbbrevMap; + this._abbrevMap = L.defaultAbbrevMap; return; } try { this._abbrevMap = JSON.parse(i); } catch { - this._abbrevMap = E.defaultAbbrevMap; + this._abbrevMap = L.defaultAbbrevMap; } } setAbbrevMap(i) { @@ -678,17 +678,17 @@ class E extends HTMLElement { } const n = this.findLongestAbbrevAt(i, s, t); if (n) { - const { match: l, meaning: r } = n; + const { match: a, meaning: r } = n; e += `
${r}
- ${l} + ${a}
- `, s += l.length; + `, s += a.length; } else e += i[s], s++; } @@ -696,15 +696,15 @@ class E extends HTMLElement { } findLongestAbbrevAt(i, t, e) { let s = null, n = 0; - for (const l of Object.keys(e)) - i.startsWith(l, t) && l.length > n && (s = l, n = l.length); + for (const a of Object.keys(e)) + i.startsWith(a, t) && a.length > n && (s = a, n = a.length); return s ? { match: s, meaning: e[s] } : null; } isSpaceOrPunct(i) { return /\s|[.,;:!?]/.test(i); } } -class Ft extends HTMLElement { +class zt extends HTMLElement { constructor() { super(); } @@ -722,11 +722,11 @@ class Ft extends HTMLElement { } } } -var T; -class Ut extends HTMLElement { +var k; +class Wt extends HTMLElement { constructor() { super(); - g(this, T, 176); + S(this, k, 176); this._images = []; } connectedCallback() { @@ -742,17 +742,17 @@ class Ut extends HTMLElement { calculateShownImages() { const t = this.getBoundingClientRect(); console.log(t); - const e = Math.floor(t.width / (k(this, T) + 10)); + const e = Math.floor(t.width / ($(this, k) + 10)); for (let s = 0; s < this._images.length; s++) s < e - 1 ? this._images[s].classList.remove("hidden") : this._images[s].classList.add("hidden"); } } -T = new WeakMap(); -const Vt = "msr-component-wrapper", W = "msr-selected-items-container", G = "msr-placeholder-no-selection-text", Kt = "msr-selected-item-pill", zt = "msr-selected-item-text", Wt = "msr-item-name", Gt = "msr-item-additional-data", jt = "msr-selected-item-role", j = "msr-selected-item-delete-btn", Jt = "msr-controls-area", J = "msr-pre-add-button", Q = "msr-input-area-wrapper", I = "msr-input-area-default-border", O = "msr-input-area-staged", X = "msr-staging-area-container", Qt = "msr-staged-item-pill", Xt = "msr-staged-item-text", M = "msr-staged-role-select", Y = "msr-staged-cancel-btn", Z = "msr-text-input", tt = "msr-add-button", et = "msr-options-list", it = "msr-option-item", Yt = "msr-option-item-name", Zt = "msr-option-item-detail", st = "msr-option-item-highlighted", R = "msr-hidden-select", te = "msr-state-no-selection", ee = "msr-state-has-selection", ie = "msr-state-list-open", se = "msr-state-item-staged"; -class yt extends HTMLElement { +k = new WeakMap(); +const Gt = "msr-component-wrapper", Q = "msr-selected-items-container", X = "msr-placeholder-no-selection-text", jt = "msr-selected-item-pill", Jt = "msr-selected-item-text", Qt = "msr-item-name", Xt = "msr-item-additional-data", Yt = "msr-selected-item-role", Y = "msr-selected-item-delete-btn", Zt = "msr-controls-area", Z = "msr-pre-add-button", tt = "msr-input-area-wrapper", w = "msr-input-area-default-border", N = "msr-input-area-staged", et = "msr-staging-area-container", te = "msr-staged-item-pill", ee = "msr-staged-item-text", P = "msr-staged-role-select", it = "msr-staged-cancel-btn", st = "msr-text-input", nt = "msr-add-button", at = "msr-options-list", lt = "msr-option-item", ie = "msr-option-item-name", se = "msr-option-item-detail", rt = "msr-option-item-highlighted", q = "msr-hidden-select", ne = "msr-state-no-selection", ae = "msr-state-has-selection", le = "msr-state-list-open", re = "msr-state-item-staged"; +class xt extends HTMLElement { constructor() { super(); - f(this, "_blurTimeout", null); + E(this, "_blurTimeout", null); this.internals_ = this.attachInternals(), this._value = [], this._stagedItem = null, this._showAddButton = !0, this._placeholderNoSelection = "Keine Elemente ausgewählt", this._placeholderSearch = "Elemente suchen...", this._placeholderRoleSelect = "Rolle auswählen...", this._options = [], this._roles = [ "Leitung", "Unterstützung", @@ -828,23 +828,23 @@ class yt extends HTMLElement { } _setupTemplates() { this.optionTemplate = document.createElement("template"), this.optionTemplate.innerHTML = ` -
  • - - +
  • + +
  • `, this.selectedItemTemplate = document.createElement("template"), this.selectedItemTemplate.innerHTML = ` - - - + + + `, this.stagedPlacePillTemplate = document.createElement("template"), this.stagedPlacePillTemplate.innerHTML = ` - - + + `, this.stagedCancelBtnTemplate = document.createElement("template"), this.stagedCancelBtnTemplate.innerHTML = ` - + `, this.stagedRoleSelectTemplate = document.createElement("template"), this.stagedRoleSelectTemplate.innerHTML = ` - `; } @@ -879,9 +879,9 @@ class yt extends HTMLElement { } set value(t) { if (Array.isArray(t)) { - const e = t.map((l) => { - if (typeof l == "string") { - const r = l.split(","); + const e = t.map((a) => { + if (typeof a == "string") { + const r = a.split(","); if (r.length === 2) { const o = r[0].trim(), d = r[1].trim(); if (this._getItemById(o) && this._roles.includes(d)) @@ -889,10 +889,10 @@ class yt extends HTMLElement { } } return null; - }).filter((l) => l !== null), s = [], n = /* @__PURE__ */ new Set(); - for (const l of e) { - const r = `${l.itemId},${l.role}`; - n.has(r) || (s.push(l), n.add(r)); + }).filter((a) => a !== null), s = [], n = /* @__PURE__ */ new Set(); + for (const a of e) { + const r = `${a.itemId},${a.role}`; + n.has(r) || (s.push(a), n.add(r)); } this._value = s; } else @@ -906,7 +906,7 @@ class yt extends HTMLElement { this.setAttribute("name", t), this.hiddenSelect && (this.hiddenSelect.name = t); } connectedCallback() { - if (this.placeholderNoSelection = this.getAttribute("placeholder-no-selection") || this._placeholderNoSelection, this.placeholderSearch = this.getAttribute("placeholder-search") || this._placeholderSearch, this.placeholderRoleSelect = this.getAttribute("placeholder-role-select") || this._placeholderRoleSelect, this._render(), this.inputAreaWrapper = this.querySelector(`.${Q}`), this.inputElement = this.querySelector(`.${Z}`), this.stagedItemPillContainer = this.querySelector(`.${X}`), this.optionsListElement = this.querySelector(`.${et}`), this.selectedItemsContainer = this.querySelector(`.${W}`), this.addButtonElement = this.querySelector(`.${tt}`), this.preAddButtonElement = this.querySelector(`.${J}`), this.hiddenSelect = this.querySelector(`.${R}`), this.name && this.hiddenSelect && (this.hiddenSelect.name = this.name), this.hasAttribute("show-add-button") ? this.showAddButton = this.getAttribute("show-add-button") : this.setAttribute("show-add-button", String(this._showAddButton)), this.inputElement && (this.inputElement.placeholder = this.placeholderSearch), this.inputElement.addEventListener("input", this._handleInput), this.inputElement.addEventListener("keydown", this._handleInputKeyDown), this.inputElement.addEventListener("focus", this._handleFocus), this.inputElement.addEventListener("blur", this._handleBlur), this.optionsListElement.addEventListener("mousedown", this._handleOptionMouseDown), this.optionsListElement.addEventListener("click", this._handleOptionClick), this.addButtonElement.addEventListener("click", this._handleAddButtonClick), this.addEventListener("keydown", this._handleKeyDown), this._renderStagedPillOrInput(), this._updateAddButtonState(), this._updatePreAddButtonVisibility(), this._updateRootElementStateClasses(), this.hasAttribute("value")) { + if (this.placeholderNoSelection = this.getAttribute("placeholder-no-selection") || this._placeholderNoSelection, this.placeholderSearch = this.getAttribute("placeholder-search") || this._placeholderSearch, this.placeholderRoleSelect = this.getAttribute("placeholder-role-select") || this._placeholderRoleSelect, this._render(), this.inputAreaWrapper = this.querySelector(`.${tt}`), this.inputElement = this.querySelector(`.${st}`), this.stagedItemPillContainer = this.querySelector(`.${et}`), this.optionsListElement = this.querySelector(`.${at}`), this.selectedItemsContainer = this.querySelector(`.${Q}`), this.addButtonElement = this.querySelector(`.${nt}`), this.preAddButtonElement = this.querySelector(`.${Z}`), this.hiddenSelect = this.querySelector(`.${q}`), this.name && this.hiddenSelect && (this.hiddenSelect.name = this.name), this.hasAttribute("show-add-button") ? this.showAddButton = this.getAttribute("show-add-button") : this.setAttribute("show-add-button", String(this._showAddButton)), this.inputElement && (this.inputElement.placeholder = this.placeholderSearch), this.inputElement.addEventListener("input", this._handleInput), this.inputElement.addEventListener("keydown", this._handleInputKeyDown), this.inputElement.addEventListener("focus", this._handleFocus), this.inputElement.addEventListener("blur", this._handleBlur), this.optionsListElement.addEventListener("mousedown", this._handleOptionMouseDown), this.optionsListElement.addEventListener("click", this._handleOptionClick), this.addButtonElement.addEventListener("click", this._handleAddButtonClick), this.addEventListener("keydown", this._handleKeyDown), this._renderStagedPillOrInput(), this._updateAddButtonState(), this._updatePreAddButtonVisibility(), this._updateRootElementStateClasses(), this.hasAttribute("value")) { const t = this.getAttribute("value"); try { const e = JSON.parse(t); @@ -934,10 +934,10 @@ class yt extends HTMLElement { this.disabledCallback(t); } disabledCallback(t) { - this.inputElement && (this.inputElement.disabled = t), this.classList.toggle("pointer-events-none", t), this.querySelectorAll(`.${j}`).forEach( + this.inputElement && (this.inputElement.disabled = t), this.classList.toggle("pointer-events-none", t), this.querySelectorAll(`.${Y}`).forEach( (s) => s.disabled = t ); - const e = this.querySelector(`.${M}`); + const e = this.querySelector(`.${P}`); e && (e.disabled = t), this.hiddenSelect && (this.hiddenSelect.disabled = t), this._updateAddButtonState(), this._updatePreAddButtonVisibility(); } formResetCallback() { @@ -957,39 +957,39 @@ class yt extends HTMLElement { this.internals_.setFormValue(null), this._synchronizeHiddenSelect(); } _updateRootElementStateClasses() { - this.classList.toggle(te, this._value.length === 0), this.classList.toggle(ee, this._value.length > 0), this.classList.toggle(ie, this._isOptionsListVisible), this.classList.toggle(se, !!this._stagedItem); + this.classList.toggle(ne, this._value.length === 0), this.classList.toggle(ae, this._value.length > 0), this.classList.toggle(le, this._isOptionsListVisible), this.classList.toggle(re, !!this._stagedItem); } _render() { const t = this.id || `msr-${crypto.randomUUID().slice(0, 8)}`; this.id || this.setAttribute("id", t), this.innerHTML = ` -
    -
    - ${this._value.length === 0 ? `${this.placeholderNoSelection}` : ""} +
    +
    + ${this._value.length === 0 ? `${this.placeholderNoSelection}` : ""}
    -
    -
    - +
    +
    +
    - +
    - - + +
    `; } @@ -999,10 +999,10 @@ class yt extends HTMLElement { } _createStagedRoleSelectElement(t, e) { const n = this.stagedRoleSelectTemplate.content.cloneNode(!0).firstElementChild; - let l = ``; - return t.length === 0 && !this._roles.includes(e) ? (l += "", n.disabled = !0) : (t.forEach((r) => { - l += ``; - }), n.disabled = t.length === 0 && e === ""), n.innerHTML = l, n.addEventListener("change", this._handleStagedRoleChange), n; + let a = ``; + return t.length === 0 && !this._roles.includes(e) ? (a += "", n.disabled = !0) : (t.forEach((r) => { + a += ``; + }), n.disabled = t.length === 0 && e === ""), n.innerHTML = a, n.addEventListener("change", this._handleStagedRoleChange), n; } _createStagedCancelButtonElement(t) { const s = this.stagedCancelBtnTemplate.content.cloneNode(!0).firstElementChild; @@ -1011,7 +1011,7 @@ class yt extends HTMLElement { _renderStagedPillOrInput() { if (!(!this.stagedItemPillContainer || !this.inputElement || !this.inputAreaWrapper)) { if (this.stagedItemPillContainer.innerHTML = "", this._stagedItem && this._stagedItem.item) { - this.inputAreaWrapper.classList.remove(I), this.inputAreaWrapper.classList.add(O); + this.inputAreaWrapper.classList.remove(w), this.inputAreaWrapper.classList.add(N); const t = this._createStagedItemPillElement(this._stagedItem.item); this.stagedItemPillContainer.appendChild(t); const e = this._getAvailableRolesForItem(this._stagedItem.item.id), s = this._createStagedRoleSelectElement( @@ -1022,7 +1022,7 @@ class yt extends HTMLElement { const n = this._createStagedCancelButtonElement(this._stagedItem.item.name); this.stagedItemPillContainer.appendChild(n), this.inputElement.classList.add("hidden"), this.inputElement.value = "", this.inputElement.removeAttribute("aria-activedescendant"), this.inputElement.setAttribute("aria-expanded", "false"); } else - this.inputAreaWrapper.classList.add(I), this.inputAreaWrapper.classList.remove(O), this.inputElement.classList.remove("hidden"); + this.inputAreaWrapper.classList.add(w), this.inputAreaWrapper.classList.remove(N), this.inputElement.classList.remove("hidden"); this._updateAddButtonState(), this._updatePreAddButtonVisibility(), this._updateRootElementStateClasses(); } } @@ -1040,16 +1040,16 @@ class yt extends HTMLElement { _createSelectedItemElement(t) { const e = this._getItemById(t.itemId); if (!e) return null; - const n = this.selectedItemTemplate.content.cloneNode(!0).firstElementChild, l = n.querySelector('[data-ref="textEl"]'); - let r = `${e.name}`, o = e.additional_data ? ` (${e.additional_data})` : "", d = ` ${t.role}`; - l.innerHTML = `${r}${o}${d}`; - const h = n.querySelector('[data-ref="deleteBtn"]'); - return h.setAttribute("aria-label", `Entferne ${e.name} als ${t.role}`), h.dataset.instanceId = t.instanceId, h.disabled = this.hasAttribute("disabled"), h.addEventListener("click", (p) => { - p.stopPropagation(), this._handleDeleteSelectedItem(t.instanceId); + const n = this.selectedItemTemplate.content.cloneNode(!0).firstElementChild, a = n.querySelector('[data-ref="textEl"]'); + let r = `${e.name}`, o = e.additional_data ? ` (${e.additional_data})` : "", d = ` ${t.role}`; + a.innerHTML = `${r}${o}${d}`; + const c = n.querySelector('[data-ref="deleteBtn"]'); + return c.setAttribute("aria-label", `Entferne ${e.name} als ${t.role}`), c.dataset.instanceId = t.instanceId, c.disabled = this.hasAttribute("disabled"), c.addEventListener("click", (h) => { + h.stopPropagation(), this._handleDeleteSelectedItem(t.instanceId); }), n; } _renderSelectedItems() { - this.selectedItemsContainer && (this.selectedItemsContainer.innerHTML = "", this._value.length === 0 ? this.selectedItemsContainer.innerHTML = `${this.placeholderNoSelection}` : this._value.forEach((t) => { + this.selectedItemsContainer && (this.selectedItemsContainer.innerHTML = "", this._value.length === 0 ? this.selectedItemsContainer.innerHTML = `${this.placeholderNoSelection}` : this._value.forEach((t) => { const e = this._createSelectedItemElement(t); e && this.selectedItemsContainer.appendChild(e); }), this._updateRootElementStateClasses()); @@ -1062,7 +1062,7 @@ class yt extends HTMLElement { } _createOptionElement(t, e) { const n = this.optionTemplate.content.cloneNode(!0).firstElementChild; - return n.querySelector('[data-ref="nameEl"]').textContent = t.name, n.querySelector('[data-ref="detailEl"]').textContent = t.additional_data ? `(${t.additional_data})` : "", n.dataset.id = t.id, n.setAttribute("aria-selected", String(e === this._highlightedIndex)), n.id = `${this.id || "msr"}-option-${t.id}`, e === this._highlightedIndex && n.classList.add(st), n; + return n.querySelector('[data-ref="nameEl"]').textContent = t.name, n.querySelector('[data-ref="detailEl"]').textContent = t.additional_data ? `(${t.additional_data})` : "", n.dataset.id = t.id, n.setAttribute("aria-selected", String(e === this._highlightedIndex)), n.id = `${this.id || "msr"}-option-${t.id}`, e === this._highlightedIndex && n.classList.add(rt), n; } _renderOptionsList() { if (!(!this.optionsListElement || !this.inputElement)) { @@ -1074,7 +1074,7 @@ class yt extends HTMLElement { this.optionsListElement.appendChild(n); }); const t = this.optionsListElement.querySelector( - `.${st}` + `.${rt}` ); t ? (t.scrollIntoView({ block: "nearest" }), this.inputElement.setAttribute("aria-activedescendant", t.id)) : this.inputElement.removeAttribute("aria-activedescendant"); } @@ -1086,7 +1086,7 @@ class yt extends HTMLElement { return; this._stagedItem = { item: t, currentRole: "" }, this.inputElement && (this.inputElement.value = "", this.inputElement.setAttribute("aria-expanded", "false"), this.inputElement.removeAttribute("aria-activedescendant")), this._renderStagedPillOrInput(), this._hideOptionsList(); const s = this.stagedItemPillContainer.querySelector( - `.${M}` + `.${P}` ); s && !s.disabled ? s.focus() : this.addButtonElement && !this.addButtonElement.disabled && this.addButtonElement.focus(); } @@ -1123,7 +1123,7 @@ class yt extends HTMLElement { if (!this.hasAttribute("disabled")) { if (t.key === "Enter" && this._stagedItem && this._stagedItem.item) { const s = document.activeElement, n = (e = this.stagedItemPillContainer) == null ? void 0 : e.querySelector( - `.${Y}` + `.${it}` ); if (s === n) { t.preventDefault(), this._handleCancelStagedItem(t); @@ -1161,7 +1161,7 @@ class yt extends HTMLElement { } _handleFocus() { if (!(this.hasAttribute("disabled") || this.inputElement && this.inputElement.disabled || this._stagedItem)) { - if (!this._stagedItem && this.inputAreaWrapper && (this.inputAreaWrapper.classList.add(I), this.inputAreaWrapper.classList.remove(O)), this.inputElement && this.inputElement.value.length > 0) { + if (!this._stagedItem && this.inputAreaWrapper && (this.inputAreaWrapper.classList.add(w), this.inputAreaWrapper.classList.remove(N)), this.inputElement && this.inputElement.value.length > 0) { const t = this.inputElement.value.toLowerCase(); this._filteredOptions = this._options.filter((e) => this._getAvailableRolesForItem(e.id).length === 0 ? !1 : e.name.toLowerCase().includes(t) || e.additional_data && e.additional_data.toLowerCase().includes(t)), this._filteredOptions.length > 0 ? (this._isOptionsListVisible = !0, this._highlightedIndex = 0, this._renderOptionsList()) : this._hideOptionsList(); } else @@ -1180,9 +1180,9 @@ class yt extends HTMLElement { } _handleOptionClick(t) { if (this.hasAttribute("disabled")) return; - const e = t.target.closest(`li[data-id].${it}`); + const e = t.target.closest(`li[data-id].${lt}`); if (e) { - const s = e.dataset.id, n = this._filteredOptions.find((l) => l.id === s); + const s = e.dataset.id, n = this._filteredOptions.find((a) => a.id === s); n && this._stageItem(n); } } @@ -1190,12 +1190,12 @@ class yt extends HTMLElement { this.hasAttribute("disabled") || (this._value = this._value.filter((e) => e.instanceId !== t), this._updateFormValue(), this._renderSelectedItems(), this._stagedItem && this._stagedItem.item && this._renderStagedPillOrInput(), this.inputElement && this.inputElement.focus(), this._updatePreAddButtonVisibility()); } } -f(yt, "formAssociated", !0); -const ne = "mss-component-wrapper", nt = "mss-selected-items-container", ae = "mss-selected-item-pill", le = "mss-selected-item-text", re = "mss-selected-item-pill-detail", at = "mss-selected-item-delete-btn", lt = "mss-input-controls-container", rt = "mss-input-wrapper", ot = "mss-input-wrapper-focused", dt = "mss-text-input", ht = "mss-create-new-button", ct = "mss-toggle-button", oe = "mss-inline-row", ut = "mss-options-list", de = "mss-option-item", he = "mss-option-item-name", ce = "mss-option-item-detail", mt = "mss-option-item-highlighted", B = "mss-hidden-select", N = "mss-no-items-text", _t = "mss-loading", $ = 1, D = 10, ue = 250, me = "mss-state-no-selection", _e = "mss-state-has-selection", pe = "mss-state-list-open"; -class Tt extends HTMLElement { +E(xt, "formAssociated", !0); +const oe = "mss-component-wrapper", ot = "mss-selected-items-container", de = "mss-selected-item-pill", he = "mss-selected-item-text", ce = "mss-selected-item-pill-detail", dt = "mss-selected-item-delete-btn", ht = "mss-input-controls-container", ct = "mss-input-wrapper", ut = "mss-input-wrapper-focused", mt = "mss-text-input", _t = "mss-create-new-button", pt = "mss-toggle-button", ue = "mss-inline-row", ft = "mss-options-list", me = "mss-option-item", _e = "mss-option-item-name", pe = "mss-option-item-detail", gt = "mss-option-item-highlighted", D = "mss-hidden-select", H = "mss-no-items-text", bt = "mss-loading", F = 1, V = 10, fe = 250, ge = "mss-state-no-selection", be = "mss-state-has-selection", Ee = "mss-state-list-open"; +class kt extends HTMLElement { constructor() { super(); - f(this, "_blurTimeout", null); + E(this, "_blurTimeout", null); this.internals_ = this.attachInternals(), this._value = [], this._initialValue = [], this._initialOrder = [], this._removedIds = /* @__PURE__ */ new Set(), this._initialCaptured = !1, this._allowInitialCapture = !0, this._options = [ { id: "abk", name: "Abchasisch" }, { id: "aar", name: "Afar" }, @@ -1380,19 +1380,19 @@ class Tt extends HTMLElement { { id: "yor", name: "Yoruba" }, { id: "zha", name: "Zhuang" }, { id: "zul", name: "Zulu" } - ], this._filteredOptions = [], this._highlightedIndex = -1, this._isOptionsListVisible = !1, this._remoteEndpoint = null, this._remoteResultKey = "items", this._remoteMinChars = $, this._remoteLimit = D, this._remoteFetchController = null, this._remoteFetchTimeout = null, this._placeholder = this.getAttribute("placeholder") || "Search items...", this._showCreateButton = this.getAttribute("show-create-button") !== "false", this._toggleLabel = this.getAttribute("data-toggle-label") || "", this._toggleInput = this._toggleLabel !== "", this._inputCollapsed = this._toggleInput, this._setupTemplates(), this._bindEventHandlers(); + ], this._filteredOptions = [], this._highlightedIndex = -1, this._isOptionsListVisible = !1, this._remoteEndpoint = null, this._remoteResultKey = "items", this._remoteMinChars = F, this._remoteLimit = V, this._remoteFetchController = null, this._remoteFetchTimeout = null, this._placeholder = this.getAttribute("placeholder") || "Search items...", this._showCreateButton = this.getAttribute("show-create-button") !== "false", this._toggleLabel = this.getAttribute("data-toggle-label") || "", this._toggleInput = this._toggleLabel !== "", this._inputCollapsed = this._toggleInput, this._setupTemplates(), this._bindEventHandlers(); } _setupTemplates() { this.optionTemplate = document.createElement("template"), this.optionTemplate.innerHTML = ` -
  • - - +
  • + +
  • `, this.selectedItemTemplate = document.createElement("template"), this.selectedItemTemplate.innerHTML = ` - - - - + + + + `; } @@ -1434,7 +1434,7 @@ class Tt extends HTMLElement { this._value = [...new Set(t.filter((n) => typeof n == "string" && this._getItemById(n)))]; else if (typeof t == "string" && t.trim() !== "") { const n = t.trim(); - this._getItemById(n) && !this._value.includes(n) ? this._value = [n] : this._getItemById(n) || (this._value = this._value.filter((l) => l !== n)); + this._getItemById(n) && !this._value.includes(n) ? this._value = [n] : this._getItemById(n) || (this._value = this._value.filter((a) => a !== n)); } else this._value = []; const s = JSON.stringify(this._value.sort()); !this._initialCaptured && this._allowInitialCapture && this._value.length > 0 && (this._initialValue = [...this._value], this._initialOrder = [...this._value], this._initialCaptured = !0), this._value.forEach((n) => { @@ -1448,7 +1448,7 @@ class Tt extends HTMLElement { this.setAttribute("name", t), this.hiddenSelect && (this.hiddenSelect.name = t); } connectedCallback() { - if (this._render(), this.inputControlsContainer = this.querySelector(`.${lt}`), this.inputWrapper = this.querySelector(`.${rt}`), this.inputElement = this.querySelector(`.${dt}`), this.createNewButton = this.querySelector(`.${ht}`), this.toggleButton = this.querySelector(`.${ct}`), this.optionsListElement = this.querySelector(`.${ut}`), this.selectedItemsContainer = this.querySelector(`.${nt}`), this.hiddenSelect = this.querySelector(`.${B}`), this.placeholder = this.getAttribute("placeholder") || "Search items...", this.showCreateButton = this.getAttribute("show-create-button") !== "false", this._toggleLabel = this.getAttribute("data-toggle-label") || "", this._toggleInput = this._toggleLabel !== "", this._inputCollapsed = this._toggleInput, this._remoteEndpoint = this.getAttribute("data-endpoint") || null, this._remoteResultKey = this.getAttribute("data-result-key") || "items", this._remoteMinChars = this._parsePositiveInt(this.getAttribute("data-minchars"), $), this._remoteLimit = this._parsePositiveInt(this.getAttribute("data-limit"), D), this.name && this.hiddenSelect && (this.hiddenSelect.name = this.name), this.inputElement.addEventListener("input", this._handleInput), this.inputElement.addEventListener("keydown", this._handleKeyDown), this.inputElement.addEventListener("focus", this._handleFocus), this.inputElement.addEventListener("blur", this._handleBlur), this.optionsListElement.addEventListener("mousedown", this._handleOptionMouseDown), this.optionsListElement.addEventListener("click", this._handleOptionClick), this.createNewButton.addEventListener("click", this._handleCreateNewButtonClick), this.selectedItemsContainer.addEventListener("click", this._handleSelectedItemsContainerClick), this.toggleButton && this.toggleButton.addEventListener("click", this._handleToggleClick), this._updateRootElementStateClasses(), this.hasAttribute("value")) { + if (this._render(), this.inputControlsContainer = this.querySelector(`.${ht}`), this.inputWrapper = this.querySelector(`.${ct}`), this.inputElement = this.querySelector(`.${mt}`), this.createNewButton = this.querySelector(`.${_t}`), this.toggleButton = this.querySelector(`.${pt}`), this.optionsListElement = this.querySelector(`.${ft}`), this.selectedItemsContainer = this.querySelector(`.${ot}`), this.hiddenSelect = this.querySelector(`.${D}`), this.placeholder = this.getAttribute("placeholder") || "Search items...", this.showCreateButton = this.getAttribute("show-create-button") !== "false", this._toggleLabel = this.getAttribute("data-toggle-label") || "", this._toggleInput = this._toggleLabel !== "", this._inputCollapsed = this._toggleInput, this._remoteEndpoint = this.getAttribute("data-endpoint") || null, this._remoteResultKey = this.getAttribute("data-result-key") || "items", this._remoteMinChars = this._parsePositiveInt(this.getAttribute("data-minchars"), F), this._remoteLimit = this._parsePositiveInt(this.getAttribute("data-limit"), V), this.name && this.hiddenSelect && (this.hiddenSelect.name = this.name), this.inputElement.addEventListener("input", this._handleInput), this.inputElement.addEventListener("keydown", this._handleKeyDown), this.inputElement.addEventListener("focus", this._handleFocus), this.inputElement.addEventListener("blur", this._handleBlur), this.optionsListElement.addEventListener("mousedown", this._handleOptionMouseDown), this.optionsListElement.addEventListener("click", this._handleOptionClick), this.createNewButton.addEventListener("click", this._handleCreateNewButtonClick), this.selectedItemsContainer.addEventListener("click", this._handleSelectedItemsContainerClick), this.toggleButton && this.toggleButton.addEventListener("click", this._handleToggleClick), this._updateRootElementStateClasses(), this.hasAttribute("value")) { const t = this.getAttribute("value"); try { this.value = JSON.parse(t); @@ -1484,9 +1484,9 @@ class Tt extends HTMLElement { try { this.value = JSON.parse(s); } catch { - this.value = s.split(",").map((l) => l.trim()).filter(Boolean); + this.value = s.split(",").map((a) => a.trim()).filter(Boolean); } - else t === "placeholder" ? this.placeholder = s : t === "show-create-button" ? this.showCreateButton = s : t === "data-endpoint" ? this._remoteEndpoint = s || null : t === "data-result-key" ? this._remoteResultKey = s || "items" : t === "data-minchars" ? this._remoteMinChars = this._parsePositiveInt(s, $) : t === "data-limit" ? this._remoteLimit = this._parsePositiveInt(s, D) : t === "data-toggle-label" && (this._toggleLabel = s || "", this._toggleInput = this._toggleLabel !== ""); + else t === "placeholder" ? this.placeholder = s : t === "show-create-button" ? this.showCreateButton = s : t === "data-endpoint" ? this._remoteEndpoint = s || null : t === "data-result-key" ? this._remoteResultKey = s || "items" : t === "data-minchars" ? this._remoteMinChars = this._parsePositiveInt(s, F) : t === "data-limit" ? this._remoteLimit = this._parsePositiveInt(s, V) : t === "data-toggle-label" && (this._toggleLabel = s || "", this._toggleInput = this._toggleLabel !== ""); } formAssociatedCallback(t) { } @@ -1499,6 +1499,9 @@ class Tt extends HTMLElement { formStateRestoreCallback(t, e) { this.value = Array.isArray(t) ? t : [], this._updateRootElementStateClasses(); } + captureInitialSelection() { + this._initialValue = [...this._value], this._initialOrder = [...this._value], this._removedIds.clear(), this._initialCaptured = !0, this._renderSelectedItems(); + } _synchronizeHiddenSelect() { this.hiddenSelect && (this.hiddenSelect.innerHTML = "", this._value.forEach((t) => { const e = document.createElement("option"); @@ -1511,10 +1514,10 @@ class Tt extends HTMLElement { this.internals_.setFormValue(null), this._synchronizeHiddenSelect(); } disabledCallback(t) { - this.inputElement && (this.inputElement.disabled = t), this.createNewButton && (this.createNewButton.disabled = t), this.toggleAttribute("disabled", t), this.querySelectorAll(`.${at}`).forEach((e) => e.disabled = t), this.hiddenSelect && (this.hiddenSelect.disabled = t), t && this._hideOptionsList(); + this.inputElement && (this.inputElement.disabled = t), this.createNewButton && (this.createNewButton.disabled = t), this.toggleAttribute("disabled", t), this.querySelectorAll(`.${dt}`).forEach((e) => e.disabled = t), this.hiddenSelect && (this.hiddenSelect.disabled = t), t && this._hideOptionsList(); } _updateRootElementStateClasses() { - this.classList.toggle(me, this._value.length === 0), this.classList.toggle(_e, this._value.length > 0), this.classList.toggle(pe, this._isOptionsListVisible); + this.classList.toggle(ge, this._value.length === 0), this.classList.toggle(be, this._value.length > 0), this.classList.toggle(Ee, this._isOptionsListVisible); } _render() { const t = this.id || `mss-${crypto.randomUUID().slice(0, 8)}`; @@ -1522,44 +1525,44 @@ class Tt extends HTMLElement { const e = this.getAttribute("data-toggle-label") || "", s = e !== "", n = s ? "hidden" : ""; this.innerHTML = ` -
    -
    -
    - ${s ? `` : ""} -
    -
    +
    +
    +
    + ${s ? `` : ""} +
    +
    - +
    - - + +
    `; } _createSelectedItemElement(t) { const e = this._getItemById(t); if (!e) return null; - const n = this.selectedItemTemplate.content.cloneNode(!0).firstElementChild, l = n.querySelector('[data-ref="textEl"]'), r = n.querySelector('[data-ref="detailEl"]'), o = n.querySelector('[data-ref="deleteBtn"]'); - l.textContent = this._normalizeText(e.name); + const n = this.selectedItemTemplate.content.cloneNode(!0).firstElementChild, a = n.querySelector('[data-ref="textEl"]'), r = n.querySelector('[data-ref="detailEl"]'), o = n.querySelector('[data-ref="deleteBtn"]'); + a.textContent = this._normalizeText(e.name); const d = this._normalizeText(e.additional_data); d ? (r.textContent = `(${d})`, r.classList.remove("hidden")) : (r.textContent = "", r.classList.add("hidden")); - const h = this._removedIds.has(t); + const c = this._removedIds.has(t); if (!this._initialValue.includes(t)) { - const m = document.createElement("span"); - m.className = "ml-1 text-xs text-gray-600", m.textContent = "(Neu)", l.appendChild(m); + const u = document.createElement("span"); + u.className = "ml-1 text-xs text-gray-600", u.textContent = "(Neu)", a.appendChild(u); } - return h && (l.classList.add("line-through", "decoration-2", "decoration-red-600", "text-gray-500"), r.classList.add("line-through", "decoration-2", "decoration-red-600", "text-gray-500")), o.setAttribute("aria-label", h ? `Undo remove ${e.name}` : `Remove ${e.name}`), o.dataset.id = t, o.disabled = this.hasAttribute("disabled"), o.innerHTML = h ? '' : "×", o.addEventListener("click", (m) => { - m.stopPropagation(), this._handleDeleteSelectedItem(t); + return c && (a.classList.add("line-through", "decoration-2", "decoration-red-600", "text-gray-500"), r.classList.add("line-through", "decoration-2", "decoration-red-600", "text-gray-500")), o.setAttribute("aria-label", c ? `Undo remove ${e.name}` : `Remove ${e.name}`), o.dataset.id = t, o.disabled = this.hasAttribute("disabled"), o.innerHTML = c ? '' : "×", o.addEventListener("click", (u) => { + u.stopPropagation(), this._handleDeleteSelectedItem(t); }), n; } _renderSelectedItems() { @@ -1568,7 +1571,7 @@ class Tt extends HTMLElement { const t = this._initialOrder.filter((s) => this._removedIds.has(s) && !this._value.includes(s)), e = [...this._value, ...t]; if (e.length === 0) { const s = this.getAttribute("data-empty-text") || "Keine Auswahl..."; - this.selectedItemsContainer.innerHTML = `${s}`; + this.selectedItemsContainer.innerHTML = `${s}`; } else e.forEach((s) => { const n = this._createSelectedItemElement(s); @@ -1577,12 +1580,12 @@ class Tt extends HTMLElement { this._updateRootElementStateClasses(); } _createOptionElement(t, e) { - const n = this.optionTemplate.content.cloneNode(!0).firstElementChild, l = n.querySelector('[data-ref="nameEl"]'), r = n.querySelector('[data-ref="detailEl"]'); - l.textContent = this._normalizeText(t.name); + const n = this.optionTemplate.content.cloneNode(!0).firstElementChild, a = n.querySelector('[data-ref="nameEl"]'), r = n.querySelector('[data-ref="detailEl"]'); + a.textContent = this._normalizeText(t.name); const o = this._normalizeText(t.additional_data); r.textContent = o ? `(${o})` : "", n.dataset.id = t.id, n.setAttribute("aria-selected", String(e === this._highlightedIndex)); const d = `option-${this.id || "mss"}-${t.id}`; - return n.id = d, e === this._highlightedIndex && (n.classList.add(mt), this.inputElement && this.inputElement.setAttribute("aria-activedescendant", d)), n; + return n.id = d, e === this._highlightedIndex && (n.classList.add(gt), this.inputElement && this.inputElement.setAttribute("aria-activedescendant", d)), n; } _renderOptionsList() { if (!(!this.optionsListElement || !this.inputElement)) { @@ -1593,7 +1596,7 @@ class Tt extends HTMLElement { const n = this._createOptionElement(e, s); this.optionsListElement.appendChild(n); }); - const t = this.optionsListElement.querySelector(`.${mt}`); + const t = this.optionsListElement.querySelector(`.${gt}`); t && (t.scrollIntoView({ block: "nearest" }), this.inputElement.setAttribute("aria-activedescendant", t.id)); } this._updateRootElementStateClasses(); @@ -1660,10 +1663,10 @@ class Tt extends HTMLElement { this._isOptionsListVisible = !1, this._highlightedIndex = -1, this.optionsListElement && this._renderOptionsList(); } _handleFocus() { - this.inputElement.disabled || (this.inputWrapper && this.inputWrapper.classList.add(ot), this.inputElement.value.length > 0 && this._handleInput({ target: this.inputElement }), this._updateRootElementStateClasses()); + this.inputElement.disabled || (this.inputWrapper && this.inputWrapper.classList.add(ut), this.inputElement.value.length > 0 && this._handleInput({ target: this.inputElement }), this._updateRootElementStateClasses()); } _handleBlur() { - this.inputWrapper && this.inputWrapper.classList.remove(ot), this._blurTimeout = setTimeout(() => { + this.inputWrapper && this.inputWrapper.classList.remove(ut), this._blurTimeout = setTimeout(() => { this.contains(document.activeElement) || (this._hideOptionsList(), this._toggleInput && (!this.inputElement || this.inputElement.value.trim() === "") && this._hideInputControls()); }, 150); } @@ -1694,7 +1697,7 @@ class Tt extends HTMLElement { _showInputControls() { if (this.inputControlsContainer) { if (this.inputControlsContainer.classList.remove("hidden"), this.toggleButton && this.toggleButton.classList.add("hidden"), this._value.length === 0 && this.selectedItemsContainer) { - const t = this.selectedItemsContainer.querySelector(`.${N}`); + const t = this.selectedItemsContainer.querySelector(`.${H}`); t && t.classList.add("hidden"); } this.inputElement && !this.hasAttribute("disabled") && this.inputElement.focus(), this._inputCollapsed = !1; @@ -1703,7 +1706,7 @@ class Tt extends HTMLElement { _hideInputControls() { if (this.inputControlsContainer) { if (this.inputControlsContainer.classList.add("hidden"), this.toggleButton && this.toggleButton.classList.remove("hidden"), this._value.length === 0 && this.selectedItemsContainer) { - const t = this.selectedItemsContainer.querySelector(`.${N}`); + const t = this.selectedItemsContainer.querySelector(`.${H}`); t && t.classList.remove("hidden"); } this._hideOptionsList(), this._inputCollapsed = !0; @@ -1721,14 +1724,14 @@ class Tt extends HTMLElement { } this._remoteFetchTimeout = setTimeout(() => { this._fetchRemoteOptions(t); - }, ue); + }, fe); } _cancelRemoteFetch() { this._remoteFetchController && (this._remoteFetchController.abort(), this._remoteFetchController = null); } async _fetchRemoteOptions(t) { if (!this._remoteEndpoint) return; - this._cancelRemoteFetch(), this.classList.add(_t); + this._cancelRemoteFetch(), this.classList.add(bt); const e = new AbortController(); this._remoteFetchController = e; try { @@ -1741,17 +1744,17 @@ class Tt extends HTMLElement { }); if (!n.ok) throw new Error(`Remote fetch failed with status ${n.status}`); - const l = await n.json(); + const a = await n.json(); if (e.signal.aborted) return; - const r = this._extractRemoteOptions(l); + const r = this._extractRemoteOptions(a); this._applyRemoteResults(r); } catch (s) { if (e.signal.aborted) return; console.error("MultiSelectSimple remote fetch error:", s), this._filteredOptions = [], this._isOptionsListVisible = !1, this._renderOptionsList(); } finally { - this._remoteFetchController === e && (this._remoteFetchController = null), this.classList.remove(_t); + this._remoteFetchController === e && (this._remoteFetchController = null), this.classList.remove(bt); } } _extractRemoteOptions(t) { @@ -1759,7 +1762,7 @@ class Tt extends HTMLElement { let e = []; return Array.isArray(t) ? e = t : this._remoteResultKey && Array.isArray(t[this._remoteResultKey]) ? e = t[this._remoteResultKey] : Array.isArray(t.items) && (e = t.items), e.map((s) => { if (!s) return null; - const n = s.id ?? s.ID ?? s.value ?? "", l = s.name ?? s.title ?? s.label ?? "", r = s.detail ?? s.additional_data ?? s.annotation ?? "", o = this._normalizeText(l), d = this._normalizeText(r); + const n = s.id ?? s.ID ?? s.value ?? "", a = s.name ?? s.title ?? s.label ?? "", r = s.detail ?? s.additional_data ?? s.annotation ?? "", o = this._normalizeText(a), d = this._normalizeText(r); return !n || !o ? null : { id: String(n), name: o, @@ -1785,9 +1788,9 @@ class Tt extends HTMLElement { return (s === '"' && n === '"' || s === "'" && n === "'") && (e = e.slice(1, -1).trim(), !e) ? "" : e; } } -f(Tt, "formAssociated", !0); -const fe = "rbi-button", ge = "rbi-icon"; -class be extends HTMLElement { +E(kt, "formAssociated", !0); +const Se = "rbi-button", ve = "rbi-icon"; +class Le extends HTMLElement { constructor() { super(), this.initialStates = /* @__PURE__ */ new Map(), this._controlledElements = [], this.button = null, this.lastOverallModifiedState = null, this.handleInputChange = this.handleInputChange.bind(this), this.handleReset = this.handleReset.bind(this); } @@ -1796,10 +1799,10 @@ class be extends HTMLElement { } connectedCallback() { const i = ` - `; @@ -1943,32 +1946,32 @@ class be extends HTMLElement { this.button.setAttribute("aria-label", i); } } -const c = "hidden", pt = "dm-stay", y = "dm-title", ft = "dm-menu-button", Ee = "dm-target", Se = "data-dm-target", gt = "dm-menu", bt = "dm-menu-item", Le = "dm-close-button"; -var C, Ct; -class ve extends HTMLElement { +const _ = "hidden", Et = "dm-stay", x = "dm-title", St = "dm-menu-button", ye = "dm-target", Ae = "data-dm-target", vt = "dm-menu", Lt = "dm-menu-item", Ie = "dm-close-button"; +var O, Ot; +class Te extends HTMLElement { constructor() { super(); - g(this, C); - v(this, C, Ct).call(this), this.boundHandleClickOutside = this.handleClickOutside.bind(this); + S(this, O); + T(this, O, Ot).call(this), this.boundHandleClickOutside = this.handleClickOutside.bind(this); } connectedCallback() { - if (this._target = document.getElementById(this.getAttribute(Ee)), this._target || (this._target = this), this._cildren = Array.from(this.children).filter((t) => t.nodeType === Node.ELEMENT_NODE && !t.classList.contains(ft)).map((t) => ({ + if (this._target = document.getElementById(this.getAttribute(ye)), this._target || (this._target = this), this._cildren = Array.from(this.children).filter((t) => t.nodeType === Node.ELEMENT_NODE && !t.classList.contains(St)).map((t) => ({ node: t, target: () => { - const e = t.getAttribute(Se); + const e = t.getAttribute(Ae); return e ? document.getElementById(e) || this._target : this._target; }, - stay: () => t.hasAttribute(pt) && t.getAttribute(pt) == "true", - hidden: () => t.classList.contains(c), + stay: () => t.hasAttribute(Et) && t.getAttribute(Et) == "true", + hidden: () => t.classList.contains(_), name: () => { const e = t.querySelector("label"); - return e ? e.innerHTML : t.hasAttribute(y) ? t.getAttribute(y) : ""; + return e ? e.innerHTML : t.hasAttribute(x) ? t.getAttribute(x) : ""; }, nameText: () => { const e = t.querySelector("label"); - return e ? e.textContent.trim() : t.hasAttribute(y) ? t.getAttribute(y) : ""; + return e ? e.textContent.trim() : t.hasAttribute(x) ? t.getAttribute(x) : ""; } - })), this._button = this.querySelector(`.${ft}`), !this._button) { + })), this._button = this.querySelector(`.${St}`), !this._button) { console.error("DivManagerMenu needs a button element."); return; } @@ -1977,7 +1980,7 @@ class ve extends HTMLElement { this.removeChild(t.node); this._button.addEventListener("click", this._toggleMenu.bind(this)), this._button.classList.add("relative"); for (const t of this._cildren) - t.node.querySelectorAll(`.${Le}`).forEach((s) => { + t.node.querySelectorAll(`.${Ie}`).forEach((s) => { s.addEventListener("click", (n) => { this.hideDiv(n, t.node); }); @@ -2006,13 +2009,13 @@ class ve extends HTMLElement { this.hideMenu(); return; } - this.renderMenu(), this._menu.classList.contains(c) ? (this._menu.classList.remove(c), document.addEventListener("click", this.boundHandleClickOutside)) : (this._menu.classList.add(c), document.removeEventListener("click", this.boundHandleClickOutside)); + this.renderMenu(), this._menu.classList.contains(_) ? (this._menu.classList.remove(_), document.addEventListener("click", this.boundHandleClickOutside)) : (this._menu.classList.add(_), document.removeEventListener("click", this.boundHandleClickOutside)); } handleClickOutside(t) { this._menu && !this._menu.contains(t.target) && !this._button.contains(t.target) && this.hideMenu(); } hideMenu() { - this._menu && (this._menu.classList.add(c), document.removeEventListener("click", this.boundHandleClickOutside)); + this._menu && (this._menu.classList.add(_), document.removeEventListener("click", this.boundHandleClickOutside)); } renderButton() { if (!this._button) @@ -2020,10 +2023,10 @@ class ve extends HTMLElement { this._originalButtonText || (this._originalButtonText = this._button.innerHTML); const t = this._cildren.filter((e) => e.hidden()); if (t.length === 0) { - this._button.classList.add(c), this._button.parentElement && this._button.parentElement.removeChild(this._button), this._menu = null, this.hideMenu(); + this._button.classList.add(_), this._button.parentElement && this._button.parentElement.removeChild(this._button), this._menu = null, this.hideMenu(); return; } - if (this._button.parentElement || this.appendChild(this._button), this._button.classList.remove(c), t.length === 1) { + if (this._button.parentElement || this.appendChild(this._button), this._button.classList.remove(_), t.length === 1) { const e = this._button.querySelector("i"), s = e ? e.outerHTML : ''; this._button.innerHTML = `${s} ${t[0].nameText()} hinzufügen`, this._menu = null, this.hideMenu(); @@ -2035,12 +2038,12 @@ ${t[0].nameText()} hinzufügen`, this._menu = null, this.hideMenu(); console.error("DivManagerMenu: Invalid node provided."); return; } - const s = this._cildren.find((l) => l.node === e); + const s = this._cildren.find((a) => a.node === e); if (!s) { console.error("DivManagerMenu: Child not found."); return; } - s.node.classList.add(c); + s.node.classList.add(_); const n = s.target(); n && n.contains(s.node) && n.removeChild(s.node), this.renderButton(), this.renderMenu(), this.updateTargetVisibility(); } @@ -2050,7 +2053,7 @@ ${t[0].nameText()} hinzufügen`, this._menu = null, this.hideMenu(); return; } const s = this._cildren[e]; - s.node.classList.remove(c), this.insertChildInOrder(s), this.renderMenu(), this.renderButton(), this.updateTargetVisibility(); + s.node.classList.remove(_), this.insertChildInOrder(s), this.renderMenu(), this.renderButton(), this.updateTargetVisibility(); } renderMenu() { const t = this._cildren.filter((s) => s.hidden()); @@ -2058,10 +2061,10 @@ ${t[0].nameText()} hinzufügen`, this._menu = null, this.hideMenu(); this.hideMenu(); return; } - (!this._menu || !this._button.contains(this._menu)) && (this._button.insertAdjacentHTML("beforeend", ``), this._menu = this._button.querySelector(`.${gt}`)), this._menu.innerHTML = `${t.map((s, n) => ` - `).join("")}`, this._menu.querySelectorAll(`.${bt}`).forEach((s) => { + `).join("")}`, this._menu.querySelectorAll(`.${Lt}`).forEach((s) => { s.addEventListener("click", (n) => { this.showDiv(n, parseInt(s.getAttribute("dm-itemno"))), this.hideMenu(), this.renderButton(); }); @@ -2073,7 +2076,7 @@ ${t[0].nameText()} hinzufügen`, this._menu = null, this.hideMenu(); }), this.updateTargetVisibility(); } insertChildInOrder(t) { - const e = t.target(), s = this._cildren.indexOf(t), n = this._cildren.slice(s + 1).filter((l) => l.target() === e).map((l) => l.node).find((l) => e && e.contains(l)); + const e = t.target(), s = this._cildren.indexOf(t), n = this._cildren.slice(s + 1).filter((a) => a.target() === e).map((a) => a.node).find((a) => e && e.contains(a)); e && (n ? e.insertBefore(t.node, n) : e.appendChild(t.node)); } updateTargetVisibility() { @@ -2081,22 +2084,22 @@ ${t[0].nameText()} hinzufügen`, this._menu = null, this.hideMenu(); this._cildren.map((e) => e.target()).filter((e) => e && e !== this) ).forEach((e) => { const s = Array.from(e.children).some( - (n) => !n.classList.contains(c) + (n) => !n.classList.contains(_) ); - e.classList.toggle(c, !s); + e.classList.toggle(_, !s); }); } } -C = new WeakSet(), Ct = function() { +O = new WeakSet(), Ot = function() { this._cildren = [], this._rendered = [], this._target = null, this._button = null, this._menu = null, this._originalButtonText = null; }; -const u = "items-row", Ae = "items-list", Ie = "items-template", ye = "items-add-button", Te = "items-cancel-button", Et = "items-remove-button", Ce = "items-edit-button", we = "items-close-button", xe = "items-summary", ke = "items-edit-panel", P = "items_removed[]", b = "data-items-removed"; -class Oe extends HTMLElement { +const p = "items-row", Ce = "items-list", we = "items-template", xe = "items-add-button", ke = "items-cancel-button", yt = "items-remove-button", Oe = "items-edit-button", Re = "items-close-button", Me = "items-summary", Be = "items-edit-panel", U = "items_removed[]", v = "data-items-removed"; +class $e extends HTMLElement { constructor() { super(), this._list = null, this._template = null, this._addButton = null, this._idPrefix = `items-editor-${crypto.randomUUID().slice(0, 8)}`, this._handleAdd = this._onAddClick.bind(this); } connectedCallback() { - if (this._list = this.querySelector(`.${Ae}`), this._template = this.querySelector(`template.${Ie}`), this._addButton = this.querySelector(`.${ye}`), !this._list || !this._template || !this._addButton) { + if (this._list = this.querySelector(`.${Ce}`), this._template = this.querySelector(`template.${we}`), this._addButton = this.querySelector(`.${xe}`), !this._list || !this._template || !this._addButton) { console.error("ItemsEditor: Missing list, template, or add button."); return; } @@ -2109,7 +2112,7 @@ class Oe extends HTMLElement { i.preventDefault(), this.addItem(); } addItem() { - const i = this._template.content.cloneNode(!0), t = i.querySelector(`.${u}`); + const i = this._template.content.cloneNode(!0), t = i.querySelector(`.${p}`); if (!t) { console.error("ItemsEditor: Template is missing a row element."); return; @@ -2117,54 +2120,54 @@ class Oe extends HTMLElement { this._list.appendChild(i), this._captureOriginalValues(t), this._wireCancelButtons(t), this._wireRemoveButtons(t), this._wireEditButtons(t), this._assignRowFieldIds(t, this._rowIndex(t)), this._wireSummarySync(t), this._syncSummary(t), this._setRowMode(t, "edit"); } removeItem(i) { - const t = i.closest(`.${u}`); + const t = i.closest(`.${p}`); if (!t) return; - const e = t.getAttribute(b) === "true"; + const e = t.getAttribute(v) === "true"; this._setRowRemoved(t, !e); } _wireRemoveButtons(i = this) { - i.querySelectorAll(`.${Et}`).forEach((t) => { + i.querySelectorAll(`.${yt}`).forEach((t) => { t.dataset.itemsBound !== "true" && (t.dataset.itemsBound = "true", t.addEventListener("click", (e) => { e.preventDefault(), this.removeItem(t); }), t.addEventListener("mouseenter", () => { - const e = t.closest(`.${u}`); - if (!e || e.getAttribute(b) !== "true") + const e = t.closest(`.${p}`); + if (!e || e.getAttribute(v) !== "true") return; const s = t.querySelector("[data-delete-label]"); s && (s.textContent = s.getAttribute("data-delete-hover") || "Rückgängig", s.classList.add("text-orange-700")); const n = t.querySelector("i"); n && (n.classList.remove("hidden"), n.classList.add("ri-arrow-go-back-line"), n.classList.remove("ri-delete-bin-line")); }), t.addEventListener("mouseleave", () => { - const e = t.closest(`.${u}`), s = t.querySelector("[data-delete-label]"); + const e = t.closest(`.${p}`), s = t.querySelector("[data-delete-label]"); if (!s) return; - s.classList.remove("text-orange-700"), e && e.getAttribute(b) === "true" ? s.textContent = s.getAttribute("data-delete-active") || "Wird entfernt" : s.textContent = s.getAttribute("data-delete-default") || "Entfernen"; + s.classList.remove("text-orange-700"), e && e.getAttribute(v) === "true" ? s.textContent = s.getAttribute("data-delete-active") || "Wird entfernt" : s.textContent = s.getAttribute("data-delete-default") || "Entfernen"; const n = t.querySelector("i"); - n && (e && e.getAttribute(b) === "true" ? (n.classList.add("hidden"), n.classList.remove("ri-delete-bin-line", "ri-arrow-go-back-line")) : (n.classList.remove("hidden"), n.classList.add("ri-delete-bin-line"), n.classList.remove("ri-arrow-go-back-line"))); + n && (e && e.getAttribute(v) === "true" ? (n.classList.add("hidden"), n.classList.remove("ri-delete-bin-line", "ri-arrow-go-back-line")) : (n.classList.remove("hidden"), n.classList.add("ri-delete-bin-line"), n.classList.remove("ri-arrow-go-back-line"))); })); }); } _wireCancelButtons(i = this) { - i.querySelectorAll(`.${Te}`).forEach((t) => { + i.querySelectorAll(`.${ke}`).forEach((t) => { t.dataset.itemsBound !== "true" && (t.dataset.itemsBound = "true", t.addEventListener("click", (e) => { e.preventDefault(); - const s = t.closest(`.${u}`); + const s = t.closest(`.${p}`); s && this._cancelEdit(s); })); }); } _wireEditButtons(i = this) { - i.querySelectorAll(`.${Ce}`).forEach((t) => { + i.querySelectorAll(`.${Oe}`).forEach((t) => { t.dataset.itemsBound !== "true" && (t.dataset.itemsBound = "true", t.addEventListener("click", (e) => { e.preventDefault(); - const s = t.closest(`.${u}`); + const s = t.closest(`.${p}`); s && this._setRowMode(s, "edit"); })); - }), i.querySelectorAll(`.${we}`).forEach((t) => { + }), i.querySelectorAll(`.${Re}`).forEach((t) => { t.dataset.itemsBound !== "true" && (t.dataset.itemsBound = "true", t.addEventListener("click", (e) => { e.preventDefault(); - const s = t.closest(`.${u}`); + const s = t.closest(`.${p}`); s && this._setRowMode(s, "summary"); })); }); @@ -2178,12 +2181,12 @@ class Oe extends HTMLElement { this._resetToOriginal(i), this._setRowMode(i, "summary"); } _setRowRemoved(i, t) { - i.setAttribute(b, t ? "true" : "false"), i.classList.toggle("bg-red-50", t), i.querySelectorAll("[data-items-strike]").forEach((n) => { + i.setAttribute(v, t ? "true" : "false"), i.classList.toggle("bg-red-50", t), i.querySelectorAll("[data-items-strike]").forEach((n) => { n.classList.toggle("line-through", t), n.classList.toggle("decoration-2", t), n.classList.toggle("decoration-red-600", t), n.classList.toggle("text-gray-500", t); }), i.querySelectorAll("[data-delete-label]").forEach((n) => { - const l = t ? n.getAttribute("data-delete-active") || "Wird entfernt" : n.getAttribute("data-delete-default") || "Entfernen"; - n.textContent = l; - }), i.querySelectorAll(`.${Et} i`).forEach((n) => { + const a = t ? n.getAttribute("data-delete-active") || "Wird entfernt" : n.getAttribute("data-delete-default") || "Entfernen"; + n.textContent = a; + }), i.querySelectorAll(`.${yt} i`).forEach((n) => { t ? (n.classList.add("hidden"), n.classList.remove("ri-delete-bin-line", "ri-arrow-go-back-line")) : (n.classList.remove("hidden"), n.classList.add("ri-delete-bin-line"), n.classList.remove("ri-arrow-go-back-line")); }); const e = i.querySelector('input[name="items_id[]"]'), s = e ? e.value.trim() : ""; @@ -2192,11 +2195,11 @@ class Oe extends HTMLElement { }); } _setRowMode(i, t) { - const e = i.querySelector(`.${xe}`), s = i.querySelector(`.${ke}`); + const e = i.querySelector(`.${Me}`), s = i.querySelector(`.${Be}`); !e || !s || (t === "edit" ? (e.classList.add("hidden"), s.classList.remove("hidden")) : (e.classList.remove("hidden"), s.classList.add("hidden"), this._syncSummary(i))); } _captureAllOriginals() { - this.querySelectorAll(`.${u}`).forEach((i) => { + this.querySelectorAll(`.${p}`).forEach((i) => { this._captureOriginalValues(i); }); } @@ -2211,12 +2214,12 @@ class Oe extends HTMLElement { }), this._syncSummary(i); } _refreshRowIds() { - Array.from(this.querySelectorAll(`.${u}`)).forEach((t, e) => { + Array.from(this.querySelectorAll(`.${p}`)).forEach((t, e) => { this._assignRowFieldIds(t, e); }); } _rowIndex(i) { - return Array.from(this.querySelectorAll(`.${u}`)).indexOf(i); + return Array.from(this.querySelectorAll(`.${p}`)).indexOf(i); } _assignRowFieldIds(i, t) { t < 0 || i.querySelectorAll("[data-field-label]").forEach((e) => { @@ -2226,12 +2229,12 @@ class Oe extends HTMLElement { const n = i.querySelector(`[data-field="${s}"]`); if (!n) return; - const l = `${this._idPrefix}-${t}-${s}`; - n.id = l, e.setAttribute("for", l); + const a = `${this._idPrefix}-${t}-${s}`; + n.id = a, e.setAttribute("for", a); }); } _syncAllSummaries() { - this.querySelectorAll(`.${u}`).forEach((i) => { + this.querySelectorAll(`.${p}`).forEach((i) => { this._wireSummarySync(i), this._syncSummary(i), this._syncNewBadge(i); }); } @@ -2248,8 +2251,8 @@ class Oe extends HTMLElement { const s = i.querySelector(`[data-field="${e}"]`); if (!s) return; - const n = this._readFieldValue(s), l = t.getAttribute("data-summary-hide-empty") === "true" ? t.closest("[data-summary-container]") : null; - n ? (this._setSummaryContent(t, n), t.classList.remove("text-gray-400"), l && l.classList.remove("hidden")) : (this._setSummaryContent(t, "—"), t.classList.add("text-gray-400"), l && l.classList.add("hidden")); + const n = this._readFieldValue(s), a = t.getAttribute("data-summary-hide-empty") === "true" ? t.closest("[data-summary-container]") : null; + n ? (this._setSummaryContent(t, n), t.classList.remove("text-gray-400"), a && a.classList.remove("hidden")) : (this._setSummaryContent(t, "—"), t.classList.add("text-gray-400"), a && a.classList.add("hidden")); }), this._syncNewBadge(i); } _syncNewBadge(i) { @@ -2272,35 +2275,35 @@ class Oe extends HTMLElement { return i instanceof HTMLInputElement || i instanceof HTMLTextAreaElement ? i.value.trim() : ""; } _ensureRemovalInput(i) { - if (Array.from(this.querySelectorAll(`input[name="${P}"]`)).some( + if (Array.from(this.querySelectorAll(`input[name="${U}"]`)).some( (s) => s.value === i )) return; const e = document.createElement("input"); - e.type = "hidden", e.name = P, e.value = i, this.appendChild(e); + e.type = "hidden", e.name = U, e.value = i, this.appendChild(e); } _removeRemovalInput(i) { - const t = Array.from(this.querySelectorAll(`input[name="${P}"]`)); + const t = Array.from(this.querySelectorAll(`input[name="${U}"]`)); for (const e of t) e.value === i && e.remove(); } } -const Me = "ssr-wrapper", St = "ssr-input", Lt = "ssr-list", Re = "ssr-option", Be = "ssr-option-name", Ne = "ssr-option-detail", $e = "ssr-option-bio", vt = "ssr-hidden-input", At = "ssr-clear-button", q = 1, H = 10, De = 250; -class Pe extends HTMLElement { +const Ne = "ssr-wrapper", At = "ssr-input", It = "ssr-list", Pe = "ssr-option", qe = "ssr-option-name", De = "ssr-option-detail", He = "ssr-option-bio", Tt = "ssr-hidden-input", Ct = "ssr-clear-button", K = 1, z = 10, Fe = 250; +class Ve extends HTMLElement { constructor() { - super(), this._endpoint = "", this._resultKey = "items", this._minChars = q, this._limit = H, this._placeholder = "Search...", this._options = [], this._selected = null, this._highlightedIndex = -1, this._fetchTimeout = null, this._fetchController = null, this._listVisible = !1, this._boundHandleInput = this._handleInput.bind(this), this._boundHandleFocus = this._handleFocus.bind(this), this._boundHandleKeyDown = this._handleKeyDown.bind(this), this._boundHandleClear = this._handleClear.bind(this), this._boundHandleClickOutside = this._handleClickOutside.bind(this); + super(), this._endpoint = "", this._resultKey = "items", this._minChars = K, this._limit = z, this._placeholder = "Search...", this._options = [], this._selected = null, this._highlightedIndex = -1, this._fetchTimeout = null, this._fetchController = null, this._listVisible = !1, this._boundHandleInput = this._handleInput.bind(this), this._boundHandleFocus = this._handleFocus.bind(this), this._boundHandleKeyDown = this._handleKeyDown.bind(this), this._boundHandleClear = this._handleClear.bind(this), this._boundHandleClickOutside = this._handleClickOutside.bind(this); } static get observedAttributes() { return ["data-endpoint", "data-result-key", "data-minchars", "data-limit", "placeholder", "name"]; } connectedCallback() { - this._render(), this._input = this.querySelector(`.${St}`), this._list = this.querySelector(`.${Lt}`), this._hiddenInput = this.querySelector(`.${vt}`), this._clearButton = this.querySelector(`.${At}`), this._endpoint = this.getAttribute("data-endpoint") || "", this._resultKey = this.getAttribute("data-result-key") || "items", this._minChars = this._parsePositiveInt(this.getAttribute("data-minchars"), q), this._limit = this._parsePositiveInt(this.getAttribute("data-limit"), H), this._placeholder = this.getAttribute("placeholder") || "Search...", this._input && (this._input.placeholder = this._placeholder, this._input.addEventListener("input", this._boundHandleInput), this._input.addEventListener("focus", this._boundHandleFocus), this._input.addEventListener("keydown", this._boundHandleKeyDown)), this._clearButton && this._clearButton.addEventListener("click", this._boundHandleClear), document.addEventListener("click", this._boundHandleClickOutside); + this._render(), this._input = this.querySelector(`.${At}`), this._list = this.querySelector(`.${It}`), this._hiddenInput = this.querySelector(`.${Tt}`), this._clearButton = this.querySelector(`.${Ct}`), this._endpoint = this.getAttribute("data-endpoint") || "", this._resultKey = this.getAttribute("data-result-key") || "items", this._minChars = this._parsePositiveInt(this.getAttribute("data-minchars"), K), this._limit = this._parsePositiveInt(this.getAttribute("data-limit"), z), this._placeholder = this.getAttribute("placeholder") || "Search...", this._input && (this._input.placeholder = this._placeholder, this._input.addEventListener("input", this._boundHandleInput), this._input.addEventListener("focus", this._boundHandleFocus), this._input.addEventListener("keydown", this._boundHandleKeyDown)), this._clearButton && this._clearButton.addEventListener("click", this._boundHandleClear), document.addEventListener("click", this._boundHandleClickOutside); } disconnectedCallback() { document.removeEventListener("click", this._boundHandleClickOutside), this._input && (this._input.removeEventListener("input", this._boundHandleInput), this._input.removeEventListener("focus", this._boundHandleFocus), this._input.removeEventListener("keydown", this._boundHandleKeyDown)), this._clearButton && this._clearButton.removeEventListener("click", this._boundHandleClear); } attributeChangedCallback(i, t, e) { - t !== e && (i === "data-endpoint" && (this._endpoint = e || ""), i === "data-result-key" && (this._resultKey = e || "items"), i === "data-minchars" && (this._minChars = this._parsePositiveInt(e, q)), i === "data-limit" && (this._limit = this._parsePositiveInt(e, H)), i === "placeholder" && (this._placeholder = e || "Search...", this._input && (this._input.placeholder = this._placeholder)), i === "name" && this._hiddenInput && (this._hiddenInput.name = e || "")); + t !== e && (i === "data-endpoint" && (this._endpoint = e || ""), i === "data-result-key" && (this._resultKey = e || "items"), i === "data-minchars" && (this._minChars = this._parsePositiveInt(e, K)), i === "data-limit" && (this._limit = this._parsePositiveInt(e, z)), i === "placeholder" && (this._placeholder = e || "Search...", this._input && (this._input.placeholder = this._placeholder)), i === "name" && this._hiddenInput && (this._hiddenInput.name = e || "")); } _handleInput(i) { const t = i.target.value.trim(); @@ -2351,7 +2354,7 @@ class Pe extends HTMLElement { _debouncedFetch(i) { this._fetchTimeout && clearTimeout(this._fetchTimeout), this._fetchTimeout = setTimeout(() => { this._fetchOptions(i); - }, De); + }, Fe); } async _fetchOptions(i) { if (!this._endpoint) @@ -2364,7 +2367,7 @@ class Pe extends HTMLElement { if (!e.ok) return; const s = await e.json(), n = Array.isArray(s == null ? void 0 : s[this._resultKey]) ? s[this._resultKey] : []; - this._options = n.filter((l) => l && l.id && l.name), this._highlightedIndex = this._options.length > 0 ? 0 : -1, this._renderOptions(), this._options.length > 0 ? this._showList() : this._hideList(); + this._options = n.filter((a) => a && a.id && a.name), this._highlightedIndex = this._options.length > 0 ? 0 : -1, this._renderOptions(), this._options.length > 0 ? this._showList() : this._hideList(); } catch (e) { if ((e == null ? void 0 : e.name) === "AbortError") return; @@ -2374,19 +2377,19 @@ class Pe extends HTMLElement { this._list && (this._list.innerHTML = "", this._options.forEach((i) => { const t = document.createElement("button"); t.type = "button", t.setAttribute("data-index", String(this._options.indexOf(i))), t.className = [ - Re, + Pe, "w-full text-left px-3 py-2 hover:bg-slate-100 transition-colors" ].join(" "); const s = this._options.indexOf(i) === this._highlightedIndex; t.classList.toggle("bg-slate-100", s), t.classList.toggle("text-gray-900", s), t.setAttribute("aria-selected", s ? "true" : "false"); const n = document.createElement("div"); - if (n.className = [Be, "text-sm font-semibold text-gray-800"].join(" "), n.textContent = i.name, t.appendChild(n), i.detail) { - const l = document.createElement("div"); - l.className = [Ne, "text-xs text-gray-600"].join(" "), l.textContent = i.detail, t.appendChild(l); + if (n.className = [qe, "text-sm font-semibold text-gray-800"].join(" "), n.textContent = i.name, t.appendChild(n), i.detail) { + const a = document.createElement("div"); + a.className = [De, "text-xs text-gray-600"].join(" "), a.textContent = i.detail, t.appendChild(a); } if (i.bio) { - const l = document.createElement("div"); - l.className = [$e, "text-xs text-gray-500"].join(" "), l.textContent = i.bio, t.appendChild(l); + const a = document.createElement("div"); + a.className = [He, "text-xs text-gray-500"].join(" "), a.textContent = i.bio, t.appendChild(a); } t.addEventListener("click", () => { this._selectOption(i); @@ -2435,33 +2438,37 @@ class Pe extends HTMLElement { _render() { const i = this.getAttribute("name") || ""; this.innerHTML = ` -
    +
    -
    - - + +
    `; } } -class qe extends HTMLElement { +const Ue = "Bevorzugter Reihentitel"; +class Ke extends HTMLElement { constructor() { - super(), this._pendingAgent = null; + super(), this._pendingAgent = null, this._form = null, this._saveButton = null, this._statusEl = null, this._saveEndpoint = "", this._isSaving = !1, this._handleSaveClick = this._handleSaveClick.bind(this); } connectedCallback() { - this._initForm(), this._initPlaces(); + this._initForm(), this._initPlaces(), this._initSaveHandling(); + } + disconnectedCallback() { + this._teardownSaveHandling(); } _initForm() { const i = this.querySelector("#changealmanachform"); @@ -2480,15 +2487,237 @@ class qe extends HTMLElement { } } _initPlaces() { + var e; const i = this.querySelector("#places"); if (!i) return; - const t = this._parseJSONAttr(i, "data-initial-options") || [], e = this._parseJSONAttr(i, "data-initial-values") || []; - t.length > 0 && typeof i.setOptions == "function" && i.setOptions(t), e.length > 0 && (i.value = e); + const t = () => { + const s = this._parseJSONAttr(i, "data-initial-options") || [], n = this._parseJSONAttr(i, "data-initial-values") || []; + s.length > 0 && typeof i.setOptions == "function" && i.setOptions(s), n.length > 0 && (i.value = n, typeof i.captureInitialSelection == "function" && i.captureInitialSelection()); + }; + if (typeof i.setOptions == "function") { + t(); + return; + } + typeof ((e = window.customElements) == null ? void 0 : e.whenDefined) == "function" && window.customElements.whenDefined("multi-select-simple").then(() => { + requestAnimationFrame(() => t()); + }); + } + _initSaveHandling() { + this._teardownSaveHandling(), this._form = this.querySelector("#changealmanachform"), this._saveButton = this.querySelector("[data-role='almanach-save']"), this._statusEl = this.querySelector("#almanach-save-feedback"), !(!this._form || !this._saveButton) && (this._saveEndpoint = this._form.getAttribute("data-save-endpoint") || this._deriveSaveEndpoint(), this._saveButton.addEventListener("click", this._handleSaveClick)); + } + _teardownSaveHandling() { + this._saveButton && this._saveButton.removeEventListener("click", this._handleSaveClick), this._saveButton = null, this._statusEl = null; + } + _deriveSaveEndpoint() { + var t; + return (t = window == null ? void 0 : window.location) != null && t.pathname ? `${window.location.pathname.endsWith("/") ? window.location.pathname.slice(0, -1) : window.location.pathname}/save` : "/almanach/save"; + } + async _handleSaveClick(i) { + if (i.preventDefault(), this._isSaving) + return; + this._clearStatus(); + let t; + try { + t = this._buildPayload(); + } catch (e) { + this._showStatus(e instanceof Error ? e.message : String(e), "error"); + return; + } + this._setSavingState(!0); + try { + const e = await fetch(this._saveEndpoint, { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json" + }, + body: JSON.stringify(t) + }); + let s = null; + try { + s = await e.clone().json(); + } catch { + s = null; + } + if (!e.ok) { + const n = (s == null ? void 0 : s.error) || `Speichern fehlgeschlagen (${e.status}).`; + throw new Error(n); + } + await this._reloadForm((s == null ? void 0 : s.message) || "Änderungen gespeichert."), this._clearStatus(); + } catch (e) { + this._showStatus(e instanceof Error ? e.message : "Speichern fehlgeschlagen.", "error"); + } finally { + this._setSavingState(!1); + } + } + _buildPayload() { + if (!this._form) + throw new Error("Formular konnte nicht gefunden werden."); + const i = new FormData(this._form), t = { + preferred_title: this._readValue(i, "preferred_title"), + title: this._readValue(i, "title"), + parallel_title: this._readValue(i, "paralleltitle"), + subtitle: this._readValue(i, "subtitle"), + variant_title: this._readValue(i, "varianttitle"), + incipit: this._readValue(i, "incipit"), + responsibility_statement: this._readValue(i, "responsibility_statement"), + publication_statement: this._readValue(i, "publication_statement"), + place_statement: this._readValue(i, "place_statement"), + edition: this._readValue(i, "edition"), + annotation: this._readValue(i, "annotation"), + edit_comment: this._readValue(i, "edit_comment"), + extent: this._readValue(i, "extent"), + dimensions: this._readValue(i, "dimensions"), + references: this._readValue(i, "refs"), + status: this._readValue(i, "type") + }; + if (!t.preferred_title) + throw new Error("Kurztitel ist erforderlich."); + const e = this._readValue(i, "year"); + if (e === "") + throw new Error("Jahr muss angegeben werden (0 ist erlaubt)."); + const s = Number.parseInt(e, 10); + if (Number.isNaN(s)) + throw new Error("Jahr ist ungültig."); + t.year = s; + const n = i.getAll("languages[]").map((g) => g.trim()).filter(Boolean), a = i.getAll("places[]").map((g) => g.trim()).filter(Boolean), { items: r, removedIds: o } = this._collectItems(i), { + relations: d, + deleted: c + } = this._collectRelations(i, { + prefix: "entries_series", + targetField: "series" + }), h = this._collectNewRelations("entries_series"); + if (![...d, ...h].some( + (g) => g.type === Ue + )) + throw new Error("Mindestens ein bevorzugter Reihentitel muss verknüpft sein."); + const { + relations: f, + deleted: m + } = this._collectRelations(i, { + prefix: "entries_agents", + targetField: "agent" + }), A = this._collectNewRelations("entries_agents"); + return { + csrf_token: this._readValue(i, "csrf_token"), + last_edited: this._readValue(i, "last_edited"), + entry: t, + languages: n, + places: a, + items: r, + deleted_item_ids: o, + series_relations: d, + new_series_relations: h, + deleted_series_relation_ids: c, + agent_relations: f, + new_agent_relations: A, + deleted_agent_relation_ids: m + }; + } + _collectItems(i) { + const t = i.getAll("items_id[]").map((h) => h.trim()), e = i.getAll("items_owner[]"), s = i.getAll("items_identifier[]"), n = i.getAll("items_location[]"), a = i.getAll("items_media[]"), r = i.getAll("items_annotation[]"), o = i.getAll("items_uri[]"), d = new Set( + i.getAll("items_removed[]").map((h) => h.trim()).filter(Boolean) + ), c = []; + for (let h = 0; h < t.length; h += 1) { + const u = t[h] || ""; + if (u && d.has(u)) + continue; + const f = (e[h] || "").trim(), m = (s[h] || "").trim(), A = (n[h] || "").trim(), g = (r[h] || "").trim(), G = (o[h] || "").trim(), M = (a[h] || "").trim(); + (u || f || m || A || g || G || M) && c.push({ + id: u, + owner: f, + identifier: m, + location: A, + annotation: g, + uri: G, + media: M ? [M] : [] + }); + } + return { + items: c, + removedIds: Array.from(d) + }; + } + _collectRelations(i, { prefix: t, targetField: e }) { + const s = [], n = []; + for (const [a, r] of i.entries()) { + if (!a.startsWith(`${t}_type[`)) + continue; + const o = a.slice(a.indexOf("[") + 1, -1), d = `${t}_${e}[${o}]`, c = `${t}_id[${o}]`, h = `${t}_delete[${o}]`, u = `${t}_uncertain[${o}]`, f = (i.get(d) || "").trim(); + if (!f) + continue; + const m = (i.get(c) || o).trim(); + if (i.has(h)) { + m && n.push(m); + continue; + } + s.push({ + id: m, + target_id: f, + type: (r || "").trim(), + uncertain: i.has(u) + }); + } + return { relations: s, deleted: n }; + } + _collectNewRelations(i) { + const t = this.querySelector(`relations-editor[data-prefix='${i}']`); + if (!t) + return []; + const e = t.querySelectorAll("[data-role='relation-add-row'] [data-rel-row]"), s = []; + return e.forEach((n) => { + const a = n.querySelector(`input[name='${i}_new_id']`), r = n.querySelector(`select[name='${i}_new_type']`), o = n.querySelector(`input[name='${i}_new_uncertain']`); + if (!a) + return; + const d = a.value.trim(); + d && s.push({ + target_id: d, + type: ((r == null ? void 0 : r.value) || "").trim(), + uncertain: !!(o != null && o.checked) + }); + }), s; + } + _readValue(i, t) { + const e = i.get(t); + return e ? String(e).trim() : ""; + } + _setSavingState(i) { + if (this._isSaving = i, !this._saveButton) + return; + this._saveButton.disabled = i; + const t = this._saveButton.querySelector("span"); + t && (t.textContent = i ? "Speichern..." : "Speichern"); + } + _clearStatus() { + this._statusEl && (this._statusEl.textContent = "", this._statusEl.classList.remove("text-red-700", "text-green-700")); + } + _showStatus(i, t) { + this._statusEl && (this._clearStatus(), this._statusEl.textContent = i, t === "success" ? this._statusEl.classList.add("text-green-700") : t === "error" && this._statusEl.classList.add("text-red-700")); + } + async _reloadForm(i) { + this._teardownSaveHandling(); + const t = new URL(window.location.href); + i ? t.searchParams.set("saved_message", i) : t.searchParams.delete("saved_message"); + const e = await fetch(t.toString(), { + headers: { + "X-Requested-With": "fetch" + } + }); + if (!e.ok) + throw new Error("Formular konnte nicht aktualisiert werden."); + const s = await e.text(), a = new DOMParser().parseFromString(s, "text/html"), r = a.querySelector("#changealmanachform"), o = this.querySelector("#changealmanachform"); + if (!r || !o) + throw new Error("Formular konnte nicht geladen werden."); + o.replaceWith(r), this._form = r; + const d = a.querySelector("#user-message"), c = this.querySelector("#user-message"); + d && c && c.replaceWith(d); + const h = a.querySelector("#almanach-header-data"), u = this.querySelector("#almanach-header-data"); + h && u && u.replaceWith(h), this._initForm(), this._initPlaces(), this._initSaveHandling(); } } -const He = "[data-role='relation-add-toggle']", Fe = "[data-role='relation-add-panel']", Ue = "[data-role='relation-add-close']", Ve = "[data-role='relation-add-apply']", Ke = "[data-role='relation-add-error']", ze = "[data-role='relation-add-row']", We = "[data-role='relation-add-select']", Ge = "[data-role='relation-type-select']", je = "[data-role='relation-uncertain']", Je = "template[data-role='relation-new-template']", Qe = "[data-role='relation-new-delete']", It = "[data-rel-row]", Xe = "[data-rel-strike]"; -class Ye extends HTMLElement { +const ze = "[data-role='relation-add-toggle']", We = "[data-role='relation-add-panel']", Ge = "[data-role='relation-add-close']", je = "[data-role='relation-add-apply']", Je = "[data-role='relation-add-error']", Qe = "[data-role='relation-add-row']", Xe = "[data-role='relation-add-select']", Ye = "[data-role='relation-type-select']", Ze = "[data-role='relation-uncertain']", ti = "template[data-role='relation-new-template']", ei = "[data-role='relation-new-delete']", wt = "[data-rel-row]", ii = "[data-rel-strike]"; +class si extends HTMLElement { constructor() { super(), this._pendingItem = null, this._pendingApply = !1; } @@ -2496,11 +2725,11 @@ class Ye extends HTMLElement { this._prefix = this.getAttribute("data-prefix") || "", this._linkBase = this.getAttribute("data-link-base") || "", this._newLabel = this.getAttribute("data-new-label") || "(Neu)", this._addToggleId = this.getAttribute("data-add-toggle-id") || "", this._setupAddPanel(), this._setupDeleteToggles(); } _setupAddPanel() { - if (this._addToggle = this.querySelector(He), this._addToggleId) { + if (this._addToggle = this.querySelector(ze), this._addToggleId) { const i = document.getElementById(this._addToggleId); i && (this._addToggle = i); } - this._addPanel = this.querySelector(Fe), this._addClose = this.querySelector(Ue), this._addApply = this.querySelector(Ve), this._addError = this.querySelector(Ke), this._addRow = this.querySelector(ze), this._addSelect = this.querySelector(We), this._typeSelect = this.querySelector(Ge), this._uncertain = this.querySelector(je), this._template = this.querySelector(Je), this._addInput = this._addSelect ? this._addSelect.querySelector(".ssr-input") : null, !(!this._addPanel || !this._addRow || !this._addSelect || !this._typeSelect || !this._uncertain || !this._template) && (this._addToggle && this._addToggle.addEventListener("click", () => { + this._addPanel = this.querySelector(We), this._addClose = this.querySelector(Ge), this._addApply = this.querySelector(je), this._addError = this.querySelector(Je), this._addRow = this.querySelector(Qe), this._addSelect = this.querySelector(Xe), this._typeSelect = this.querySelector(Ye), this._uncertain = this.querySelector(Ze), this._template = this.querySelector(ti), this._addInput = this._addSelect ? this._addSelect.querySelector(".ssr-input") : null, !(!this._addPanel || !this._addRow || !this._addSelect || !this._typeSelect || !this._uncertain || !this._template) && (this._addToggle && this._addToggle.addEventListener("click", () => { this._addPanel.classList.toggle("hidden"); }), this._addClose && this._addClose.addEventListener("click", () => { this._addPanel.classList.add("hidden"); @@ -2528,30 +2757,30 @@ class Ye extends HTMLElement { } _insertNewRow() { const i = this._template.content.cloneNode(!0); - if (!(i.querySelector(It) || i.firstElementChild)) + if (!(i.querySelector(wt) || i.firstElementChild)) return; const e = i.querySelector("[data-rel-link]"); e && e.setAttribute("href", `${this._linkBase}${this._pendingItem.id}`); const s = i.querySelector("[data-rel-name]"); s && (s.textContent = this._pendingItem.name || ""); - const n = i.querySelector("[data-rel-detail]"), l = i.querySelector("[data-rel-detail-container]"), r = this._pendingItem.detail || this._pendingItem.bio || ""; - n && r ? n.textContent = r : l && l.remove(); + const n = i.querySelector("[data-rel-detail]"), a = i.querySelector("[data-rel-detail-container]"), r = this._pendingItem.detail || this._pendingItem.bio || ""; + n && r ? n.textContent = r : a && a.remove(); const o = i.querySelector("[data-rel-new]"); o && (o.textContent = this._newLabel); const d = i.querySelector("[data-rel-input='type']"); d && this._typeSelect && (d.innerHTML = this._typeSelect.innerHTML, d.value = this._typeSelect.value, d.name = `${this._prefix}_new_type`); - const h = i.querySelector("[data-rel-input='uncertain']"); - if (h && this._uncertain) { - h.checked = this._uncertain.checked, h.name = `${this._prefix}_new_uncertain`; - const U = `${this._prefix}_new_uncertain_row`; - h.id = U; - const V = i.querySelector("[data-rel-uncertain-label]"); - V && V.setAttribute("for", U); + const c = i.querySelector("[data-rel-input='uncertain']"); + if (c && this._uncertain) { + c.checked = this._uncertain.checked, c.name = `${this._prefix}_new_uncertain`; + const f = `${this._prefix}_new_uncertain_row`; + c.id = f; + const m = i.querySelector("[data-rel-uncertain-label]"); + m && m.setAttribute("for", f); } - const p = i.querySelector("[data-rel-input='id']"); - p && (p.name = `${this._prefix}_new_id`, p.value = this._pendingItem.id); - const m = i.querySelector(Qe); - m && m.addEventListener("click", () => { + const h = i.querySelector("[data-rel-input='id']"); + h && (h.name = `${this._prefix}_new_id`, h.value = this._pendingItem.id); + const u = i.querySelector(ei); + u && u.addEventListener("click", () => { this._addRow.innerHTML = "", this._pendingItem = null, this._clearAddPanel(), this._addPanel && this._addPanel.classList.add("hidden"); }), this._addRow.innerHTML = "", this._addRow.appendChild(i), this._pendingItem = null, this._clearAddPanel(), this._addPanel && this._addPanel.classList.add("hidden"); } @@ -2562,14 +2791,14 @@ class Ye extends HTMLElement { if (!e) return; e.checked = !e.checked, i.classList.toggle("opacity-60", e.checked); - const s = i.closest(It); - s && (s.classList.toggle("bg-red-50", e.checked), s.querySelectorAll(Xe).forEach((r) => { + const s = i.closest(wt); + s && (s.classList.toggle("bg-red-50", e.checked), s.querySelectorAll(ii).forEach((r) => { r.classList.toggle("is-removed", e.checked), r.classList.toggle("text-gray-500", e.checked); })); const n = i.querySelector("[data-delete-label]"); n && (n.textContent = e.checked ? n.getAttribute("data-delete-active") || "Wird entfernt" : n.getAttribute("data-delete-default") || "Entfernen"); - const l = i.querySelector("i"); - l && (e.checked ? (l.classList.add("hidden"), l.classList.remove("ri-delete-bin-line", "ri-arrow-go-back-line")) : (l.classList.remove("hidden"), l.classList.add("ri-delete-bin-line"), l.classList.remove("ri-arrow-go-back-line"))); + const a = i.querySelector("i"); + a && (e.checked ? (a.classList.add("hidden"), a.classList.remove("ri-delete-bin-line", "ri-arrow-go-back-line")) : (a.classList.remove("hidden"), a.classList.add("ri-delete-bin-line"), a.classList.remove("ri-arrow-go-back-line"))); }), i.addEventListener("mouseenter", () => { const t = i.getAttribute("data-delete-toggle"), e = this.querySelector(`#${CSS.escape(t)}`); if (!e || !e.checked) @@ -2589,40 +2818,40 @@ class Ye extends HTMLElement { }); } } -const Ze = "filter-list", ti = "scroll-button", ei = "tool-tip", ii = "abbrev-tooltips", si = "int-link", ni = "popup-image", ai = "tab-list", li = "filter-pill", ri = "image-reel", oi = "multi-select-places", di = "multi-select-simple", hi = "single-select-remote", wt = "reset-button", ci = "div-manager", ui = "items-editor", mi = "almanach-edit-page", _i = "relations-editor"; -customElements.define(si, Ft); -customElements.define(ii, E); -customElements.define(Ze, $t); -customElements.define(ti, Dt); -customElements.define(ei, Pt); -customElements.define(ni, qt); -customElements.define(ai, Ht); -customElements.define(li, Rt); -customElements.define(ri, Ut); -customElements.define(oi, yt); -customElements.define(di, Tt); -customElements.define(hi, Pe); -customElements.define(wt, be); -customElements.define(ci, ve); -customElements.define(ui, Oe); -customElements.define(mi, qe); -customElements.define(_i, Ye); -function pi() { - const a = window.location.pathname, i = window.location.search, t = a + i; +const ni = "filter-list", ai = "scroll-button", li = "tool-tip", ri = "abbrev-tooltips", oi = "int-link", di = "popup-image", hi = "tab-list", ci = "filter-pill", ui = "image-reel", mi = "multi-select-places", _i = "multi-select-simple", pi = "single-select-remote", Rt = "reset-button", fi = "div-manager", gi = "items-editor", bi = "almanach-edit-page", Ei = "relations-editor"; +customElements.define(oi, zt); +customElements.define(ri, L); +customElements.define(ni, Ht); +customElements.define(ai, Ft); +customElements.define(li, Vt); +customElements.define(di, Ut); +customElements.define(hi, Kt); +customElements.define(ci, Pt); +customElements.define(ui, Wt); +customElements.define(mi, xt); +customElements.define(_i, kt); +customElements.define(pi, Ve); +customElements.define(Rt, Le); +customElements.define(fi, Te); +customElements.define(gi, $e); +customElements.define(bi, Ke); +customElements.define(Ei, si); +function Si() { + const l = window.location.pathname, i = window.location.search, t = l + i; return encodeURIComponent(t); } -function fi(a = 5e3, i = 100) { +function vi(l = 5e3, i = 100) { return new Promise((t, e) => { let s = 0; const n = setInterval(() => { - typeof window.QRCode == "function" ? (clearInterval(n), t(window.QRCode)) : (s += i, s >= a && (clearInterval(n), console.error("Timed out waiting for QRCode to become available."), e(new Error("QRCode not available after " + a + "ms. Check if qrcode.min.js is loaded correctly and sets window.QRCode.")))); + typeof window.QRCode == "function" ? (clearInterval(n), t(window.QRCode)) : (s += i, s >= l && (clearInterval(n), console.error("Timed out waiting for QRCode to become available."), e(new Error("QRCode not available after " + l + "ms. Check if qrcode.min.js is loaded correctly and sets window.QRCode.")))); }, i); }); } -async function gi(a) { - const i = await fi(), t = document.getElementById("qr"); +async function Li(l) { + const i = await vi(), t = document.getElementById("qr"); t && (t.innerHTML = "", t.classList.add("hidden"), new i(t, { - text: a, + text: l, width: 1280, height: 1280, colorDark: "#000000", @@ -2632,31 +2861,31 @@ async function gi(a) { t.classList.remove("hidden"); }, 20)); } -function bi(a) { - a && (a.addEventListener("focus", (i) => { - i.preventDefault(), a.select(); - }), a.addEventListener("mousedown", (i) => { - i.preventDefault(), a.select(); - }), a.addEventListener("mouseup", (i) => { - i.preventDefault(), a.select(); - })), a && (a.addEventListener("focus", () => { - a.select(); - }), a.addEventListener("click", () => { - a.select(); +function yi(l) { + l && (l.addEventListener("focus", (i) => { + i.preventDefault(), l.select(); + }), l.addEventListener("mousedown", (i) => { + i.preventDefault(), l.select(); + }), l.addEventListener("mouseup", (i) => { + i.preventDefault(), l.select(); + })), l && (l.addEventListener("focus", () => { + l.select(); + }), l.addEventListener("click", () => { + l.select(); })); } -function Ei() { - document.body.addEventListener("htmx:responseError", function(a) { - const i = a.detail.requestConfig; +function Ai() { + document.body.addEventListener("htmx:responseError", function(l) { + const i = l.detail.requestConfig; if (i.boosted) { - document.body.innerHTML = a.detail.xhr.responseText; - const t = a.detail.xhr.responseURL || i.url; + document.body.innerHTML = l.detail.xhr.responseText; + const t = l.detail.xhr.responseURL || i.url; window.history.pushState(null, "", t); } }); } -function Si(a, i) { - if (!(a instanceof HTMLElement)) { +function Ii(l, i) { + if (!(l instanceof HTMLElement)) { console.warn("Target must be an HTMLElement."); return; } @@ -2664,8 +2893,8 @@ function Si(a, i) { console.warn("Action must be a function."); return; } - const t = a.querySelectorAll(wt); - a.addEventListener("rbichange", (e) => { + const t = l.querySelectorAll(Rt); + l.addEventListener("rbichange", (e) => { for (const s of t) if (s.isCurrentlyModified()) { i(e.details, !0); @@ -2674,85 +2903,85 @@ function Si(a, i) { i(e.details, !1); }); } -function w(a) { - if (!(a instanceof HTMLTextAreaElement)) { +function R(l) { + if (!(l instanceof HTMLTextAreaElement)) { console.warn("TextareaAutoResize: Provided element is not a textarea."); return; } - a.style.height = "auto", a.style.height = `${a.scrollHeight}px`; + l.style.height = "auto", l.style.height = `${l.scrollHeight}px`; } -function xt(a) { - a.key === "Enter" && a.preventDefault(); +function Mt(l) { + l.key === "Enter" && l.preventDefault(); } -function kt(a) { - if (!(a instanceof HTMLTextAreaElement)) { +function Bt(l) { + if (!(l instanceof HTMLTextAreaElement)) { console.warn("HookupTextareaAutoResize: Provided element is not a textarea."); return; } - a.addEventListener("input", () => { - w(a); + l.addEventListener("input", () => { + R(l); }); } -function Li(a) { - if (!(a instanceof HTMLTextAreaElement)) { +function Ti(l) { + if (!(l instanceof HTMLTextAreaElement)) { console.warn("DisconnectTextareaAutoResize: Provided element is not a textarea."); return; } - a.removeEventListener("input", () => { - w(a); + l.removeEventListener("input", () => { + R(l); }); } -function vi(a) { - !(a instanceof HTMLTextAreaElement) && a.classList.contains("no-enter") || a.addEventListener("keydown", xt); +function Ci(l) { + !(l instanceof HTMLTextAreaElement) && l.classList.contains("no-enter") || l.addEventListener("keydown", Mt); } -function Ai(a) { - !(a instanceof HTMLTextAreaElement) && a.classList.contains("no-enter") || a.removeEventListener("keydown", xt); +function wi(l) { + !(l instanceof HTMLTextAreaElement) && l.classList.contains("no-enter") || l.removeEventListener("keydown", Mt); } -function Ii(a, i) { - for (const t of a) +function xi(l, i) { + for (const t of l) if (t.type === "childList") { for (const e of t.addedNodes) - e.nodeType === Node.ELEMENT_NODE && e.matches("textarea") && (kt(e), w(e)); + e.nodeType === Node.ELEMENT_NODE && e.matches("textarea") && (Bt(e), R(e)); for (const e of t.removedNodes) - e.nodeType === Node.ELEMENT_NODE && e.matches("textarea") && (Ai(e), Li(e)); + e.nodeType === Node.ELEMENT_NODE && e.matches("textarea") && (wi(e), Ti(e)); } } -function yi(a) { - if (!(a instanceof HTMLFormElement)) { +function ki(l) { + if (!(l instanceof HTMLFormElement)) { console.warn("FormLoad: Provided element is not a form."); return; } const i = document.querySelectorAll("textarea"); for (const s of i) - kt(s), w(s); + Bt(s), R(s); const t = document.querySelectorAll("textarea.no-enter"); for (const s of t) - vi(s); - new MutationObserver(Ii).observe(a, { + Ci(s); + new MutationObserver(xi).observe(l, { childList: !0, subtree: !0 }); } -window.ShowBoostedErrors = Ei; -window.GenQRCode = gi; -window.SelectableInput = bi; -window.PathPlusQuery = pi; -window.HookupRBChange = Si; -window.FormLoad = yi; +window.ShowBoostedErrors = Ai; +window.GenQRCode = Li; +window.SelectableInput = yi; +window.PathPlusQuery = Si; +window.HookupRBChange = Ii; +window.FormLoad = ki; export { - E as AbbreviationTooltips, - qe as AlmanachEditPage, - $t as FilterList, - Rt as FilterPill, - Ut as ImageReel, - Ft as IntLink, - Oe as ItemsEditor, - yt as MultiSelectRole, - Tt as MultiSelectSimple, - qt as PopupImage, - Ye as RelationsEditor, - Dt as ScrollButton, - Pe as SingleSelectRemote, - Ht as TabList, - Pt as ToolTip + L as AbbreviationTooltips, + Ke as AlmanachEditPage, + Ht as FilterList, + Pt as FilterPill, + Wt as ImageReel, + zt as IntLink, + $e as ItemsEditor, + xt as MultiSelectRole, + kt as MultiSelectSimple, + Ut as PopupImage, + si as RelationsEditor, + Ft as ScrollButton, + Ve as SingleSelectRemote, + Kt as TabList, + Vt as ToolTip }; diff --git a/views/routes/almanach/edit/body.gohtml b/views/routes/almanach/edit/body.gohtml index 077a907..b8bee5c 100644 --- a/views/routes/almanach/edit/body.gohtml +++ b/views/routes/almanach/edit/body.gohtml @@ -80,17 +80,16 @@ type AlmanachResult struct {
    Zuletzt bearbeitet
    -
    - {{ GermanDate $model.result.Entry.Updated }}, - {{ GermanTime - $model.result.Entry.Updated - }}h +
    + {{ GermanDate $model.result.Entry.Updated }}, + {{ GermanTime $model.result.Entry.Updated }}h +
    +
    + + {{- if $model.result.User -}}{{ $model.result.User.Name }}{{- end -}}
    - {{- if $model.result.User -}} -
    - {{- $model.result.User.Name -}} -
    - {{- end -}}
    @@ -100,10 +99,17 @@ type AlmanachResult struct {
    {{ template "_usermessage" $model }} -
    + +
    @@ -553,6 +559,7 @@ type AlmanachResult struct { + data-initial-options='[{{- range $i, $place := $model.result.Places -}}{{- if $i }},{{ end -}}{{ printf "{\"id\":%q,\"name\":%q,\"additional_data\":%q}" $place.Id $place.Name $place.Pseudonyms }}{{- end -}}]' + data-initial-values='[{{- range $i, $place := $model.result.Places -}}{{- if $i }},{{ end -}}{{ printf "%q" $place.Id }}{{- end -}}]'>
    @@ -655,6 +662,7 @@ type AlmanachResult struct {
    +
    +
    +

    + +
    diff --git a/views/transform/almanach-edit.js b/views/transform/almanach-edit.js index ada6d8a..2b0d51a 100644 --- a/views/transform/almanach-edit.js +++ b/views/transform/almanach-edit.js @@ -1,12 +1,25 @@ +const PREFERRED_SERIES_RELATION = "Bevorzugter Reihentitel"; + export class AlmanachEditPage extends HTMLElement { constructor() { super(); this._pendingAgent = null; + this._form = null; + this._saveButton = null; + this._statusEl = null; + this._saveEndpoint = ""; + this._isSaving = false; + this._handleSaveClick = this._handleSaveClick.bind(this); } connectedCallback() { this._initForm(); this._initPlaces(); + this._initSaveHandling(); + } + + disconnectedCallback() { + this._teardownSaveHandling(); } _initForm() { @@ -31,20 +44,383 @@ export class AlmanachEditPage extends HTMLElement { } } - _initPlaces() { - const placesSelect = this.querySelector("#places"); - if (!placesSelect) { + _initPlaces() { + const placesSelect = this.querySelector("#places"); + if (!placesSelect) { + return; + } + const applyInitial = () => { + const initialPlaces = this._parseJSONAttr(placesSelect, "data-initial-options") || []; + const initialPlaceIds = this._parseJSONAttr(placesSelect, "data-initial-values") || []; + if (initialPlaces.length > 0 && typeof placesSelect.setOptions === "function") { + placesSelect.setOptions(initialPlaces); + } + if (initialPlaceIds.length > 0) { + placesSelect.value = initialPlaceIds; + if (typeof placesSelect.captureInitialSelection === "function") { + placesSelect.captureInitialSelection(); + } + } + }; + + if (typeof placesSelect.setOptions === "function") { + applyInitial(); + return; + } + + if (typeof window.customElements?.whenDefined === "function") { + window.customElements.whenDefined("multi-select-simple").then(() => { + requestAnimationFrame(() => applyInitial()); + }); + } + } + + _initSaveHandling() { + this._teardownSaveHandling(); + this._form = this.querySelector("#changealmanachform"); + this._saveButton = this.querySelector("[data-role='almanach-save']"); + this._statusEl = this.querySelector("#almanach-save-feedback"); + if (!this._form || !this._saveButton) { return; } - const initialPlaces = this._parseJSONAttr(placesSelect, "data-initial-options") || []; - const initialPlaceIds = this._parseJSONAttr(placesSelect, "data-initial-values") || []; + this._saveEndpoint = this._form.getAttribute("data-save-endpoint") || this._deriveSaveEndpoint(); + this._saveButton.addEventListener("click", this._handleSaveClick); + } - if (initialPlaces.length > 0 && typeof placesSelect.setOptions === "function") { - placesSelect.setOptions(initialPlaces); + _teardownSaveHandling() { + if (this._saveButton) { + this._saveButton.removeEventListener("click", this._handleSaveClick); } - if (initialPlaceIds.length > 0) { - placesSelect.value = initialPlaceIds; + this._saveButton = null; + this._statusEl = null; + } + + _deriveSaveEndpoint() { + if (!window?.location?.pathname) { + return "/almanach/save"; + } + const path = window.location.pathname.endsWith("/") + ? window.location.pathname.slice(0, -1) + : window.location.pathname; + return `${path}/save`; + } + + async _handleSaveClick(event) { + event.preventDefault(); + if (this._isSaving) { + return; + } + this._clearStatus(); + let payload; + try { + payload = this._buildPayload(); + } catch (error) { + this._showStatus(error instanceof Error ? error.message : String(error), "error"); + return; + } + this._setSavingState(true); + try { + const response = await fetch(this._saveEndpoint, { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(payload), + }); + + let data = null; + try { + data = await response.clone().json(); + } catch { + data = null; + } + + if (!response.ok) { + const message = data?.error || `Speichern fehlgeschlagen (${response.status}).`; + throw new Error(message); + } + + await this._reloadForm(data?.message || "Änderungen gespeichert."); + this._clearStatus(); + } catch (error) { + this._showStatus(error instanceof Error ? error.message : "Speichern fehlgeschlagen.", "error"); + } finally { + this._setSavingState(false); } } + _buildPayload() { + if (!this._form) { + throw new Error("Formular konnte nicht gefunden werden."); + } + const formData = new FormData(this._form); + const entry = { + preferred_title: this._readValue(formData, "preferred_title"), + title: this._readValue(formData, "title"), + parallel_title: this._readValue(formData, "paralleltitle"), + subtitle: this._readValue(formData, "subtitle"), + variant_title: this._readValue(formData, "varianttitle"), + incipit: this._readValue(formData, "incipit"), + responsibility_statement: this._readValue(formData, "responsibility_statement"), + publication_statement: this._readValue(formData, "publication_statement"), + place_statement: this._readValue(formData, "place_statement"), + edition: this._readValue(formData, "edition"), + annotation: this._readValue(formData, "annotation"), + edit_comment: this._readValue(formData, "edit_comment"), + extent: this._readValue(formData, "extent"), + dimensions: this._readValue(formData, "dimensions"), + references: this._readValue(formData, "refs"), + status: this._readValue(formData, "type"), + }; + + if (!entry.preferred_title) { + throw new Error("Kurztitel ist erforderlich."); + } + + const yearValue = this._readValue(formData, "year"); + if (yearValue === "") { + throw new Error("Jahr muss angegeben werden (0 ist erlaubt)."); + } + const parsedYear = Number.parseInt(yearValue, 10); + if (Number.isNaN(parsedYear)) { + throw new Error("Jahr ist ungültig."); + } + entry.year = parsedYear; + + const languages = formData.getAll("languages[]").map((value) => value.trim()).filter(Boolean); + const places = formData.getAll("places[]").map((value) => value.trim()).filter(Boolean); + const { items, removedIds } = this._collectItems(formData); + const { + relations: seriesRelations, + deleted: deletedSeriesRelationIds, + } = this._collectRelations(formData, { + prefix: "entries_series", + targetField: "series", + }); + const newSeriesRelations = this._collectNewRelations("entries_series"); + const hasPreferredSeries = [...seriesRelations, ...newSeriesRelations].some( + (relation) => relation.type === PREFERRED_SERIES_RELATION, + ); + if (!hasPreferredSeries) { + throw new Error("Mindestens ein bevorzugter Reihentitel muss verknüpft sein."); + } + + const { + relations: agentRelations, + deleted: deletedAgentRelationIds, + } = this._collectRelations(formData, { + prefix: "entries_agents", + targetField: "agent", + }); + const newAgentRelations = this._collectNewRelations("entries_agents"); + + return { + csrf_token: this._readValue(formData, "csrf_token"), + last_edited: this._readValue(formData, "last_edited"), + entry, + languages, + places, + items, + deleted_item_ids: removedIds, + series_relations: seriesRelations, + new_series_relations: newSeriesRelations, + deleted_series_relation_ids: deletedSeriesRelationIds, + agent_relations: agentRelations, + new_agent_relations: newAgentRelations, + deleted_agent_relation_ids: deletedAgentRelationIds, + }; + } + + _collectItems(formData) { + const ids = formData.getAll("items_id[]").map((value) => value.trim()); + const owners = formData.getAll("items_owner[]"); + const identifiers = formData.getAll("items_identifier[]"); + const locations = formData.getAll("items_location[]"); + const media = formData.getAll("items_media[]"); + const annotations = formData.getAll("items_annotation[]"); + const uris = formData.getAll("items_uri[]"); + const removed = new Set( + formData + .getAll("items_removed[]") + .map((value) => value.trim()) + .filter(Boolean), + ); + const items = []; + for (let index = 0; index < ids.length; index += 1) { + const id = ids[index] || ""; + if (id && removed.has(id)) { + continue; + } + const owner = (owners[index] || "").trim(); + const identifier = (identifiers[index] || "").trim(); + const location = (locations[index] || "").trim(); + const annotation = (annotations[index] || "").trim(); + const uri = (uris[index] || "").trim(); + const mediaValue = (media[index] || "").trim(); + const hasValues = id || owner || identifier || location || annotation || uri || mediaValue; + if (!hasValues) { + continue; + } + items.push({ + id, + owner, + identifier, + location, + annotation, + uri, + media: mediaValue ? [mediaValue] : [], + }); + } + return { + items, + removedIds: Array.from(removed), + }; + } + + _collectRelations(formData, { prefix, targetField }) { + const relations = []; + const deleted = []; + for (const [key, value] of formData.entries()) { + if (!key.startsWith(`${prefix}_type[`)) { + continue; + } + const relationKey = key.slice(key.indexOf("[") + 1, -1); + const targetKey = `${prefix}_${targetField}[${relationKey}]`; + const relationIdKey = `${prefix}_id[${relationKey}]`; + const deleteKey = `${prefix}_delete[${relationKey}]`; + const uncertainKey = `${prefix}_uncertain[${relationKey}]`; + const targetId = (formData.get(targetKey) || "").trim(); + if (!targetId) { + continue; + } + const relationId = (formData.get(relationIdKey) || relationKey).trim(); + if (formData.has(deleteKey)) { + if (relationId) { + deleted.push(relationId); + } + continue; + } + relations.push({ + id: relationId, + target_id: targetId, + type: (value || "").trim(), + uncertain: formData.has(uncertainKey), + }); + } + return { relations, deleted }; + } + + _collectNewRelations(prefix) { + const editor = this.querySelector(`relations-editor[data-prefix='${prefix}']`); + if (!editor) { + return []; + } + const newRows = editor.querySelectorAll("[data-role='relation-add-row'] [data-rel-row]"); + const relations = []; + newRows.forEach((row) => { + const idInput = row.querySelector(`input[name='${prefix}_new_id']`); + const typeInput = row.querySelector(`select[name='${prefix}_new_type']`); + const uncertainInput = row.querySelector(`input[name='${prefix}_new_uncertain']`); + if (!idInput) { + return; + } + const targetId = idInput.value.trim(); + if (!targetId) { + return; + } + relations.push({ + target_id: targetId, + type: (typeInput?.value || "").trim(), + uncertain: Boolean(uncertainInput?.checked), + }); + }); + return relations; + } + + _readValue(formData, field) { + const value = formData.get(field); + return value ? String(value).trim() : ""; + } + + _setSavingState(isSaving) { + this._isSaving = isSaving; + if (!this._saveButton) { + return; + } + this._saveButton.disabled = isSaving; + const label = this._saveButton.querySelector("span"); + if (label) { + label.textContent = isSaving ? "Speichern..." : "Speichern"; + } + } + + _clearStatus() { + if (!this._statusEl) { + return; + } + this._statusEl.textContent = ""; + this._statusEl.classList.remove("text-red-700", "text-green-700"); + } + + _showStatus(message, type) { + if (!this._statusEl) { + return; + } + this._clearStatus(); + this._statusEl.textContent = message; + if (type === "success") { + this._statusEl.classList.add("text-green-700"); + } else if (type === "error") { + this._statusEl.classList.add("text-red-700"); + } + } + + async _reloadForm(successMessage) { + this._teardownSaveHandling(); + const targetUrl = new URL(window.location.href); + if (successMessage) { + targetUrl.searchParams.set("saved_message", successMessage); + } else { + targetUrl.searchParams.delete("saved_message"); + } + + const response = await fetch(targetUrl.toString(), { + headers: { + "X-Requested-With": "fetch", + }, + }); + if (!response.ok) { + throw new Error("Formular konnte nicht aktualisiert werden."); + } + + const html = await response.text(); + const parser = new DOMParser(); + const doc = parser.parseFromString(html, "text/html"); + + const newForm = doc.querySelector("#changealmanachform"); + const currentForm = this.querySelector("#changealmanachform"); + if (!newForm || !currentForm) { + throw new Error("Formular konnte nicht geladen werden."); + } + currentForm.replaceWith(newForm); + this._form = newForm; + + const newMessage = doc.querySelector("#user-message"); + const currentMessage = this.querySelector("#user-message"); + if (newMessage && currentMessage) { + currentMessage.replaceWith(newMessage); + } + + const newHeader = doc.querySelector("#almanach-header-data"); + const currentHeader = this.querySelector("#almanach-header-data"); + if (newHeader && currentHeader) { + currentHeader.replaceWith(newHeader); + } + + this._initForm(); + this._initPlaces(); + this._initSaveHandling(); + } + } diff --git a/views/transform/multi-select-simple.js b/views/transform/multi-select-simple.js index 8ca0fb3..9b6d01f 100644 --- a/views/transform/multi-select-simple.js +++ b/views/transform/multi-select-simple.js @@ -506,6 +506,14 @@ export class MultiSelectSimple extends HTMLElement { this._updateRootElementStateClasses(); } + captureInitialSelection() { + this._initialValue = [...this._value]; + this._initialOrder = [...this._value]; + this._removedIds.clear(); + this._initialCaptured = true; + this._renderSelectedItems(); + } + _synchronizeHiddenSelect() { if (!this.hiddenSelect) return; this.hiddenSelect.innerHTML = "";