new places

This commit is contained in:
Simon Martens
2026-01-09 14:45:11 +01:00
parent 8903da2350
commit 80c28eca4e
15 changed files with 1028 additions and 137 deletions

View File

@@ -52,3 +52,18 @@ func nextAgentMusenalmID(app core.App) (int, error) {
}
return agent.MusenalmID() + 1, nil
}
func nextPlaceMusenalmID(app core.App) (int, error) {
var place dbmodels.Place
err := app.RecordQuery(dbmodels.PLACES_TABLE).
OrderBy(dbmodels.MUSENALMID_FIELD + " DESC").
Limit(1).
One(&place)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return 1, nil
}
return 0, err
}
return place.MusenalmID() + 1, nil
}

333
controllers/ort_edit.go Normal file
View File

@@ -0,0 +1,333 @@
package controllers
import (
"fmt"
"net/http"
"net/url"
"slices"
"strings"
"github.com/Theodor-Springmann-Stiftung/musenalm/app"
"github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels"
"github.com/Theodor-Springmann-Stiftung/musenalm/middleware"
"github.com/Theodor-Springmann-Stiftung/musenalm/pagemodels"
"github.com/Theodor-Springmann-Stiftung/musenalm/templating"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/router"
"github.com/pocketbase/pocketbase/tools/types"
)
const (
URL_ORT = "/ort/{id}/"
URL_ORT_EDIT = "edit"
URL_ORT_DELETE = "edit/delete"
TEMPLATE_ORT_EDIT = "/ort/edit/"
)
func init() {
pep := &OrtEditPage{
StaticPage: pagemodels.StaticPage{
Name: pagemodels.P_ORT_EDIT_NAME,
URL: URL_ORT_EDIT,
Template: TEMPLATE_ORT_EDIT,
Layout: pagemodels.LAYOUT_LOGIN_PAGES,
},
}
app.Register(pep)
}
type OrtEditPage struct {
pagemodels.StaticPage
}
func (p *OrtEditPage) Setup(router *router.Router[*core.RequestEvent], app core.App, engine *templating.Engine) error {
rg := router.Group(URL_ORT)
rg.BindFunc(middleware.IsAdminOrEditor())
rg.GET(URL_ORT_EDIT, p.GET(engine, app))
rg.POST(URL_ORT_EDIT, p.POST(engine, app))
rg.POST(URL_ORT_DELETE, p.POSTDelete(engine, app))
return nil
}
type OrtEditResult struct {
Place *dbmodels.Place
User *dbmodels.User
Prev *dbmodels.Place
Next *dbmodels.Place
Entries []*dbmodels.Entry
}
func NewOrtEditResult(app core.App, id string) (*OrtEditResult, error) {
place, err := dbmodels.Places_ID(app, id)
if err != nil {
return nil, err
}
var user *dbmodels.User
if place.Editor() != "" {
u, err := dbmodels.Users_ID(app, place.Editor())
if err == nil {
user = u
} else {
app.Logger().Error("Failed to load user for place editor", "place", place.Id, "error", err)
}
}
prev, next, err := placeNeighbors(app, place.Id)
if err != nil {
app.Logger().Error("Failed to load place neighbors", "place", place.Id, "error", err)
}
entries, err := placeEntries(app, place.Id)
if err != nil {
app.Logger().Error("Failed to load place entries", "place", place.Id, "error", err)
}
if len(entries) > 0 {
dbmodels.Sort_Entries_Year_Title(entries)
}
return &OrtEditResult{
Place: place,
User: user,
Prev: prev,
Next: next,
Entries: entries,
}, nil
}
func (p *OrtEditPage) GET(engine *templating.Engine, app core.App) HandleFunc {
return func(e *core.RequestEvent) error {
id := e.Request.PathValue("id")
data := make(map[string]any)
result, err := NewOrtEditResult(app, id)
if err != nil {
return engine.Response404(e, err, data)
}
data["result"] = result
req := templating.NewRequest(e)
data["csrf_token"] = req.Session().Token
if msg := e.Request.URL.Query().Get("saved_message"); msg != "" {
data["success"] = msg
}
return engine.Response200(e, p.Template, data, p.Layout)
}
}
func (p *OrtEditPage) renderError(engine *templating.Engine, app core.App, e *core.RequestEvent, message string) error {
id := e.Request.PathValue("id")
data := make(map[string]any)
result, err := NewOrtEditResult(app, id)
if err != nil {
return engine.Response404(e, err, data)
}
data["result"] = result
data["error"] = message
req := templating.NewRequest(e)
data["csrf_token"] = req.Session().Token
return engine.Response200(e, p.Template, data, p.Layout)
}
type ortEditForm struct {
CSRFToken string `form:"csrf_token"`
LastEdited string `form:"last_edited"`
Name string `form:"name"`
Pseudonyms string `form:"pseudonyms"`
Annotation string `form:"annotation"`
URI string `form:"uri"`
Fictional bool `form:"fictional"`
Status string `form:"status"`
Comment string `form:"edit_comment"`
}
func applyPlaceForm(place *dbmodels.Place, formdata ortEditForm, name string, status string, user *dbmodels.FixedUser) {
place.SetName(name)
place.SetPseudonyms(strings.TrimSpace(formdata.Pseudonyms))
place.SetAnnotation(strings.TrimSpace(formdata.Annotation))
place.SetURI(strings.TrimSpace(formdata.URI))
place.SetFictional(formdata.Fictional)
place.SetEditState(status)
place.SetComment(strings.TrimSpace(formdata.Comment))
if user != nil {
place.SetEditor(user.Id)
}
}
func (p *OrtEditPage) POST(engine *templating.Engine, app core.App) HandleFunc {
return func(e *core.RequestEvent) error {
id := e.Request.PathValue("id")
req := templating.NewRequest(e)
formdata := ortEditForm{}
if err := e.BindBody(&formdata); err != nil {
return p.renderError(engine, app, e, "Formulardaten ungültig.")
}
if err := req.CheckCSRF(formdata.CSRFToken); err != nil {
return p.renderError(engine, app, e, err.Error())
}
place, err := dbmodels.Places_ID(app, id)
if err != nil {
return engine.Response404(e, err, nil)
}
if formdata.LastEdited != "" {
lastEdited, err := types.ParseDateTime(formdata.LastEdited)
if err != nil {
return p.renderError(engine, app, e, "Ungültiger Bearbeitungszeitstempel.")
}
if !place.Updated().Time().Equal(lastEdited.Time()) {
return p.renderError(engine, app, e, "Der Ort wurde inzwischen geändert. Bitte Seite neu laden.")
}
}
name := strings.TrimSpace(formdata.Name)
if name == "" {
return p.renderError(engine, app, e, "Name ist erforderlich.")
}
status := strings.TrimSpace(formdata.Status)
if status == "" || !slices.Contains(dbmodels.EDITORSTATE_VALUES, status) {
return p.renderError(engine, app, e, "Ungültiger Status.")
}
user := req.User()
if err := app.RunInTransaction(func(tx core.App) error {
applyPlaceForm(place, formdata, name, status, user)
return tx.Save(place)
}); err != nil {
app.Logger().Error("Failed to save place", "place_id", place.Id, "error", err)
return p.renderError(engine, app, e, "Speichern fehlgeschlagen.")
}
redirect := fmt.Sprintf("/ort/%s/edit?saved_message=%s", id, url.QueryEscape("Änderungen gespeichert."))
return e.Redirect(http.StatusSeeOther, redirect)
}
}
type ortDeletePayload struct {
CSRFToken string `json:"csrf_token"`
LastEdited string `json:"last_edited"`
}
func (p *OrtEditPage) POSTDelete(engine *templating.Engine, app core.App) HandleFunc {
return func(e *core.RequestEvent) error {
id := e.Request.PathValue("id")
req := templating.NewRequest(e)
payload := ortDeletePayload{}
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(),
})
}
place, err := dbmodels.Places_ID(app, id)
if err != nil {
return e.JSON(http.StatusNotFound, map[string]any{
"error": "Ort wurde nicht gefunden.",
})
}
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 !place.Updated().Time().Equal(lastEdited.Time()) {
return e.JSON(http.StatusConflict, map[string]any{
"error": "Der Ort wurde inzwischen geändert. Bitte Seite neu laden.",
})
}
}
entries, err := placeEntries(app, place.Id)
if err != nil {
return e.JSON(http.StatusInternalServerError, map[string]any{
"error": "Löschen fehlgeschlagen.",
})
}
if err := app.RunInTransaction(func(tx core.App) error {
for _, entry := range entries {
updatedPlaces := []string{}
for _, placeID := range entry.Places() {
if placeID != place.Id {
updatedPlaces = append(updatedPlaces, placeID)
}
}
entry.SetPlaces(updatedPlaces)
if err := tx.Save(entry); err != nil {
return err
}
}
record, err := tx.FindRecordById(dbmodels.PLACES_TABLE, place.Id)
if err != nil {
return err
}
return tx.Delete(record)
}); err != nil {
app.Logger().Error("Failed to delete place", "place_id", place.Id, "error", err)
return e.JSON(http.StatusInternalServerError, map[string]any{
"error": "Löschen fehlgeschlagen.",
})
}
return e.JSON(http.StatusOK, map[string]any{
"success": true,
"redirect": "/orte",
})
}
}
func placeEntries(app core.App, placeID string) ([]*dbmodels.Entry, error) {
entries := []*dbmodels.Entry{}
err := app.RecordQuery(dbmodels.ENTRIES_TABLE).
Where(dbx.NewExp(
dbmodels.PLACES_TABLE+" = {:id} OR (json_valid("+dbmodels.PLACES_TABLE+") = 1 AND EXISTS (SELECT 1 FROM json_each("+dbmodels.PLACES_TABLE+") WHERE value = {:id}))",
dbx.Params{"id": placeID},
)).
All(&entries)
return entries, err
}
func placeNeighbors(app core.App, currentID string) (*dbmodels.Place, *dbmodels.Place, error) {
places := []*dbmodels.Place{}
if err := app.RecordQuery(dbmodels.PLACES_TABLE).All(&places); err != nil {
return nil, nil, err
}
if len(places) == 0 {
return nil, nil, nil
}
dbmodels.Sort_Places_Name(places)
for index, item := range places {
if item.Id != currentID {
continue
}
var prev *dbmodels.Place
var next *dbmodels.Place
if index > 0 {
prev = places[index-1]
}
if index+1 < len(places) {
next = places[index+1]
}
return prev, next, nil
}
return nil, nil, nil
}

140
controllers/ort_new.go Normal file
View File

@@ -0,0 +1,140 @@
package controllers
import (
"fmt"
"net/http"
"net/url"
"slices"
"strings"
"github.com/Theodor-Springmann-Stiftung/musenalm/app"
"github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels"
"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"
)
const (
URL_ORTE_NEW = "/orte/new/"
)
func init() {
pnp := &OrtNewPage{
StaticPage: pagemodels.StaticPage{
Name: pagemodels.P_ORT_NEW_NAME,
URL: URL_ORTE_NEW,
Template: TEMPLATE_ORT_EDIT,
Layout: pagemodels.LAYOUT_LOGIN_PAGES,
},
}
app.Register(pnp)
}
type OrtNewPage struct {
pagemodels.StaticPage
}
func (p *OrtNewPage) Setup(router *router.Router[*core.RequestEvent], app core.App, engine *templating.Engine) error {
rg := router.Group(URL_ORTE_NEW)
rg.BindFunc(middleware.IsAdminOrEditor())
rg.GET("", p.GET(engine, app))
rg.POST("", p.POST(engine, app))
return nil
}
func (p *OrtNewPage) GET(engine *templating.Engine, app core.App) HandleFunc {
return func(e *core.RequestEvent) error {
req := templating.NewRequest(e)
return p.renderPage(engine, app, e, req, "")
}
}
func (p *OrtNewPage) renderPage(engine *templating.Engine, app core.App, e *core.RequestEvent, req *templating.Request, message string) error {
data := make(map[string]any)
collection, err := app.FindCollectionByNameOrId(dbmodels.PLACES_TABLE)
if err != nil {
return engine.Response500(e, err, data)
}
place := dbmodels.NewPlace(core.NewRecord(collection))
place.SetEditState("Unknown")
result := &OrtEditResult{
Place: place,
User: nil,
Prev: nil,
Next: nil,
Entries: []*dbmodels.Entry{},
}
data["result"] = result
data["csrf_token"] = req.Session().Token
data["is_new"] = true
if message != "" {
data["error"] = message
}
return engine.Response200(e, p.Template, data, p.Layout)
}
func (p *OrtNewPage) POST(engine *templating.Engine, app core.App) HandleFunc {
return func(e *core.RequestEvent) error {
req := templating.NewRequest(e)
formdata := ortEditForm{}
if err := e.BindBody(&formdata); err != nil {
return p.renderPage(engine, app, e, req, "Formulardaten ungültig.")
}
if err := req.CheckCSRF(formdata.CSRFToken); err != nil {
return p.renderPage(engine, app, e, req, err.Error())
}
name := strings.TrimSpace(formdata.Name)
if name == "" {
return p.renderPage(engine, app, e, req, "Name ist erforderlich.")
}
status := strings.TrimSpace(formdata.Status)
if status == "" || !slices.Contains(dbmodels.EDITORSTATE_VALUES, status) {
return p.renderPage(engine, app, e, req, "Ungültiger Status.")
}
var createdPlace *dbmodels.Place
user := req.User()
if err := app.RunInTransaction(func(tx core.App) error {
collection, err := tx.FindCollectionByNameOrId(dbmodels.PLACES_TABLE)
if err != nil {
return err
}
place := dbmodels.NewPlace(core.NewRecord(collection))
nextID, err := nextPlaceMusenalmID(tx)
if err != nil {
return err
}
place.SetMusenalmID(nextID)
applyPlaceForm(place, formdata, name, status, user)
if err := tx.Save(place); err != nil {
return err
}
createdPlace = place
return nil
}); err != nil {
app.Logger().Error("Failed to create place", "error", err)
return p.renderPage(engine, app, e, req, "Speichern fehlgeschlagen.")
}
if createdPlace == nil {
return p.renderPage(engine, app, e, req, "Speichern fehlgeschlagen.")
}
redirect := fmt.Sprintf(
"/ort/%s/edit?saved_message=%s",
createdPlace.Id,
url.QueryEscape("Änderungen gespeichert."),
)
return e.Redirect(http.StatusSeeOther, redirect)
}
}

52
controllers/orte.go Normal file
View File

@@ -0,0 +1,52 @@
package controllers
import (
"github.com/Theodor-Springmann-Stiftung/musenalm/app"
"github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels"
"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"
)
const (
URL_ORTE = "/orte/"
TEMPLATE_ORTE = "/orte/"
)
func init() {
op := &OrtePage{
StaticPage: pagemodels.StaticPage{
Name: pagemodels.P_ORTE_NAME,
URL: URL_ORTE,
Template: TEMPLATE_ORTE,
Layout: templating.DEFAULT_LAYOUT_NAME,
},
}
app.Register(op)
}
type OrtePage struct {
pagemodels.StaticPage
}
type OrteResult struct {
Places []*dbmodels.Place
}
func (p *OrtePage) Setup(router *router.Router[*core.RequestEvent], app core.App, engine *templating.Engine) error {
router.GET(URL_ORTE, func(e *core.RequestEvent) error {
places := []*dbmodels.Place{}
if err := app.RecordQuery(dbmodels.PLACES_TABLE).All(&places); err != nil {
return engine.Response500(e, err, nil)
}
if len(places) > 0 {
dbmodels.Sort_Places_Name(places)
}
data := map[string]any{
"result": &OrteResult{Places: places},
}
return engine.Response200(e, p.Template, data, p.Layout)
})
return nil
}

View File

@@ -1,6 +1,9 @@
package dbmodels
import "github.com/pocketbase/pocketbase/core"
import (
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/types"
)
var _ core.RecordProxy = (*Place)(nil)
@@ -89,3 +92,7 @@ func (p *Place) Editor() string {
func (p *Place) SetEditor(editor string) {
p.Set(EDITOR_FIELD, editor)
}
func (p *Place) Updated() types.DateTime {
return p.GetDateTime(UPDATED_FIELD)
}

View File

@@ -13,6 +13,7 @@ const (
T_INDEX_TEXTE = "texte"
P_REIHEN_NAME = "reihen"
P_ORTE_NAME = "orte"
P_DANK_NAME = "danksagungen"
P_KONTAKT_NAME = "kontakt"
P_LIT_NAME = "literatur"
@@ -51,4 +52,6 @@ const (
P_REIHE_NEW_NAME = "reihe_new"
P_PERSON_EDIT_NAME = "person_edit"
P_PERSON_NEW_NAME = "person_new"
P_ORT_EDIT_NAME = "ort_edit"
P_ORT_NEW_NAME = "ort_new"
)

View File

@@ -1221,7 +1221,7 @@ class Re extends HTMLElement {
}
}
y(Re, "formAssociated", !0);
const ct = "mss-component-wrapper", ue = "mss-selected-items-container", ut = "mss-selected-item-pill", mt = "mss-selected-item-text", _t = "mss-selected-item-pill-detail", me = "mss-selected-item-delete-btn", _e = "mss-input-controls-container", pe = "mss-input-wrapper", fe = "mss-input-wrapper-focused", ge = "mss-text-input", be = "mss-create-new-button", Ee = "mss-toggle-button", pt = "mss-inline-row", Se = "mss-options-list", ft = "mss-option-item", gt = "mss-option-item-name", bt = "mss-option-item-detail", ve = "mss-option-item-highlighted", z = "mss-hidden-select", K = "mss-no-items-text", Le = "mss-loading", W = 1, G = 10, Et = 250, St = "mss-state-no-selection", vt = "mss-state-has-selection", Lt = "mss-state-list-open";
const ct = "mss-component-wrapper", ue = "mss-selected-items-container", ut = "mss-selected-item-pill", mt = "mss-selected-item-text", _t = "mss-selected-item-pill-detail", me = "mss-selected-item-delete-btn", pt = "mss-selected-item-edit-link", _e = "mss-input-controls-container", pe = "mss-input-wrapper", fe = "mss-input-wrapper-focused", ge = "mss-text-input", be = "mss-create-new-button", Ee = "mss-toggle-button", ft = "mss-inline-row", Se = "mss-options-list", gt = "mss-option-item", bt = "mss-option-item-name", Et = "mss-option-item-detail", ve = "mss-option-item-highlighted", z = "mss-hidden-select", K = "mss-no-items-text", Le = "mss-loading", W = 1, G = 10, St = 250, vt = "mss-state-no-selection", Lt = "mss-state-has-selection", yt = "mss-state-list-open";
class Oe extends HTMLElement {
constructor() {
super();
@@ -1410,18 +1410,21 @@ class Oe 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 = W, this._remoteLimit = G, 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 = W, this._remoteLimit = G, 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._editBase = this.getAttribute("data-edit-base") || "", this._editSuffix = this.getAttribute("data-edit-suffix") || "/edit", this._setupTemplates(), this._bindEventHandlers();
}
_setupTemplates() {
this.optionTemplate = document.createElement("template"), this.optionTemplate.innerHTML = `
<li role="option" class="${ft}">
<span data-ref="nameEl" class="${gt}"></span>
<span data-ref="detailEl" class="${bt}"></span>
<li role="option" class="${gt}">
<span data-ref="nameEl" class="${bt}"></span>
<span data-ref="detailEl" class="${Et}"></span>
</li>
`, this.selectedItemTemplate = document.createElement("template"), this.selectedItemTemplate.innerHTML = `
<span class="${ut} flex items-center">
<span data-ref="textEl" class="${mt}"></span>
<span data-ref="detailEl" class="${_t} hidden"></span>
<a data-ref="editLink" class="${pt} hidden" aria-label="Bearbeiten">
<i class="ri-edit-line"></i>
</a>
<button type="button" data-ref="deleteBtn" class="${me}">&times;</button>
</span>
`;
@@ -1547,7 +1550,7 @@ class Oe extends HTMLElement {
this.inputElement && (this.inputElement.disabled = e), this.createNewButton && (this.createNewButton.disabled = e), this.toggleAttribute("disabled", e), this.querySelectorAll(`.${me}`).forEach((i) => i.disabled = e), this.hiddenSelect && (this.hiddenSelect.disabled = e), e && this._hideOptionsList();
}
_updateRootElementStateClasses() {
this.classList.toggle(St, this._value.length === 0), this.classList.toggle(vt, this._value.length > 0), this.classList.toggle(Lt, this._isOptionsListVisible);
this.classList.toggle(vt, this._value.length === 0), this.classList.toggle(Lt, this._value.length > 0), this.classList.toggle(yt, this._isOptionsListVisible);
}
_render() {
const e = this.id || `mss-${crypto.randomUUID().slice(0, 8)}`;
@@ -1558,7 +1561,7 @@ class Oe extends HTMLElement {
.${z} { display: block !important; visibility: hidden !important; position: absolute !important; width: 0px !important; height: 0px !important; opacity: 0 !important; pointer-events: none !important; margin: -1px !important; padding: 0 !important; border: 0 !important; overflow: hidden !important; clip: rect(0, 0, 0, 0) !important; white-space: nowrap !important; }
</style>
<div class="${ct} relative">
<div class="${pt} flex flex-wrap items-center gap-2">
<div class="${ft} flex flex-wrap items-center gap-2">
<div class="${ue} flex flex-wrap items-center gap-1 min-h-[30px]" aria-live="polite" tabindex="-1"></div>
${s ? `<button type="button" class="${Ee}">${i}</button>` : ""}
<div class="${_e} flex items-center gap-2 ${n}">
@@ -1582,16 +1585,16 @@ class Oe extends HTMLElement {
_createSelectedItemElement(e) {
const i = this._getItemById(e);
if (!i) return null;
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"]');
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="editLink"]'), d = n.querySelector('[data-ref="deleteBtn"]');
a.textContent = this._normalizeText(i.name);
const d = this._normalizeText(i.additional_data);
d ? (r.textContent = `(${d})`, r.classList.remove("hidden")) : (r.textContent = "", r.classList.add("hidden"));
const c = this._removedIds.has(e);
const c = this._normalizeText(i.additional_data);
c ? (r.textContent = `(${c})`, r.classList.remove("hidden")) : (r.textContent = "", r.classList.add("hidden"));
const h = this._removedIds.has(e);
if (!this._initialValue.includes(e)) {
const u = document.createElement("span");
u.className = "ml-1 text-xs text-gray-600", u.textContent = "(Neu)", a.appendChild(u);
}
return c && (n.classList.add("bg-red-100"), n.style.position = "relative"), o.setAttribute("aria-label", c ? `Undo remove ${i.name}` : `Remove ${i.name}`), o.dataset.id = e, o.disabled = this.hasAttribute("disabled"), o.innerHTML = c ? '<span class="text-xs inline-flex items-center"><i class="ri-arrow-go-back-line"></i></span>' : "&times;", o.addEventListener("click", (u) => {
return h && (n.classList.add("bg-red-100"), n.style.position = "relative"), o && (this._editBase && !h ? (o.href = `${this._editBase}${e}${this._editSuffix}`, o.target = "_blank", o.rel = "noreferrer", o.classList.remove("hidden")) : (o.classList.add("hidden"), o.removeAttribute("href"), o.removeAttribute("target"), o.removeAttribute("rel"))), d.setAttribute("aria-label", h ? `Undo remove ${i.name}` : `Remove ${i.name}`), d.dataset.id = e, d.disabled = this.hasAttribute("disabled"), d.innerHTML = h ? '<span class="text-xs inline-flex items-center"><i class="ri-arrow-go-back-line"></i></span>' : "&times;", d.addEventListener("click", (u) => {
u.stopPropagation(), this._handleDeleteSelectedItem(e);
}), n;
}
@@ -1754,7 +1757,7 @@ class Oe extends HTMLElement {
}
this._remoteFetchTimeout = setTimeout(() => {
this._fetchRemoteOptions(e);
}, Et);
}, St);
}
_cancelRemoteFetch() {
this._remoteFetchController && (this._remoteFetchController.abort(), this._remoteFetchController = null);
@@ -1819,8 +1822,8 @@ class Oe extends HTMLElement {
}
}
y(Oe, "formAssociated", !0);
const yt = "rbi-button", At = "rbi-icon";
class It extends HTMLElement {
const At = "rbi-button", It = "rbi-icon";
class Ct 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);
}
@@ -1829,10 +1832,10 @@ class It extends HTMLElement {
}
connectedCallback() {
const t = `
<button type="button" class="${yt} cursor-pointer disabled:cursor-default" aria-label="Reset field">
<button type="button" class="${At} cursor-pointer disabled:cursor-default" aria-label="Reset field">
<tool-tip position="right">
<div class="data-tip">Feld zurücksetzen</div>
<span class="${At} ri-arrow-go-back-fill"></span>
<span class="${It} ri-arrow-go-back-fill"></span>
</tool-tip>
</button>
`;
@@ -1976,19 +1979,19 @@ class It extends HTMLElement {
this.button.setAttribute("aria-label", t);
}
}
const p = "hidden", ye = "dm-stay", M = "dm-title", Ae = "dm-menu-button", Ct = "dm-target", Tt = "data-dm-target", Ie = "dm-menu", Ce = "dm-menu-item", wt = "dm-close-button";
const p = "hidden", ye = "dm-stay", M = "dm-title", Ae = "dm-menu-button", Tt = "dm-target", wt = "data-dm-target", Ie = "dm-menu", Ce = "dm-menu-item", xt = "dm-close-button";
var D, Be;
class xt extends HTMLElement {
class kt extends HTMLElement {
constructor() {
super();
A(this, D);
R(this, D, Be).call(this), this.boundHandleClickOutside = this.handleClickOutside.bind(this);
}
connectedCallback() {
if (this._target = document.getElementById(this.getAttribute(Ct)), this._target || (this._target = this), this._cildren = Array.from(this.children).filter((e) => e.nodeType === Node.ELEMENT_NODE && !e.classList.contains(Ae)).map((e) => ({
if (this._target = document.getElementById(this.getAttribute(Tt)), this._target || (this._target = this), this._cildren = Array.from(this.children).filter((e) => e.nodeType === Node.ELEMENT_NODE && !e.classList.contains(Ae)).map((e) => ({
node: e,
target: () => {
const i = e.getAttribute(Tt);
const i = e.getAttribute(wt);
return i ? document.getElementById(i) || this._target : this._target;
},
stay: () => e.hasAttribute(ye) && e.getAttribute(ye) == "true",
@@ -2010,7 +2013,7 @@ class xt extends HTMLElement {
this.removeChild(e.node);
this._button.addEventListener("click", this._toggleMenu.bind(this)), this._button.classList.add("relative");
for (const e of this._cildren)
e.node.querySelectorAll(`.${wt}`).forEach((s) => {
e.node.querySelectorAll(`.${xt}`).forEach((s) => {
s.addEventListener("click", (n) => {
this.hideDiv(n, e.node);
});
@@ -2132,13 +2135,13 @@ ${e[0].nameText()} hinzufügen`, this._menu = null, this.hideMenu();
D = new WeakSet(), Be = function() {
this._cildren = [], this._rendered = [], this._target = null, this._button = null, this._menu = null, this._originalButtonText = null;
};
const f = "items-row", kt = "items-list", Rt = "items-template", Ot = "items-add-button", Bt = "items-cancel-button", $ = "items-remove-button", Mt = "items-edit-button", $t = "items-close-button", Nt = "items-summary", Dt = "items-edit-panel", j = "items_removed[]", I = "data-items-removed";
class Pt extends HTMLElement {
const f = "items-row", Rt = "items-list", Ot = "items-template", Bt = "items-add-button", Mt = "items-cancel-button", $ = "items-remove-button", $t = "items-edit-button", Nt = "items-close-button", Dt = "items-summary", Pt = "items-edit-panel", j = "items_removed[]", I = "data-items-removed";
class qt 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(`.${kt}`), this._template = this.querySelector(`template.${Rt}`), this._addButton = this.querySelector(`.${Ot}`), !this._list || !this._template || !this._addButton) {
if (this._list = this.querySelector(`.${Rt}`), this._template = this.querySelector(`template.${Ot}`), this._addButton = this.querySelector(`.${Bt}`), !this._list || !this._template || !this._addButton) {
console.error("ItemsEditor: Missing list, template, or add button.");
return;
}
@@ -2188,7 +2191,7 @@ class Pt extends HTMLElement {
});
}
_wireCancelButtons(t = this) {
t.querySelectorAll(`.${Bt}`).forEach((e) => {
t.querySelectorAll(`.${Mt}`).forEach((e) => {
e.dataset.itemsBound !== "true" && (e.dataset.itemsBound = "true", e.addEventListener("click", (i) => {
i.preventDefault();
const s = e.closest(`.${f}`);
@@ -2197,13 +2200,13 @@ class Pt extends HTMLElement {
});
}
_wireEditButtons(t = this) {
t.querySelectorAll(`.${Mt}`).forEach((e) => {
t.querySelectorAll(`.${$t}`).forEach((e) => {
e.dataset.itemsBound !== "true" && (e.dataset.itemsBound = "true", e.addEventListener("click", (i) => {
i.preventDefault();
const s = e.closest(`.${f}`);
s && this._setRowMode(s, "edit");
}));
}), t.querySelectorAll(`.${$t}`).forEach((e) => {
}), t.querySelectorAll(`.${Nt}`).forEach((e) => {
e.dataset.itemsBound !== "true" && (e.dataset.itemsBound = "true", e.addEventListener("click", (i) => {
i.preventDefault();
const s = e.closest(`.${f}`);
@@ -2236,7 +2239,7 @@ class Pt extends HTMLElement {
});
}
_setRowMode(t, e) {
const i = t.querySelector(`.${Nt}`), s = t.querySelector(`.${Dt}`);
const i = t.querySelector(`.${Dt}`), s = t.querySelector(`.${Pt}`);
!i || !s || (e === "edit" ? (i.classList.add("hidden"), s.classList.remove("hidden")) : (i.classList.remove("hidden"), s.classList.add("hidden"), this._syncSummary(t)));
}
_captureAllOriginals() {
@@ -2329,8 +2332,8 @@ class Pt extends HTMLElement {
i.value === t && i.remove();
}
}
const qt = "ssr-wrapper", Te = "ssr-input", we = "ssr-list", Ht = "ssr-option", Ft = "ssr-option-name", Vt = "ssr-option-detail", Ut = "ssr-option-bio", xe = "ssr-hidden-input", ke = "ssr-clear-button", J = 1, Q = 10, zt = 250;
class Kt extends HTMLElement {
const Ht = "ssr-wrapper", Te = "ssr-input", we = "ssr-list", Ft = "ssr-option", Vt = "ssr-option-name", Ut = "ssr-option-detail", zt = "ssr-option-bio", xe = "ssr-hidden-input", ke = "ssr-clear-button", J = 1, Q = 10, Kt = 250;
class Wt extends HTMLElement {
constructor() {
super(), this._endpoint = "", this._resultKey = "items", this._minChars = J, this._limit = Q, 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);
}
@@ -2395,7 +2398,7 @@ class Kt extends HTMLElement {
_debouncedFetch(t) {
this._fetchTimeout && clearTimeout(this._fetchTimeout), this._fetchTimeout = setTimeout(() => {
this._fetchOptions(t);
}, zt);
}, Kt);
}
async _fetchOptions(t) {
if (!this._endpoint)
@@ -2423,19 +2426,19 @@ class Kt extends HTMLElement {
this._list && (this._list.innerHTML = "", this._options.forEach((t) => {
const e = document.createElement("button");
e.type = "button", e.setAttribute("data-index", String(this._options.indexOf(t))), e.className = [
Ht,
Ft,
"w-full text-left px-3 py-2 hover:bg-slate-100 transition-colors"
].join(" ");
const s = this._options.indexOf(t) === this._highlightedIndex;
e.classList.toggle("bg-slate-100", s), e.classList.toggle("text-gray-900", s), e.setAttribute("aria-selected", s ? "true" : "false");
const n = document.createElement("div");
if (n.className = [Ft, "text-sm font-semibold text-gray-800"].join(" "), n.textContent = t.name, e.appendChild(n), t.detail) {
if (n.className = [Vt, "text-sm font-semibold text-gray-800"].join(" "), n.textContent = t.name, e.appendChild(n), t.detail) {
const a = document.createElement("div");
a.className = [Vt, "text-xs text-gray-600"].join(" "), a.textContent = t.detail, e.appendChild(a);
a.className = [Ut, "text-xs text-gray-600"].join(" "), a.textContent = t.detail, e.appendChild(a);
}
if (t.bio) {
const a = document.createElement("div");
a.className = [Ut, "text-xs text-gray-500"].join(" "), a.textContent = t.bio, e.appendChild(a);
a.className = [zt, "text-xs text-gray-500"].join(" "), a.textContent = t.bio, e.appendChild(a);
}
e.addEventListener("click", () => {
this._selectOption(t);
@@ -2484,7 +2487,7 @@ class Kt extends HTMLElement {
_render() {
const t = this.getAttribute("name") || "";
this.innerHTML = `
<div class="${qt} relative">
<div class="${Ht} relative">
<div class="flex items-center gap-2">
<input
type="text"
@@ -2505,8 +2508,8 @@ class Kt extends HTMLElement {
`;
}
}
const Wt = "Bevorzugter Reihentitel";
class Gt extends HTMLElement {
const Gt = "Bevorzugter Reihentitel";
class jt extends HTMLElement {
constructor() {
super(), this._pendingAgent = null, this._form = null, this._saveButton = null, this._resetButton = null, this._deleteButton = null, this._deleteDialog = null, this._deleteConfirmButton = null, this._deleteCancelButton = null, this._statusEl = null, this._saveEndpoint = "", this._deleteEndpoint = "", this._isSaving = !1, this._handleSaveClick = this._handleSaveClick.bind(this), this._handleResetClick = this._handleResetClick.bind(this), this._handleDeleteClick = this._handleDeleteClick.bind(this), this._handleDeleteConfirmClick = this._handleDeleteConfirmClick.bind(this), this._handleDeleteCancelClick = this._handleDeleteCancelClick.bind(this);
}
@@ -2690,15 +2693,15 @@ class Gt extends HTMLElement {
} = this._collectRelations(t, {
prefix: "entries_series",
targetField: "series"
}), h = this._collectNewRelations("entries_series"), u = [...d, ...h].filter(
(g) => g.type === Wt
}), h = this._collectNewRelations("entries_series"), m = [...d, ...h].filter(
(g) => g.type === Gt
).length;
if (u === 0)
if (m === 0)
throw new Error("Mindestens ein bevorzugter Reihentitel muss verknüpft sein.");
if (u > 1)
if (m > 1)
throw new Error("Es darf nur ein bevorzugter Reihentitel gesetzt sein.");
const {
relations: m,
relations: u,
deleted: _
} = this._collectRelations(t, {
prefix: "entries_agents",
@@ -2717,7 +2720,7 @@ class Gt extends HTMLElement {
series_relations: d,
new_series_relations: h,
deleted_series_relation_ids: c,
agent_relations: m,
agent_relations: u,
new_agent_relations: S,
deleted_agent_relation_ids: _
};
@@ -2727,13 +2730,13 @@ class Gt extends HTMLElement {
t.getAll("items_removed[]").map((h) => h.trim()).filter(Boolean)
), c = [];
for (let h = 0; h < e.length; h += 1) {
const u = e[h] || "";
if (u && d.has(u))
const m = e[h] || "";
if (m && d.has(m))
continue;
const m = (i[h] || "").trim(), _ = (s[h] || "").trim(), S = (n[h] || "").trim(), P = (r[h] || "").trim(), L = (o[h] || "").trim(), x = (a[h] || "").trim();
(u || m || _ || S || P || L || x) && c.push({
id: u,
owner: m,
const u = (i[h] || "").trim(), _ = (s[h] || "").trim(), S = (n[h] || "").trim(), P = (r[h] || "").trim(), L = (o[h] || "").trim(), x = (a[h] || "").trim();
(m || u || _ || S || P || L || x) && c.push({
id: m,
owner: u,
identifier: _,
location: S,
annotation: P,
@@ -2751,19 +2754,19 @@ class Gt extends HTMLElement {
for (const [a, r] of t.entries()) {
if (!a.startsWith(`${e}_id[`))
continue;
const o = a.slice(a.indexOf("[") + 1, -1), d = `${e}_${i}[${o}]`, c = `${e}_type[${o}]`, h = `${e}_delete[${o}]`, u = `${e}_uncertain[${o}]`, m = (r || "").trim(), _ = (t.get(d) || "").trim();
if (!_ || !m)
const o = a.slice(a.indexOf("[") + 1, -1), d = `${e}_${i}[${o}]`, c = `${e}_type[${o}]`, h = `${e}_delete[${o}]`, m = `${e}_uncertain[${o}]`, u = (r || "").trim(), _ = (t.get(d) || "").trim();
if (!_ || !u)
continue;
if (t.has(h)) {
n.push(m);
n.push(u);
continue;
}
const S = (t.get(c) || "").trim();
s.push({
id: m,
id: u,
target_id: _,
type: S,
uncertain: t.has(u)
uncertain: t.has(m)
});
}
return { relations: s, deleted: n };
@@ -2819,16 +2822,16 @@ class Gt extends HTMLElement {
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(), typeof window.TextareaAutoResize == "function" && setTimeout(() => {
this.querySelectorAll("textarea").forEach((m) => {
window.TextareaAutoResize(m);
const h = a.querySelector("#almanach-header-data"), m = this.querySelector("#almanach-header-data");
h && m && m.replaceWith(h), this._initForm(), this._initPlaces(), this._initSaveHandling(), typeof window.TextareaAutoResize == "function" && setTimeout(() => {
this.querySelectorAll("textarea").forEach((u) => {
window.TextareaAutoResize(u);
});
}, 100);
}
}
const jt = "[data-role='relation-add-toggle']", Jt = "[data-role='relation-add-panel']", Qt = "[data-role='relation-add-close']", Xt = "[data-role='relation-add-apply']", Yt = "[data-role='relation-add-error']", Zt = "[data-role='relation-add-row']", ei = "[data-role='relation-add-select']", ti = "[data-role='relation-type-select']", ii = "[data-role='relation-uncertain']", si = "template[data-role='relation-new-template']", ni = "[data-role='relation-new-delete']", C = "[data-rel-row]";
class ai extends HTMLElement {
const Jt = "[data-role='relation-add-toggle']", Qt = "[data-role='relation-add-panel']", Xt = "[data-role='relation-add-close']", Yt = "[data-role='relation-add-apply']", Zt = "[data-role='relation-add-error']", ei = "[data-role='relation-add-row']", ti = "[data-role='relation-add-select']", ii = "[data-role='relation-type-select']", si = "[data-role='relation-uncertain']", ni = "template[data-role='relation-new-template']", ai = "[data-role='relation-new-delete']", C = "[data-rel-row]";
class li extends HTMLElement {
constructor() {
super(), this._pendingItem = null, this._pendingApply = !1;
}
@@ -2852,11 +2855,11 @@ class ai extends HTMLElement {
this._addPanel && !this._addPanel.classList.contains("hidden") || e || i ? this._emptyText.classList.add("hidden") : this._emptyText.classList.remove("hidden");
}
_setupAddPanel() {
if (this._addToggle = this.querySelector(jt), this._addToggleId) {
if (this._addToggle = this.querySelector(Jt), this._addToggleId) {
const t = document.getElementById(this._addToggleId);
t && (this._addToggle = t);
}
this._addPanel = this.querySelector(Jt), this._addClose = this.querySelector(Qt), this._addApply = this.querySelector(Xt), this._addError = this.querySelector(Yt), this._addRow = this.querySelector(Zt), this._addSelect = this.querySelector(ei), this._typeSelect = this.querySelector(ti), this._uncertain = this.querySelector(ii), this._template = this.querySelector(si), this._addInput = this._addSelect ? this._addSelect.querySelector(".ssr-input") : null, !(!this._addPanel || !this._addRow || !this._addSelect || !this._typeSelect || !this._uncertain || !this._template) && (this._addSelect && this._prefix === "entries_series" && this._addSelect.addEventListener("ssrbeforefetch", () => {
this._addPanel = this.querySelector(Qt), this._addClose = this.querySelector(Xt), this._addApply = this.querySelector(Yt), this._addError = this.querySelector(Zt), this._addRow = this.querySelector(ei), this._addSelect = this.querySelector(ti), this._typeSelect = this.querySelector(ii), this._uncertain = this.querySelector(si), this._template = this.querySelector(ni), this._addInput = this._addSelect ? this._addSelect.querySelector(".ssr-input") : null, !(!this._addPanel || !this._addRow || !this._addSelect || !this._typeSelect || !this._uncertain || !this._template) && (this._addSelect && this._prefix === "entries_series" && this._addSelect.addEventListener("ssrbeforefetch", () => {
this._addSelect._excludeIds = Array.from(this._getExistingIds());
}), this._addToggle && this._addToggle.addEventListener("click", () => {
this._addPanel.classList.toggle("hidden"), this._updateEmptyTextVisibility();
@@ -2907,15 +2910,15 @@ class ai extends HTMLElement {
const c = t.querySelector("[data-rel-input='uncertain']");
if (c && this._uncertain) {
c.checked = this._uncertain.checked, c.name = `${this._prefix}_new_uncertain`;
const m = `${this._prefix}_new_uncertain_row`;
c.id = m;
const u = `${this._prefix}_new_uncertain_row`;
c.id = u;
const _ = t.querySelector("[data-rel-uncertain-label]");
_ && _.setAttribute("for", m);
_ && _.setAttribute("for", u);
}
const h = t.querySelector("[data-rel-input='id']");
h && (h.name = `${this._prefix}_new_id`, h.value = this._pendingItem.id);
const u = t.querySelector(ni);
u && u.addEventListener("click", () => {
const m = t.querySelector(ai);
m && m.addEventListener("click", () => {
this._addRow.innerHTML = "", this._pendingItem = null, this._clearAddPanel(), this._addPanel && this._addPanel.classList.add("hidden"), this._updateEmptyTextVisibility();
}), this._addRow.innerHTML = "", this._addRow.appendChild(t), this._pendingItem = null, this._clearAddPanel(), this._addPanel && this._addPanel.classList.add("hidden"), this._updateEmptyTextVisibility(), this._updatePreferredOptions();
}
@@ -2983,20 +2986,20 @@ class ai extends HTMLElement {
e.forEach(({ select: s, row: n, isAddPanel: a }) => {
if (!s)
return;
const r = Array.from(s.options).find((m) => m.value.trim() === t);
const r = Array.from(s.options).find((u) => u.value.trim() === t);
if (!r)
return;
const o = n ? n.querySelector(`input[name^="${this._prefix}_delete["]`) : null, d = !!(o && o.checked), c = (s.value || "").trim(), h = !i || c === t && !d;
if (a && i && c === t) {
const m = Array.from(s.options).find((_) => _.value.trim() !== t);
m && (s.value = m.value);
const u = Array.from(s.options).find((_) => _.value.trim() !== t);
u && (s.value = u.value);
}
const u = !h || a && i;
r.hidden = u, r.disabled = u, r.style.display = u ? "none" : "";
const m = !h || a && i;
r.hidden = m, r.disabled = m, r.style.display = m ? "none" : "";
});
}
}
class li extends HTMLElement {
class ri extends HTMLElement {
connectedCallback() {
setTimeout(() => {
const t = this.querySelector("form");
@@ -3034,35 +3037,35 @@ class li extends HTMLElement {
});
if (!h.ok)
return;
const u = await h.json().catch(() => null), m = (u == null ? void 0 : u.redirect) || "/";
window.location.assign(m);
const m = await h.json().catch(() => null), u = (m == null ? void 0 : m.redirect) || "/";
window.location.assign(u);
});
}
}
const ri = "filter-list", oi = "scroll-button", di = "tool-tip", hi = "abbrev-tooltips", ci = "int-link", ui = "popup-image", mi = "tab-list", _i = "filter-pill", pi = "image-reel", fi = "multi-select-places", gi = "multi-select-simple", bi = "single-select-remote", Me = "reset-button", Ei = "div-manager", Si = "items-editor", vi = "almanach-edit-page", Li = "relations-editor", yi = "edit-page";
customElements.define(ci, je);
customElements.define(hi, T);
customElements.define(ri, Ue);
customElements.define(oi, ze);
customElements.define(di, Ke);
customElements.define(ui, We);
customElements.define(mi, Ge);
customElements.define(_i, He);
customElements.define(pi, Je);
customElements.define(fi, Re);
customElements.define(gi, Oe);
customElements.define(bi, Kt);
customElements.define(Me, It);
customElements.define(Ei, xt);
customElements.define(Si, Pt);
customElements.define(vi, Gt);
customElements.define(Li, ai);
const oi = "filter-list", di = "scroll-button", hi = "tool-tip", ci = "abbrev-tooltips", ui = "int-link", mi = "popup-image", _i = "tab-list", pi = "filter-pill", fi = "image-reel", gi = "multi-select-places", bi = "multi-select-simple", Ei = "single-select-remote", Me = "reset-button", Si = "div-manager", vi = "items-editor", Li = "almanach-edit-page", yi = "relations-editor", Ai = "edit-page";
customElements.define(ui, je);
customElements.define(ci, T);
customElements.define(oi, Ue);
customElements.define(di, ze);
customElements.define(hi, Ke);
customElements.define(mi, We);
customElements.define(_i, Ge);
customElements.define(pi, He);
customElements.define(fi, Je);
customElements.define(gi, Re);
customElements.define(bi, Oe);
customElements.define(Ei, Wt);
customElements.define(Me, Ct);
customElements.define(Si, kt);
customElements.define(vi, qt);
customElements.define(Li, jt);
customElements.define(yi, li);
function Ai() {
customElements.define(Ai, ri);
function Ii() {
const l = window.location.pathname, t = window.location.search, e = l + t;
return encodeURIComponent(e);
}
function Ii(l = 5e3, t = 100) {
function Ci(l = 5e3, t = 100) {
return new Promise((e, i) => {
let s = 0;
const n = setInterval(() => {
@@ -3070,8 +3073,8 @@ function Ii(l = 5e3, t = 100) {
}, t);
});
}
async function Ci(l) {
const t = await Ii(), e = document.getElementById("qr");
async function Ti(l) {
const t = await Ci(), e = document.getElementById("qr");
e && (e.innerHTML = "", e.classList.add("hidden"), new t(e, {
text: l,
width: 1280,
@@ -3083,7 +3086,7 @@ async function Ci(l) {
e.classList.remove("hidden");
}, 20));
}
function Ti(l) {
function wi(l) {
l && (l.addEventListener("focus", (t) => {
t.preventDefault(), l.select();
}), l.addEventListener("mousedown", (t) => {
@@ -3096,7 +3099,7 @@ function Ti(l) {
l.select();
}));
}
function wi() {
function xi() {
document.body.addEventListener("htmx:responseError", function(l) {
const t = l.detail.requestConfig;
if (t.boosted) {
@@ -3106,7 +3109,7 @@ function wi() {
}
});
}
function xi(l, t) {
function ki(l, t) {
if (!(l instanceof HTMLElement)) {
console.warn("Target must be an HTMLElement.");
return;
@@ -3151,7 +3154,7 @@ function E(l) {
function Ne(l) {
l.key === "Enter" && l.preventDefault();
}
function ki(l) {
function Ri(l) {
if (!(l instanceof HTMLTextAreaElement)) {
console.warn("HookupTextareaAutoResize: Provided element is not a textarea.");
return;
@@ -3160,7 +3163,7 @@ function ki(l) {
E(l);
});
}
function Ri(l) {
function Oi(l) {
if (!(l instanceof HTMLTextAreaElement)) {
console.warn("DisconnectTextareaAutoResize: Provided element is not a textarea.");
return;
@@ -3169,23 +3172,23 @@ function Ri(l) {
E(l);
});
}
function Oi(l) {
function Bi(l) {
!(l instanceof HTMLTextAreaElement) && l.classList.contains("no-enter") || l.addEventListener("keydown", Ne);
}
function Bi(l) {
function Mi(l) {
!(l instanceof HTMLTextAreaElement) && l.classList.contains("no-enter") || l.removeEventListener("keydown", Ne);
}
function Mi(l, t) {
function $i(l, t) {
const e = !$e();
for (const i of l)
if (i.type === "childList") {
for (const s of i.addedNodes)
s.nodeType === Node.ELEMENT_NODE && s.matches("textarea") && e && (ki(s), E(s));
s.nodeType === Node.ELEMENT_NODE && s.matches("textarea") && e && (Ri(s), E(s));
for (const s of i.removedNodes)
s.nodeType === Node.ELEMENT_NODE && s.matches("textarea") && (Bi(s), e && Ri(s));
s.nodeType === Node.ELEMENT_NODE && s.matches("textarea") && (Mi(s), e && Oi(s));
}
}
function $i(l) {
function Ni(l) {
if (console.log("=== FormLoad CALLED ==="), !(l instanceof HTMLFormElement)) {
console.warn("FormLoad: Provided element is not a form.");
return;
@@ -3203,8 +3206,8 @@ function $i(l) {
}, 200);
const e = document.querySelectorAll("textarea.no-enter");
for (const n of e)
Oi(n);
new MutationObserver(Mi).observe(l, {
Bi(n);
new MutationObserver($i).observe(l, {
childList: !0,
subtree: !0
}), new MutationObserver((n) => {
@@ -3229,28 +3232,28 @@ document.addEventListener("keydown", (l) => {
const t = l.target;
t instanceof HTMLElement && t.matches("textarea.no-enter") && l.preventDefault();
});
window.ShowBoostedErrors = wi;
window.GenQRCode = Ci;
window.SelectableInput = Ti;
window.PathPlusQuery = Ai;
window.HookupRBChange = xi;
window.FormLoad = $i;
window.ShowBoostedErrors = xi;
window.GenQRCode = Ti;
window.SelectableInput = wi;
window.PathPlusQuery = Ii;
window.HookupRBChange = ki;
window.FormLoad = Ni;
window.TextareaAutoResize = E;
export {
T as AbbreviationTooltips,
Gt as AlmanachEditPage,
li as EditPage,
jt as AlmanachEditPage,
ri as EditPage,
Ue as FilterList,
He as FilterPill,
Je as ImageReel,
je as IntLink,
Pt as ItemsEditor,
qt as ItemsEditor,
Re as MultiSelectRole,
Oe as MultiSelectSimple,
We as PopupImage,
ai as RelationsEditor,
li as RelationsEditor,
ze as ScrollButton,
Kt as SingleSelectRemote,
Wt as SingleSelectRemote,
Ge as TabList,
Ke as ToolTip
};

File diff suppressed because one or more lines are too long

View File

@@ -289,10 +289,10 @@ type AlmanachResult struct {
</div>
<div class="flex items-center gap-3">
<button type="button" id="series-add-toggle" class="text-gray-700 hover:text-gray-900">
<i class="ri-add-line"></i> Reihe hinzufügen
<i class="ri-add-line"></i> Reihe verlinken
</button>
<button type="button" id="agents-add-toggle" class="text-gray-700 hover:text-gray-900">
<i class="ri-add-line"></i> Person hinzufügen
<i class="ri-add-line"></i> Person verlinken
</button>
</div>
</div>
@@ -300,7 +300,12 @@ type AlmanachResult struct {
<div class="mt-3">
<relations-editor data-prefix="entries_series" data-link-base="/reihe/" data-new-label="(Neu)" data-add-toggle-id="series-add-toggle" data-preferred-label="Bevorzugter Reihentitel">
<div class="inputwrapper">
<label class="inputlabel" for="series-section">Reihen</label>
<div class="flex items-center justify-between">
<label class="inputlabel" for="series-section">Reihen</label>
<a href="/reihen/new/" class="text-sm font-bold text-gray-700 hover:text-slate-950 no-underline pr-3" target="_blank" rel="noreferrer">
<i class="ri-add-line"></i> Neue Reihe anlegen
</a>
</div>
<div id="series-section" class="rel-section-container">
{{- if $model.result.Series -}}
{{- range $i, $s := $model.result.Series -}}
@@ -309,7 +314,7 @@ type AlmanachResult struct {
<div data-rel-row class="entries-series-row rel-row">
<div class="rel-grid">
<div data-rel-strike class="relation-strike rel-name-col">
<a data-rel-link href="/reihe/{{ $s.MusenalmID }}" class="rel-link">
<a data-rel-link href="/reihe/{{ $s.MusenalmID }}" class="rel-link" target="_blank" rel="noreferrer">
<span data-rel-name>{{- $s.Title -}}</span>
</a>
{{- if $s.Pseudonyms -}}
@@ -429,7 +434,12 @@ type AlmanachResult struct {
<div class="mt-3">
<relations-editor data-prefix="entries_agents" data-link-base="/person/" data-new-label="(Neu)" data-add-toggle-id="agents-add-toggle">
<div class="inputwrapper">
<label class="inputlabel" for="agents-section">Personen &amp; Körperschaften</label>
<div class="flex items-center justify-between">
<label class="inputlabel" for="agents-section">Personen &amp; Körperschaften</label>
<a href="/personen/new/" class="text-sm font-bold text-gray-700 hover:text-slate-950 no-underline pr-3" target="_blank" rel="noreferrer">
<i class="ri-add-line"></i> Neue Person/Körperschaft anlegen
</a>
</div>
<div id="agents-section" class="rel-section-container">
{{- if $model.result.EntriesAgents -}}
{{- range $i, $r := $model.result.EntriesAgents -}}
@@ -438,7 +448,7 @@ type AlmanachResult struct {
<div class="rel-grid">
<div data-rel-strike class="relation-strike rel-name-col">
{{- if $a -}}
<a data-rel-link href="/person/{{ $a.Id }}" class="rel-link">
<a data-rel-link href="/person/{{ $a.Id }}" class="rel-link" target="_blank" rel="noreferrer">
<span data-rel-name>{{- $a.Name -}}</span>
</a>
{{- if $a.BiographicalData -}}
@@ -559,13 +569,20 @@ type AlmanachResult struct {
</div>
<div class="flex flex-col gap-4 mt-4">
<div class="inputwrapper">
<div class="inputwrapper">
<div class="flex items-center justify-between">
<label for="places" class="inputlabel">Erscheinungs- und Verlagsorte</label>
<multi-select-simple
id="places"
name="places[]"
value='[{{- range $i, $place := $model.result.Places -}}{{- if $i }},{{ end -}}"{{ $place.Id }}"{{- end -}}]'
placeholder="Orte suchen..."
<a href="/orte/new/" class="text-sm font-bold text-gray-700 hover:text-slate-950 no-underline pr-3" target="_blank" rel="noreferrer">
<i class="ri-add-line"></i> Neuen Ort anlegen
</a>
</div>
<multi-select-simple
id="places"
name="places[]"
data-edit-base="/ort/"
data-edit-suffix="/edit"
value='[{{- range $i, $place := $model.result.Places -}}{{- if $i }},{{ end -}}"{{ $place.Id }}"{{- end -}}]'
placeholder="Orte suchen..."
data-toggle-label='<i class="ri-add-circle-line"></i>'
data-empty-text="Keine Orte ausgewählt..."
show-create-button="false"

View File

@@ -0,0 +1,248 @@
{{ $model := . }}
{{ $place := $model.result.Place }}
<edit-page>
<div class="flex container-normal bg-slate-100 mx-auto px-8">
<div class="flex flex-row w-full justify-between">
<div class="flex flex-col justify-end-safe flex-2/5">
<h1 class="text-2xl w-full font-bold text-slate-900 mb-4">
{{- if $model.is_new -}}
Neuer Ort
{{- else -}}
{{- $place.Name -}}
{{- end -}}
{{- if $model.is_new -}}
<span class="ml-2 text-sm font-semibold text-amber-700 bg-amber-100 px-2 py-0.5 rounded-xs align-middle">Neu</span>
{{- end -}}
</h1>
{{- if not $model.is_new -}}
<div class="flex flex-row gap-x-3">
<div>
<a href="/orte/" class="text-gray-700 hover:text-slate-950 block no-underline">
<i class="ri-eye-line"></i> Orte
</a>
</div>
&middot;
<div>
<a href="/ort/{{ $place.Id }}/edit" class="text-gray-700 no-underline hover:text-slate-950 block">
<i class="ri-loop-left-line"></i> Reset
</a>
</div>
</div>
{{- end -}}
</div>
{{- if not $model.is_new -}}
<div class="flex flex-row" id="place-header-data">
<div class="flex flex-col justify-end gap-y-6 pr-20">
<div class="">
<div class="font-bold text-sm">
<i class="ri-database-2-line"></i> Datenbank-ID
</div>
<div class="">{{ $place.Id }}</div>
</div>
</div>
<div class="flex flex-col justify-end gap-y-6 pr-6">
<div class="">
<div class="font-bold text-sm">
<i class="ri-navigation-line"></i> Navigation
</div>
<div class="flex items-center gap-3">
{{- if $model.result.Prev -}}
<tool-tip position="top" class="!inline">
<div class="data-tip">{{ $model.result.Prev.Name }}</div>
<a
href="/ort/{{ $model.result.Prev.Id }}/edit"
class="text-gray-700 hover:text-slate-950 no-underline">
<i class="ri-arrow-left-s-line"></i>
</a>
</tool-tip>
{{- end -}}
{{- if $model.result.Next -}}
<tool-tip position="top" class="!inline">
<div class="data-tip">{{ $model.result.Next.Name }}</div>
<a
href="/ort/{{ $model.result.Next.Id }}/edit"
class="text-gray-700 hover:text-slate-950 no-underline">
<i class="ri-arrow-right-s-line"></i>
</a>
</tool-tip>
{{- end -}}
</div>
</div>
</div>
<div class="flex flex-col justify-end gap-y-6 pr-4">
<div class="">
<div class="font-bold text-sm mb-1"><i class="ri-calendar-line"></i> Zuletzt bearbeitet</div>
<div>
<div class="px-1.5 py-0.5 rounded-xs bg-gray-200 w-fit" id="place-updated-stamp">
<span id="place-updated-date">{{ GermanDate $place.Updated }}</span>,
<span id="place-updated-time">{{ GermanTime $place.Updated }}</span>h
</div>
<div
class="px-1.5 py-0.5 rounded-xs mt-1.5 bg-gray-200 w-fit {{ if not $model.result.User }}hidden{{ end }}"
id="place-updated-user">
<i class="ri-user-line mr-1"></i>
<span id="place-updated-user-name">{{- if $model.result.User -}}{{ $model.result.User.Name }}{{- end -}}</span>
</div>
</div>
</div>
</div>
</div>
{{- end -}}
</div>
</div>
<div class="container-normal mx-auto mt-4 !px-0">
{{ template "_usermessage" $model }}
<form
class="w-full dbform"
id="changeplaceform"
method="POST"
action="{{ if $model.is_new }}/orte/new/{{ else }}/ort/{{ $place.Id }}/edit{{ end }}"
{{- if not $model.is_new -}}
data-delete-endpoint="/ort/{{ $place.Id }}/edit/delete"
{{- end -}}>
<input type="hidden" name="csrf_token" value="{{ $model.csrf_token }}" />
<input type="hidden" name="last_edited" value="{{ if not $model.is_new }}{{ $place.Updated }}{{ end }}" />
<div class="flex gap-8">
<div class="flex-1 flex flex-col gap-4">
<div class="inputwrapper">
<label for="name" class="inputlabel">Name</label>
<textarea name="name" id="name" class="inputinput no-enter" autocomplete="off" rows="1">{{- $place.Name -}}</textarea>
</div>
<div class="inputwrapper">
<label for="pseudonyms" class="inputlabel">Alternativnamen</label>
<textarea name="pseudonyms" id="pseudonyms" class="inputinput" autocomplete="off" rows="1">{{- $place.Pseudonyms -}}</textarea>
</div>
<div class="inputwrapper">
<label for="annotation" class="inputlabel">Annotation</label>
<textarea name="annotation" id="annotation" class="inputinput" autocomplete="off" rows="2">{{- $place.Annotation -}}</textarea>
</div>
<div class="inputwrapper">
<label for="uri" class="inputlabel">URI</label>
<input name="uri" id="uri" class="inputinput" autocomplete="off" value="{{ $place.URI }}" />
</div>
<div class="inputwrapper">
<label class="inputlabel">Typ</label>
<div class="px-3 py-2 flex flex-col gap-2">
<label class="flex items-center gap-2 text-sm text-gray-700">
<input type="checkbox" name="fictional" {{ if $place.Fictional }}checked{{ end }} />
Fiktiv
</label>
</div>
</div>
</div>
<div class="w-[28rem] shrink-0 flex flex-col gap-3">
<div class="inputwrapper">
<label for="status" class="inputlabel">Status</label>
<select name="status" id="status" autocomplete="off" class="inputselect font-bold">
<option value="Unknown" {{ if eq $place.EditState "Unknown" }}selected{{ end }}>Unbekannt</option>
<option value="ToDo" {{ if eq $place.EditState "ToDo" }}selected{{ end }}>Zu erledigen</option>
<option value="Review" {{ if eq $place.EditState "Review" }}selected{{ end }}>Überprüfen</option>
<option value="Seen" {{ if eq $place.EditState "Seen" }}selected{{ end }}>Autopsiert</option>
<option value="Edited" {{ if eq $place.EditState "Edited" }}selected{{ end }}>Vollständig Erfasst</option>
</select>
</div>
<div class="inputwrapper">
<label for="edit_comment" class="inputlabel">Bearbeitungsvermerk</label>
<textarea name="edit_comment" id="edit_comment" class="inputinput" autocomplete="off" rows="1">{{- $place.Comment -}}</textarea>
</div>
<div class="mt-2">
<tab-list
data-default-index="{{ if gt (len $model.result.Entries) 0 }}0{{ end }}"
data-disabled-indices="{{ if eq (len $model.result.Entries) 0 }}0{{ end }}">
<div class="flex items-center gap-3 text-sm font-bold text-gray-700">
<div class="tab-list-head flex items-center gap-2">
<i class="ri-book-2-line"></i>
<span>Verknüpfte Bände</span>
<span class="text-xs bg-stone-200 text-gray-700 px-2 py-0.5 rounded-sm">{{ len $model.result.Entries }}</span>
</div>
</div>
<hr class="border-slate-400 mt-2 mb-3" />
<div class="tab-list-panel text-sm text-gray-700 max-h-96 overflow-auto pr-1 pl-0 ml-0">
{{- if $model.result.Entries -}}
<ul class="flex flex-col gap-2 pl-0 pr-0 m-0 ml-0 list-none">
{{- range $entry := $model.result.Entries -}}
<li class="flex items-baseline justify-between gap-3 ml-0 pl-0">
<a href="/almanach/{{ $entry.MusenalmID }}" class="no-underline hover:text-slate-900">
{{- $entry.PreferredTitle -}}
</a>
<span class="text-xs text-gray-500">{{ $entry.Year }}</span>
</li>
{{- end -}}
</ul>
{{- else -}}
<div class="italic text-gray-500">Keine Bände verknüpft.</div>
{{- end -}}
</div>
</tab-list>
</div>
</div>
</div>
<div class="w-full flex items-end justify-between gap-4 mt-6 flex-wrap">
<p id="place-save-feedback" class="text-sm text-gray-600" aria-live="polite"></p>
<div class="flex items-center gap-3 self-end flex-wrap">
<a href="/orte/" class="resetbutton w-40 flex items-center gap-2 justify-center">
<i class="ri-close-line"></i>
<span>Abbrechen</span>
</a>
{{- if not $model.is_new -}}
<a href="/ort/{{ $place.Id }}/edit" class="resetbutton w-40 flex items-center gap-2 justify-center">
<i class="ri-loop-left-line"></i>
<span>Reset</span>
</a>
<button
type="button"
class="resetbutton w-40 flex items-center gap-2 justify-center bg-red-50 text-red-800 hover:bg-red-100 hover:text-red-900"
data-role="edit-delete">
<i class="ri-delete-bin-line"></i>
<span>Ort löschen</span>
</button>
{{- end -}}
<button type="submit" class="submitbutton w-40 flex items-center gap-2 justify-center">
<i class="ri-save-line"></i>
<span>Speichern</span>
</button>
</div>
</div>
</form>
</div>
{{- if not $model.is_new -}}
<dialog data-role="edit-delete-dialog" class="fixed inset-0 m-auto rounded-md border border-slate-200 p-0 shadow-xl backdrop:bg-black/40">
<div class="p-5 w-[26rem]">
<div class="text-base font-bold text-gray-900">Ort löschen?</div>
<div class="text-sm font-bold text-gray-900 mt-1">{{ $place.Name }}</div>
<p class="text-sm text-gray-700 mt-2">
Der Ort wird gelöscht und aus allen verknüpften Bänden entfernt.
</p>
<div class="mt-3">
<div class="text-sm font-semibold text-gray-700">Betroffene Bände</div>
<div class="mt-2 max-h-40 overflow-auto pr-1">
{{- if $model.result.Entries -}}
<ul class="flex flex-col gap-2 pl-0 pr-0 m-0 list-none">
{{- range $entry := $model.result.Entries -}}
<li class="flex items-baseline justify-between gap-3 ml-0 pl-0 text-sm text-gray-700">
<span>{{ $entry.PreferredTitle }}</span>
<span class="text-xs text-gray-500">{{ $entry.Year }}</span>
</li>
{{- end -}}
</ul>
{{- else -}}
<div class="italic text-gray-500">Keine Bände betroffen.</div>
{{- end -}}
</div>
</div>
<div class="flex items-center justify-end gap-3 mt-4">
<button type="button" class="resetbutton w-auto px-3 py-1 text-sm" data-role="edit-delete-cancel">Abbrechen</button>
<button type="button" class="submitbutton w-auto bg-red-700 hover:bg-red-800 px-3 py-1 text-sm" data-role="edit-delete-confirm">
Löschen
</button>
</div>
</div>
</dialog>
{{- end -}}
</edit-page>

View File

@@ -0,0 +1,10 @@
{{ $model := . }}
<title>
{{- if $model.is_new -}}
Neuer Ort - Musenalm
{{- else if $model.result -}}
Bearbeiten: {{ $model.result.Place.Name }} - Musenalm
{{- else -}}
Ort bearbeiten - Musenalm
{{- end -}}
</title>

View File

@@ -0,0 +1,37 @@
{{ $model := . }}
<div class="container-normal mt-6">
<div class="flex items-end justify-between gap-6 border-b border-zinc-300 pb-2">
<h1 class="text-3xl font-bold">Orte</h1>
{{- if (IsAdminOrEditor $model.request.user) -}}
<a href="/orte/new/" class="inline-flex items-center gap-2 text-sm font-bold text-gray-700 hover:text-slate-950 no-underline">
<i class="ri-add-line"></i>
<span>Neu</span>
</a>
{{- end -}}
</div>
<div class="mt-6 font-serif">
{{- if $model.result.Places -}}
<ul class="flex flex-col gap-2 pl-0 pr-0 m-0 list-none">
{{- range $place := $model.result.Places -}}
<li class="flex items-baseline justify-between gap-4">
<div class="flex flex-col">
<span class="font-bold text-slate-900">{{ $place.Name }}</span>
{{- if $place.Pseudonyms -}}
<span class="text-sm text-gray-600 italic">{{ $place.Pseudonyms }}</span>
{{- end -}}
</div>
{{- if (IsAdminOrEditor $model.request.user) -}}
<a href="/ort/{{ $place.Id }}/edit" class="text-sm font-bold text-gray-700 hover:text-slate-950 no-underline">
<i class="ri-edit-line"></i> Bearbeiten
</a>
{{- end -}}
</li>
{{- end -}}
</ul>
{{- else -}}
<p>Keine Orte gefunden.</p>
{{- end -}}
</div>
</div>

View File

@@ -0,0 +1,2 @@
<title>Musenalm &ndash; Orte</title>
<meta name="description" content="Musenalm: Orte." />

View File

@@ -379,6 +379,10 @@
@apply bg-transparent border-none text-gray-600 opacity-70 cursor-pointer ml-2 text-lg leading-none align-middle hover:opacity-100 hover:text-gray-900 disabled:opacity-40 disabled:cursor-not-allowed;
}
.mss-selected-item-edit-link {
@apply text-gray-500 hover:text-slate-900 ml-2 text-base inline-flex items-center;
}
.mss-input-controls-container {
/* Tailwind classes from component: flex items-center space-x-2 */
}

View File

@@ -5,6 +5,7 @@ const MSS_SELECTED_ITEM_PILL_CLASS = "mss-selected-item-pill";
const MSS_SELECTED_ITEM_TEXT_CLASS = "mss-selected-item-text";
const MSS_SELECTED_ITEM_PILL_DETAIL_CLASS = "mss-selected-item-pill-detail"; // New class for pill detail
const MSS_SELECTED_ITEM_DELETE_BTN_CLASS = "mss-selected-item-delete-btn";
const MSS_SELECTED_ITEM_EDIT_LINK_CLASS = "mss-selected-item-edit-link";
const MSS_INPUT_CONTROLS_CONTAINER_CLASS = "mss-input-controls-container";
const MSS_INPUT_WRAPPER_CLASS = "mss-input-wrapper";
const MSS_INPUT_WRAPPER_FOCUSED_CLASS = "mss-input-wrapper-focused";
@@ -244,6 +245,8 @@ export class MultiSelectSimple extends HTMLElement {
this._toggleLabel = this.getAttribute("data-toggle-label") || "";
this._toggleInput = this._toggleLabel !== "";
this._inputCollapsed = this._toggleInput;
this._editBase = this.getAttribute("data-edit-base") || "";
this._editSuffix = this.getAttribute("data-edit-suffix") || "/edit";
this._setupTemplates();
this._bindEventHandlers();
@@ -264,6 +267,9 @@ export class MultiSelectSimple extends HTMLElement {
<span class="${MSS_SELECTED_ITEM_PILL_CLASS} flex items-center">
<span data-ref="textEl" class="${MSS_SELECTED_ITEM_TEXT_CLASS}"></span>
<span data-ref="detailEl" class="${MSS_SELECTED_ITEM_PILL_DETAIL_CLASS} hidden"></span>
<a data-ref="editLink" class="${MSS_SELECTED_ITEM_EDIT_LINK_CLASS} hidden" aria-label="Bearbeiten">
<i class="ri-edit-line"></i>
</a>
<button type="button" data-ref="deleteBtn" class="${MSS_SELECTED_ITEM_DELETE_BTN_CLASS}">&times;</button>
</span>
`;
@@ -582,6 +588,7 @@ export class MultiSelectSimple extends HTMLElement {
const pillEl = fragment.firstElementChild;
const textEl = pillEl.querySelector('[data-ref="textEl"]');
const detailEl = pillEl.querySelector('[data-ref="detailEl"]'); // This now uses MSS_SELECTED_ITEM_PILL_DETAIL_CLASS
const editLink = pillEl.querySelector('[data-ref="editLink"]');
const deleteBtn = pillEl.querySelector('[data-ref="deleteBtn"]');
textEl.textContent = this._normalizeText(itemData.name);
const detailText = this._normalizeText(itemData.additional_data);
@@ -604,6 +611,19 @@ export class MultiSelectSimple extends HTMLElement {
pillEl.classList.add("bg-red-100");
pillEl.style.position = "relative";
}
if (editLink) {
if (this._editBase && !isRemoved) {
editLink.href = `${this._editBase}${itemId}${this._editSuffix}`;
editLink.target = "_blank";
editLink.rel = "noreferrer";
editLink.classList.remove("hidden");
} else {
editLink.classList.add("hidden");
editLink.removeAttribute("href");
editLink.removeAttribute("target");
editLink.removeAttribute("rel");
}
}
deleteBtn.setAttribute("aria-label", isRemoved ? `Undo remove ${itemData.name}` : `Remove ${itemData.name}`);
deleteBtn.dataset.id = itemId;
deleteBtn.disabled = this.hasAttribute("disabled");