mirror of
https://github.com/Theodor-Springmann-Stiftung/musenalm.git
synced 2026-02-04 10:35:30 +00:00
new places
This commit is contained in:
@@ -52,3 +52,18 @@ func nextAgentMusenalmID(app core.App) (int, error) {
|
|||||||
}
|
}
|
||||||
return agent.MusenalmID() + 1, nil
|
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
333
controllers/ort_edit.go
Normal 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
140
controllers/ort_new.go
Normal 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
52
controllers/orte.go
Normal 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
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
package dbmodels
|
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)
|
var _ core.RecordProxy = (*Place)(nil)
|
||||||
|
|
||||||
@@ -89,3 +92,7 @@ func (p *Place) Editor() string {
|
|||||||
func (p *Place) SetEditor(editor string) {
|
func (p *Place) SetEditor(editor string) {
|
||||||
p.Set(EDITOR_FIELD, editor)
|
p.Set(EDITOR_FIELD, editor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Place) Updated() types.DateTime {
|
||||||
|
return p.GetDateTime(UPDATED_FIELD)
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const (
|
|||||||
T_INDEX_TEXTE = "texte"
|
T_INDEX_TEXTE = "texte"
|
||||||
|
|
||||||
P_REIHEN_NAME = "reihen"
|
P_REIHEN_NAME = "reihen"
|
||||||
|
P_ORTE_NAME = "orte"
|
||||||
P_DANK_NAME = "danksagungen"
|
P_DANK_NAME = "danksagungen"
|
||||||
P_KONTAKT_NAME = "kontakt"
|
P_KONTAKT_NAME = "kontakt"
|
||||||
P_LIT_NAME = "literatur"
|
P_LIT_NAME = "literatur"
|
||||||
@@ -51,4 +52,6 @@ const (
|
|||||||
P_REIHE_NEW_NAME = "reihe_new"
|
P_REIHE_NEW_NAME = "reihe_new"
|
||||||
P_PERSON_EDIT_NAME = "person_edit"
|
P_PERSON_EDIT_NAME = "person_edit"
|
||||||
P_PERSON_NEW_NAME = "person_new"
|
P_PERSON_NEW_NAME = "person_new"
|
||||||
|
P_ORT_EDIT_NAME = "ort_edit"
|
||||||
|
P_ORT_NEW_NAME = "ort_new"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1221,7 +1221,7 @@ class Re extends HTMLElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
y(Re, "formAssociated", !0);
|
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 {
|
class Oe extends HTMLElement {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
@@ -1410,18 +1410,21 @@ class Oe extends HTMLElement {
|
|||||||
{ id: "yor", name: "Yoruba" },
|
{ id: "yor", name: "Yoruba" },
|
||||||
{ id: "zha", name: "Zhuang" },
|
{ id: "zha", name: "Zhuang" },
|
||||||
{ id: "zul", name: "Zulu" }
|
{ 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() {
|
_setupTemplates() {
|
||||||
this.optionTemplate = document.createElement("template"), this.optionTemplate.innerHTML = `
|
this.optionTemplate = document.createElement("template"), this.optionTemplate.innerHTML = `
|
||||||
<li role="option" class="${ft}">
|
<li role="option" class="${gt}">
|
||||||
<span data-ref="nameEl" class="${gt}"></span>
|
<span data-ref="nameEl" class="${bt}"></span>
|
||||||
<span data-ref="detailEl" class="${bt}"></span>
|
<span data-ref="detailEl" class="${Et}"></span>
|
||||||
</li>
|
</li>
|
||||||
`, this.selectedItemTemplate = document.createElement("template"), this.selectedItemTemplate.innerHTML = `
|
`, this.selectedItemTemplate = document.createElement("template"), this.selectedItemTemplate.innerHTML = `
|
||||||
<span class="${ut} flex items-center">
|
<span class="${ut} flex items-center">
|
||||||
<span data-ref="textEl" class="${mt}"></span>
|
<span data-ref="textEl" class="${mt}"></span>
|
||||||
<span data-ref="detailEl" class="${_t} hidden"></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}">×</button>
|
<button type="button" data-ref="deleteBtn" class="${me}">×</button>
|
||||||
</span>
|
</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();
|
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() {
|
_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() {
|
_render() {
|
||||||
const e = this.id || `mss-${crypto.randomUUID().slice(0, 8)}`;
|
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; }
|
.${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>
|
</style>
|
||||||
<div class="${ct} relative">
|
<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>
|
<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>` : ""}
|
${s ? `<button type="button" class="${Ee}">${i}</button>` : ""}
|
||||||
<div class="${_e} flex items-center gap-2 ${n}">
|
<div class="${_e} flex items-center gap-2 ${n}">
|
||||||
@@ -1582,16 +1585,16 @@ class Oe extends HTMLElement {
|
|||||||
_createSelectedItemElement(e) {
|
_createSelectedItemElement(e) {
|
||||||
const i = this._getItemById(e);
|
const i = this._getItemById(e);
|
||||||
if (!i) return null;
|
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);
|
a.textContent = this._normalizeText(i.name);
|
||||||
const d = this._normalizeText(i.additional_data);
|
const c = this._normalizeText(i.additional_data);
|
||||||
d ? (r.textContent = `(${d})`, r.classList.remove("hidden")) : (r.textContent = "", r.classList.add("hidden"));
|
c ? (r.textContent = `(${c})`, r.classList.remove("hidden")) : (r.textContent = "", r.classList.add("hidden"));
|
||||||
const c = this._removedIds.has(e);
|
const h = this._removedIds.has(e);
|
||||||
if (!this._initialValue.includes(e)) {
|
if (!this._initialValue.includes(e)) {
|
||||||
const u = document.createElement("span");
|
const u = document.createElement("span");
|
||||||
u.className = "ml-1 text-xs text-gray-600", u.textContent = "(Neu)", a.appendChild(u);
|
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>' : "×", 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>' : "×", d.addEventListener("click", (u) => {
|
||||||
u.stopPropagation(), this._handleDeleteSelectedItem(e);
|
u.stopPropagation(), this._handleDeleteSelectedItem(e);
|
||||||
}), n;
|
}), n;
|
||||||
}
|
}
|
||||||
@@ -1754,7 +1757,7 @@ class Oe extends HTMLElement {
|
|||||||
}
|
}
|
||||||
this._remoteFetchTimeout = setTimeout(() => {
|
this._remoteFetchTimeout = setTimeout(() => {
|
||||||
this._fetchRemoteOptions(e);
|
this._fetchRemoteOptions(e);
|
||||||
}, Et);
|
}, St);
|
||||||
}
|
}
|
||||||
_cancelRemoteFetch() {
|
_cancelRemoteFetch() {
|
||||||
this._remoteFetchController && (this._remoteFetchController.abort(), this._remoteFetchController = null);
|
this._remoteFetchController && (this._remoteFetchController.abort(), this._remoteFetchController = null);
|
||||||
@@ -1819,8 +1822,8 @@ class Oe extends HTMLElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
y(Oe, "formAssociated", !0);
|
y(Oe, "formAssociated", !0);
|
||||||
const yt = "rbi-button", At = "rbi-icon";
|
const At = "rbi-button", It = "rbi-icon";
|
||||||
class It extends HTMLElement {
|
class Ct extends HTMLElement {
|
||||||
constructor() {
|
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);
|
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() {
|
connectedCallback() {
|
||||||
const t = `
|
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">
|
<tool-tip position="right">
|
||||||
<div class="data-tip">Feld zurücksetzen</div>
|
<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>
|
</tool-tip>
|
||||||
</button>
|
</button>
|
||||||
`;
|
`;
|
||||||
@@ -1976,19 +1979,19 @@ class It extends HTMLElement {
|
|||||||
this.button.setAttribute("aria-label", t);
|
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;
|
var D, Be;
|
||||||
class xt extends HTMLElement {
|
class kt extends HTMLElement {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
A(this, D);
|
A(this, D);
|
||||||
R(this, D, Be).call(this), this.boundHandleClickOutside = this.handleClickOutside.bind(this);
|
R(this, D, Be).call(this), this.boundHandleClickOutside = this.handleClickOutside.bind(this);
|
||||||
}
|
}
|
||||||
connectedCallback() {
|
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,
|
node: e,
|
||||||
target: () => {
|
target: () => {
|
||||||
const i = e.getAttribute(Tt);
|
const i = e.getAttribute(wt);
|
||||||
return i ? document.getElementById(i) || this._target : this._target;
|
return i ? document.getElementById(i) || this._target : this._target;
|
||||||
},
|
},
|
||||||
stay: () => e.hasAttribute(ye) && e.getAttribute(ye) == "true",
|
stay: () => e.hasAttribute(ye) && e.getAttribute(ye) == "true",
|
||||||
@@ -2010,7 +2013,7 @@ class xt extends HTMLElement {
|
|||||||
this.removeChild(e.node);
|
this.removeChild(e.node);
|
||||||
this._button.addEventListener("click", this._toggleMenu.bind(this)), this._button.classList.add("relative");
|
this._button.addEventListener("click", this._toggleMenu.bind(this)), this._button.classList.add("relative");
|
||||||
for (const e of this._cildren)
|
for (const e of this._cildren)
|
||||||
e.node.querySelectorAll(`.${wt}`).forEach((s) => {
|
e.node.querySelectorAll(`.${xt}`).forEach((s) => {
|
||||||
s.addEventListener("click", (n) => {
|
s.addEventListener("click", (n) => {
|
||||||
this.hideDiv(n, e.node);
|
this.hideDiv(n, e.node);
|
||||||
});
|
});
|
||||||
@@ -2132,13 +2135,13 @@ ${e[0].nameText()} hinzufügen`, this._menu = null, this.hideMenu();
|
|||||||
D = new WeakSet(), Be = function() {
|
D = new WeakSet(), Be = function() {
|
||||||
this._cildren = [], this._rendered = [], this._target = null, this._button = null, this._menu = null, this._originalButtonText = null;
|
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";
|
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 Pt extends HTMLElement {
|
class qt extends HTMLElement {
|
||||||
constructor() {
|
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);
|
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() {
|
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.");
|
console.error("ItemsEditor: Missing list, template, or add button.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2188,7 +2191,7 @@ class Pt extends HTMLElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
_wireCancelButtons(t = this) {
|
_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) => {
|
e.dataset.itemsBound !== "true" && (e.dataset.itemsBound = "true", e.addEventListener("click", (i) => {
|
||||||
i.preventDefault();
|
i.preventDefault();
|
||||||
const s = e.closest(`.${f}`);
|
const s = e.closest(`.${f}`);
|
||||||
@@ -2197,13 +2200,13 @@ class Pt extends HTMLElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
_wireEditButtons(t = this) {
|
_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) => {
|
e.dataset.itemsBound !== "true" && (e.dataset.itemsBound = "true", e.addEventListener("click", (i) => {
|
||||||
i.preventDefault();
|
i.preventDefault();
|
||||||
const s = e.closest(`.${f}`);
|
const s = e.closest(`.${f}`);
|
||||||
s && this._setRowMode(s, "edit");
|
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) => {
|
e.dataset.itemsBound !== "true" && (e.dataset.itemsBound = "true", e.addEventListener("click", (i) => {
|
||||||
i.preventDefault();
|
i.preventDefault();
|
||||||
const s = e.closest(`.${f}`);
|
const s = e.closest(`.${f}`);
|
||||||
@@ -2236,7 +2239,7 @@ class Pt extends HTMLElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
_setRowMode(t, e) {
|
_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)));
|
!i || !s || (e === "edit" ? (i.classList.add("hidden"), s.classList.remove("hidden")) : (i.classList.remove("hidden"), s.classList.add("hidden"), this._syncSummary(t)));
|
||||||
}
|
}
|
||||||
_captureAllOriginals() {
|
_captureAllOriginals() {
|
||||||
@@ -2329,8 +2332,8 @@ class Pt extends HTMLElement {
|
|||||||
i.value === t && i.remove();
|
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;
|
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 Kt extends HTMLElement {
|
class Wt extends HTMLElement {
|
||||||
constructor() {
|
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);
|
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) {
|
_debouncedFetch(t) {
|
||||||
this._fetchTimeout && clearTimeout(this._fetchTimeout), this._fetchTimeout = setTimeout(() => {
|
this._fetchTimeout && clearTimeout(this._fetchTimeout), this._fetchTimeout = setTimeout(() => {
|
||||||
this._fetchOptions(t);
|
this._fetchOptions(t);
|
||||||
}, zt);
|
}, Kt);
|
||||||
}
|
}
|
||||||
async _fetchOptions(t) {
|
async _fetchOptions(t) {
|
||||||
if (!this._endpoint)
|
if (!this._endpoint)
|
||||||
@@ -2423,19 +2426,19 @@ class Kt extends HTMLElement {
|
|||||||
this._list && (this._list.innerHTML = "", this._options.forEach((t) => {
|
this._list && (this._list.innerHTML = "", this._options.forEach((t) => {
|
||||||
const e = document.createElement("button");
|
const e = document.createElement("button");
|
||||||
e.type = "button", e.setAttribute("data-index", String(this._options.indexOf(t))), e.className = [
|
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"
|
"w-full text-left px-3 py-2 hover:bg-slate-100 transition-colors"
|
||||||
].join(" ");
|
].join(" ");
|
||||||
const s = this._options.indexOf(t) === this._highlightedIndex;
|
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");
|
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");
|
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");
|
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) {
|
if (t.bio) {
|
||||||
const a = document.createElement("div");
|
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", () => {
|
e.addEventListener("click", () => {
|
||||||
this._selectOption(t);
|
this._selectOption(t);
|
||||||
@@ -2484,7 +2487,7 @@ class Kt extends HTMLElement {
|
|||||||
_render() {
|
_render() {
|
||||||
const t = this.getAttribute("name") || "";
|
const t = this.getAttribute("name") || "";
|
||||||
this.innerHTML = `
|
this.innerHTML = `
|
||||||
<div class="${qt} relative">
|
<div class="${Ht} relative">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -2505,8 +2508,8 @@ class Kt extends HTMLElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const Wt = "Bevorzugter Reihentitel";
|
const Gt = "Bevorzugter Reihentitel";
|
||||||
class Gt extends HTMLElement {
|
class jt extends HTMLElement {
|
||||||
constructor() {
|
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);
|
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, {
|
} = this._collectRelations(t, {
|
||||||
prefix: "entries_series",
|
prefix: "entries_series",
|
||||||
targetField: "series"
|
targetField: "series"
|
||||||
}), h = this._collectNewRelations("entries_series"), u = [...d, ...h].filter(
|
}), h = this._collectNewRelations("entries_series"), m = [...d, ...h].filter(
|
||||||
(g) => g.type === Wt
|
(g) => g.type === Gt
|
||||||
).length;
|
).length;
|
||||||
if (u === 0)
|
if (m === 0)
|
||||||
throw new Error("Mindestens ein bevorzugter Reihentitel muss verknüpft sein.");
|
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.");
|
throw new Error("Es darf nur ein bevorzugter Reihentitel gesetzt sein.");
|
||||||
const {
|
const {
|
||||||
relations: m,
|
relations: u,
|
||||||
deleted: _
|
deleted: _
|
||||||
} = this._collectRelations(t, {
|
} = this._collectRelations(t, {
|
||||||
prefix: "entries_agents",
|
prefix: "entries_agents",
|
||||||
@@ -2717,7 +2720,7 @@ class Gt extends HTMLElement {
|
|||||||
series_relations: d,
|
series_relations: d,
|
||||||
new_series_relations: h,
|
new_series_relations: h,
|
||||||
deleted_series_relation_ids: c,
|
deleted_series_relation_ids: c,
|
||||||
agent_relations: m,
|
agent_relations: u,
|
||||||
new_agent_relations: S,
|
new_agent_relations: S,
|
||||||
deleted_agent_relation_ids: _
|
deleted_agent_relation_ids: _
|
||||||
};
|
};
|
||||||
@@ -2727,13 +2730,13 @@ class Gt extends HTMLElement {
|
|||||||
t.getAll("items_removed[]").map((h) => h.trim()).filter(Boolean)
|
t.getAll("items_removed[]").map((h) => h.trim()).filter(Boolean)
|
||||||
), c = [];
|
), c = [];
|
||||||
for (let h = 0; h < e.length; h += 1) {
|
for (let h = 0; h < e.length; h += 1) {
|
||||||
const u = e[h] || "";
|
const m = e[h] || "";
|
||||||
if (u && d.has(u))
|
if (m && d.has(m))
|
||||||
continue;
|
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();
|
const u = (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({
|
(m || u || _ || S || P || L || x) && c.push({
|
||||||
id: u,
|
id: m,
|
||||||
owner: m,
|
owner: u,
|
||||||
identifier: _,
|
identifier: _,
|
||||||
location: S,
|
location: S,
|
||||||
annotation: P,
|
annotation: P,
|
||||||
@@ -2751,19 +2754,19 @@ class Gt extends HTMLElement {
|
|||||||
for (const [a, r] of t.entries()) {
|
for (const [a, r] of t.entries()) {
|
||||||
if (!a.startsWith(`${e}_id[`))
|
if (!a.startsWith(`${e}_id[`))
|
||||||
continue;
|
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();
|
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 (!_ || !m)
|
if (!_ || !u)
|
||||||
continue;
|
continue;
|
||||||
if (t.has(h)) {
|
if (t.has(h)) {
|
||||||
n.push(m);
|
n.push(u);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const S = (t.get(c) || "").trim();
|
const S = (t.get(c) || "").trim();
|
||||||
s.push({
|
s.push({
|
||||||
id: m,
|
id: u,
|
||||||
target_id: _,
|
target_id: _,
|
||||||
type: S,
|
type: S,
|
||||||
uncertain: t.has(u)
|
uncertain: t.has(m)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return { relations: s, deleted: n };
|
return { relations: s, deleted: n };
|
||||||
@@ -2819,16 +2822,16 @@ class Gt extends HTMLElement {
|
|||||||
o.replaceWith(r), this._form = r;
|
o.replaceWith(r), this._form = r;
|
||||||
const d = a.querySelector("#user-message"), c = this.querySelector("#user-message");
|
const d = a.querySelector("#user-message"), c = this.querySelector("#user-message");
|
||||||
d && c && c.replaceWith(d);
|
d && c && c.replaceWith(d);
|
||||||
const h = a.querySelector("#almanach-header-data"), u = this.querySelector("#almanach-header-data");
|
const h = a.querySelector("#almanach-header-data"), m = this.querySelector("#almanach-header-data");
|
||||||
h && u && u.replaceWith(h), this._initForm(), this._initPlaces(), this._initSaveHandling(), typeof window.TextareaAutoResize == "function" && setTimeout(() => {
|
h && m && m.replaceWith(h), this._initForm(), this._initPlaces(), this._initSaveHandling(), typeof window.TextareaAutoResize == "function" && setTimeout(() => {
|
||||||
this.querySelectorAll("textarea").forEach((m) => {
|
this.querySelectorAll("textarea").forEach((u) => {
|
||||||
window.TextareaAutoResize(m);
|
window.TextareaAutoResize(u);
|
||||||
});
|
});
|
||||||
}, 100);
|
}, 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]";
|
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 ai extends HTMLElement {
|
class li extends HTMLElement {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(), this._pendingItem = null, this._pendingApply = !1;
|
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");
|
this._addPanel && !this._addPanel.classList.contains("hidden") || e || i ? this._emptyText.classList.add("hidden") : this._emptyText.classList.remove("hidden");
|
||||||
}
|
}
|
||||||
_setupAddPanel() {
|
_setupAddPanel() {
|
||||||
if (this._addToggle = this.querySelector(jt), this._addToggleId) {
|
if (this._addToggle = this.querySelector(Jt), this._addToggleId) {
|
||||||
const t = document.getElementById(this._addToggleId);
|
const t = document.getElementById(this._addToggleId);
|
||||||
t && (this._addToggle = t);
|
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._addSelect._excludeIds = Array.from(this._getExistingIds());
|
||||||
}), this._addToggle && this._addToggle.addEventListener("click", () => {
|
}), this._addToggle && this._addToggle.addEventListener("click", () => {
|
||||||
this._addPanel.classList.toggle("hidden"), this._updateEmptyTextVisibility();
|
this._addPanel.classList.toggle("hidden"), this._updateEmptyTextVisibility();
|
||||||
@@ -2907,15 +2910,15 @@ class ai extends HTMLElement {
|
|||||||
const c = t.querySelector("[data-rel-input='uncertain']");
|
const c = t.querySelector("[data-rel-input='uncertain']");
|
||||||
if (c && this._uncertain) {
|
if (c && this._uncertain) {
|
||||||
c.checked = this._uncertain.checked, c.name = `${this._prefix}_new_uncertain`;
|
c.checked = this._uncertain.checked, c.name = `${this._prefix}_new_uncertain`;
|
||||||
const m = `${this._prefix}_new_uncertain_row`;
|
const u = `${this._prefix}_new_uncertain_row`;
|
||||||
c.id = m;
|
c.id = u;
|
||||||
const _ = t.querySelector("[data-rel-uncertain-label]");
|
const _ = t.querySelector("[data-rel-uncertain-label]");
|
||||||
_ && _.setAttribute("for", m);
|
_ && _.setAttribute("for", u);
|
||||||
}
|
}
|
||||||
const h = t.querySelector("[data-rel-input='id']");
|
const h = t.querySelector("[data-rel-input='id']");
|
||||||
h && (h.name = `${this._prefix}_new_id`, h.value = this._pendingItem.id);
|
h && (h.name = `${this._prefix}_new_id`, h.value = this._pendingItem.id);
|
||||||
const u = t.querySelector(ni);
|
const m = t.querySelector(ai);
|
||||||
u && u.addEventListener("click", () => {
|
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._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();
|
}), 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 }) => {
|
e.forEach(({ select: s, row: n, isAddPanel: a }) => {
|
||||||
if (!s)
|
if (!s)
|
||||||
return;
|
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)
|
if (!r)
|
||||||
return;
|
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;
|
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) {
|
if (a && i && c === t) {
|
||||||
const m = Array.from(s.options).find((_) => _.value.trim() !== t);
|
const u = Array.from(s.options).find((_) => _.value.trim() !== t);
|
||||||
m && (s.value = m.value);
|
u && (s.value = u.value);
|
||||||
}
|
}
|
||||||
const u = !h || a && i;
|
const m = !h || a && i;
|
||||||
r.hidden = u, r.disabled = u, r.style.display = u ? "none" : "";
|
r.hidden = m, r.disabled = m, r.style.display = m ? "none" : "";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
class li extends HTMLElement {
|
class ri extends HTMLElement {
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const t = this.querySelector("form");
|
const t = this.querySelector("form");
|
||||||
@@ -3034,35 +3037,35 @@ class li extends HTMLElement {
|
|||||||
});
|
});
|
||||||
if (!h.ok)
|
if (!h.ok)
|
||||||
return;
|
return;
|
||||||
const u = await h.json().catch(() => null), m = (u == null ? void 0 : u.redirect) || "/";
|
const m = await h.json().catch(() => null), u = (m == null ? void 0 : m.redirect) || "/";
|
||||||
window.location.assign(m);
|
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";
|
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(ci, je);
|
customElements.define(ui, je);
|
||||||
customElements.define(hi, T);
|
customElements.define(ci, T);
|
||||||
customElements.define(ri, Ue);
|
customElements.define(oi, Ue);
|
||||||
customElements.define(oi, ze);
|
customElements.define(di, ze);
|
||||||
customElements.define(di, Ke);
|
customElements.define(hi, Ke);
|
||||||
customElements.define(ui, We);
|
customElements.define(mi, We);
|
||||||
customElements.define(mi, Ge);
|
customElements.define(_i, Ge);
|
||||||
customElements.define(_i, He);
|
customElements.define(pi, He);
|
||||||
customElements.define(pi, Je);
|
customElements.define(fi, Je);
|
||||||
customElements.define(fi, Re);
|
customElements.define(gi, Re);
|
||||||
customElements.define(gi, Oe);
|
customElements.define(bi, Oe);
|
||||||
customElements.define(bi, Kt);
|
customElements.define(Ei, Wt);
|
||||||
customElements.define(Me, It);
|
customElements.define(Me, Ct);
|
||||||
customElements.define(Ei, xt);
|
customElements.define(Si, kt);
|
||||||
customElements.define(Si, Pt);
|
customElements.define(vi, qt);
|
||||||
customElements.define(vi, Gt);
|
customElements.define(Li, jt);
|
||||||
customElements.define(Li, ai);
|
|
||||||
customElements.define(yi, li);
|
customElements.define(yi, li);
|
||||||
function Ai() {
|
customElements.define(Ai, ri);
|
||||||
|
function Ii() {
|
||||||
const l = window.location.pathname, t = window.location.search, e = l + t;
|
const l = window.location.pathname, t = window.location.search, e = l + t;
|
||||||
return encodeURIComponent(e);
|
return encodeURIComponent(e);
|
||||||
}
|
}
|
||||||
function Ii(l = 5e3, t = 100) {
|
function Ci(l = 5e3, t = 100) {
|
||||||
return new Promise((e, i) => {
|
return new Promise((e, i) => {
|
||||||
let s = 0;
|
let s = 0;
|
||||||
const n = setInterval(() => {
|
const n = setInterval(() => {
|
||||||
@@ -3070,8 +3073,8 @@ function Ii(l = 5e3, t = 100) {
|
|||||||
}, t);
|
}, t);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async function Ci(l) {
|
async function Ti(l) {
|
||||||
const t = await Ii(), e = document.getElementById("qr");
|
const t = await Ci(), e = document.getElementById("qr");
|
||||||
e && (e.innerHTML = "", e.classList.add("hidden"), new t(e, {
|
e && (e.innerHTML = "", e.classList.add("hidden"), new t(e, {
|
||||||
text: l,
|
text: l,
|
||||||
width: 1280,
|
width: 1280,
|
||||||
@@ -3083,7 +3086,7 @@ async function Ci(l) {
|
|||||||
e.classList.remove("hidden");
|
e.classList.remove("hidden");
|
||||||
}, 20));
|
}, 20));
|
||||||
}
|
}
|
||||||
function Ti(l) {
|
function wi(l) {
|
||||||
l && (l.addEventListener("focus", (t) => {
|
l && (l.addEventListener("focus", (t) => {
|
||||||
t.preventDefault(), l.select();
|
t.preventDefault(), l.select();
|
||||||
}), l.addEventListener("mousedown", (t) => {
|
}), l.addEventListener("mousedown", (t) => {
|
||||||
@@ -3096,7 +3099,7 @@ function Ti(l) {
|
|||||||
l.select();
|
l.select();
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
function wi() {
|
function xi() {
|
||||||
document.body.addEventListener("htmx:responseError", function(l) {
|
document.body.addEventListener("htmx:responseError", function(l) {
|
||||||
const t = l.detail.requestConfig;
|
const t = l.detail.requestConfig;
|
||||||
if (t.boosted) {
|
if (t.boosted) {
|
||||||
@@ -3106,7 +3109,7 @@ function wi() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function xi(l, t) {
|
function ki(l, t) {
|
||||||
if (!(l instanceof HTMLElement)) {
|
if (!(l instanceof HTMLElement)) {
|
||||||
console.warn("Target must be an HTMLElement.");
|
console.warn("Target must be an HTMLElement.");
|
||||||
return;
|
return;
|
||||||
@@ -3151,7 +3154,7 @@ function E(l) {
|
|||||||
function Ne(l) {
|
function Ne(l) {
|
||||||
l.key === "Enter" && l.preventDefault();
|
l.key === "Enter" && l.preventDefault();
|
||||||
}
|
}
|
||||||
function ki(l) {
|
function Ri(l) {
|
||||||
if (!(l instanceof HTMLTextAreaElement)) {
|
if (!(l instanceof HTMLTextAreaElement)) {
|
||||||
console.warn("HookupTextareaAutoResize: Provided element is not a textarea.");
|
console.warn("HookupTextareaAutoResize: Provided element is not a textarea.");
|
||||||
return;
|
return;
|
||||||
@@ -3160,7 +3163,7 @@ function ki(l) {
|
|||||||
E(l);
|
E(l);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function Ri(l) {
|
function Oi(l) {
|
||||||
if (!(l instanceof HTMLTextAreaElement)) {
|
if (!(l instanceof HTMLTextAreaElement)) {
|
||||||
console.warn("DisconnectTextareaAutoResize: Provided element is not a textarea.");
|
console.warn("DisconnectTextareaAutoResize: Provided element is not a textarea.");
|
||||||
return;
|
return;
|
||||||
@@ -3169,23 +3172,23 @@ function Ri(l) {
|
|||||||
E(l);
|
E(l);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function Oi(l) {
|
function Bi(l) {
|
||||||
!(l instanceof HTMLTextAreaElement) && l.classList.contains("no-enter") || l.addEventListener("keydown", Ne);
|
!(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);
|
!(l instanceof HTMLTextAreaElement) && l.classList.contains("no-enter") || l.removeEventListener("keydown", Ne);
|
||||||
}
|
}
|
||||||
function Mi(l, t) {
|
function $i(l, t) {
|
||||||
const e = !$e();
|
const e = !$e();
|
||||||
for (const i of l)
|
for (const i of l)
|
||||||
if (i.type === "childList") {
|
if (i.type === "childList") {
|
||||||
for (const s of i.addedNodes)
|
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)
|
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)) {
|
if (console.log("=== FormLoad CALLED ==="), !(l instanceof HTMLFormElement)) {
|
||||||
console.warn("FormLoad: Provided element is not a form.");
|
console.warn("FormLoad: Provided element is not a form.");
|
||||||
return;
|
return;
|
||||||
@@ -3203,8 +3206,8 @@ function $i(l) {
|
|||||||
}, 200);
|
}, 200);
|
||||||
const e = document.querySelectorAll("textarea.no-enter");
|
const e = document.querySelectorAll("textarea.no-enter");
|
||||||
for (const n of e)
|
for (const n of e)
|
||||||
Oi(n);
|
Bi(n);
|
||||||
new MutationObserver(Mi).observe(l, {
|
new MutationObserver($i).observe(l, {
|
||||||
childList: !0,
|
childList: !0,
|
||||||
subtree: !0
|
subtree: !0
|
||||||
}), new MutationObserver((n) => {
|
}), new MutationObserver((n) => {
|
||||||
@@ -3229,28 +3232,28 @@ document.addEventListener("keydown", (l) => {
|
|||||||
const t = l.target;
|
const t = l.target;
|
||||||
t instanceof HTMLElement && t.matches("textarea.no-enter") && l.preventDefault();
|
t instanceof HTMLElement && t.matches("textarea.no-enter") && l.preventDefault();
|
||||||
});
|
});
|
||||||
window.ShowBoostedErrors = wi;
|
window.ShowBoostedErrors = xi;
|
||||||
window.GenQRCode = Ci;
|
window.GenQRCode = Ti;
|
||||||
window.SelectableInput = Ti;
|
window.SelectableInput = wi;
|
||||||
window.PathPlusQuery = Ai;
|
window.PathPlusQuery = Ii;
|
||||||
window.HookupRBChange = xi;
|
window.HookupRBChange = ki;
|
||||||
window.FormLoad = $i;
|
window.FormLoad = Ni;
|
||||||
window.TextareaAutoResize = E;
|
window.TextareaAutoResize = E;
|
||||||
export {
|
export {
|
||||||
T as AbbreviationTooltips,
|
T as AbbreviationTooltips,
|
||||||
Gt as AlmanachEditPage,
|
jt as AlmanachEditPage,
|
||||||
li as EditPage,
|
ri as EditPage,
|
||||||
Ue as FilterList,
|
Ue as FilterList,
|
||||||
He as FilterPill,
|
He as FilterPill,
|
||||||
Je as ImageReel,
|
Je as ImageReel,
|
||||||
je as IntLink,
|
je as IntLink,
|
||||||
Pt as ItemsEditor,
|
qt as ItemsEditor,
|
||||||
Re as MultiSelectRole,
|
Re as MultiSelectRole,
|
||||||
Oe as MultiSelectSimple,
|
Oe as MultiSelectSimple,
|
||||||
We as PopupImage,
|
We as PopupImage,
|
||||||
ai as RelationsEditor,
|
li as RelationsEditor,
|
||||||
ze as ScrollButton,
|
ze as ScrollButton,
|
||||||
Kt as SingleSelectRemote,
|
Wt as SingleSelectRemote,
|
||||||
Ge as TabList,
|
Ge as TabList,
|
||||||
Ke as ToolTip
|
Ke as ToolTip
|
||||||
};
|
};
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -289,10 +289,10 @@ type AlmanachResult struct {
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<button type="button" id="series-add-toggle" class="text-gray-700 hover:text-gray-900">
|
<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>
|
||||||
<button type="button" id="agents-add-toggle" class="text-gray-700 hover:text-gray-900">
|
<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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -300,7 +300,12 @@ type AlmanachResult struct {
|
|||||||
<div class="mt-3">
|
<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">
|
<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">
|
<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">
|
<div id="series-section" class="rel-section-container">
|
||||||
{{- if $model.result.Series -}}
|
{{- if $model.result.Series -}}
|
||||||
{{- range $i, $s := $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 data-rel-row class="entries-series-row rel-row">
|
||||||
<div class="rel-grid">
|
<div class="rel-grid">
|
||||||
<div data-rel-strike class="relation-strike rel-name-col">
|
<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>
|
<span data-rel-name>{{- $s.Title -}}</span>
|
||||||
</a>
|
</a>
|
||||||
{{- if $s.Pseudonyms -}}
|
{{- if $s.Pseudonyms -}}
|
||||||
@@ -429,7 +434,12 @@ type AlmanachResult struct {
|
|||||||
<div class="mt-3">
|
<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">
|
<relations-editor data-prefix="entries_agents" data-link-base="/person/" data-new-label="(Neu)" data-add-toggle-id="agents-add-toggle">
|
||||||
<div class="inputwrapper">
|
<div class="inputwrapper">
|
||||||
<label class="inputlabel" for="agents-section">Personen & Körperschaften</label>
|
<div class="flex items-center justify-between">
|
||||||
|
<label class="inputlabel" for="agents-section">Personen & 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">
|
<div id="agents-section" class="rel-section-container">
|
||||||
{{- if $model.result.EntriesAgents -}}
|
{{- if $model.result.EntriesAgents -}}
|
||||||
{{- range $i, $r := $model.result.EntriesAgents -}}
|
{{- range $i, $r := $model.result.EntriesAgents -}}
|
||||||
@@ -438,7 +448,7 @@ type AlmanachResult struct {
|
|||||||
<div class="rel-grid">
|
<div class="rel-grid">
|
||||||
<div data-rel-strike class="relation-strike rel-name-col">
|
<div data-rel-strike class="relation-strike rel-name-col">
|
||||||
{{- if $a -}}
|
{{- 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>
|
<span data-rel-name>{{- $a.Name -}}</span>
|
||||||
</a>
|
</a>
|
||||||
{{- if $a.BiographicalData -}}
|
{{- if $a.BiographicalData -}}
|
||||||
@@ -559,13 +569,20 @@ type AlmanachResult struct {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col gap-4 mt-4">
|
<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>
|
<label for="places" class="inputlabel">Erscheinungs- und Verlagsorte</label>
|
||||||
<multi-select-simple
|
<a href="/orte/new/" class="text-sm font-bold text-gray-700 hover:text-slate-950 no-underline pr-3" target="_blank" rel="noreferrer">
|
||||||
id="places"
|
<i class="ri-add-line"></i> Neuen Ort anlegen
|
||||||
name="places[]"
|
</a>
|
||||||
value='[{{- range $i, $place := $model.result.Places -}}{{- if $i }},{{ end -}}"{{ $place.Id }}"{{- end -}}]'
|
</div>
|
||||||
placeholder="Orte suchen..."
|
<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-toggle-label='<i class="ri-add-circle-line"></i>'
|
||||||
data-empty-text="Keine Orte ausgewählt..."
|
data-empty-text="Keine Orte ausgewählt..."
|
||||||
show-create-button="false"
|
show-create-button="false"
|
||||||
|
|||||||
248
views/routes/ort/edit/body.gohtml
Normal file
248
views/routes/ort/edit/body.gohtml
Normal 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>
|
||||||
|
·
|
||||||
|
<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>
|
||||||
10
views/routes/ort/edit/head.gohtml
Normal file
10
views/routes/ort/edit/head.gohtml
Normal 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>
|
||||||
37
views/routes/orte/body.gohtml
Normal file
37
views/routes/orte/body.gohtml
Normal 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>
|
||||||
2
views/routes/orte/head.gohtml
Normal file
2
views/routes/orte/head.gohtml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
<title>Musenalm – Orte</title>
|
||||||
|
<meta name="description" content="Musenalm: Orte." />
|
||||||
@@ -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;
|
@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 {
|
.mss-input-controls-container {
|
||||||
/* Tailwind classes from component: flex items-center space-x-2 */
|
/* Tailwind classes from component: flex items-center space-x-2 */
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_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_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_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_CONTROLS_CONTAINER_CLASS = "mss-input-controls-container";
|
||||||
const MSS_INPUT_WRAPPER_CLASS = "mss-input-wrapper";
|
const MSS_INPUT_WRAPPER_CLASS = "mss-input-wrapper";
|
||||||
const MSS_INPUT_WRAPPER_FOCUSED_CLASS = "mss-input-wrapper-focused";
|
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._toggleLabel = this.getAttribute("data-toggle-label") || "";
|
||||||
this._toggleInput = this._toggleLabel !== "";
|
this._toggleInput = this._toggleLabel !== "";
|
||||||
this._inputCollapsed = this._toggleInput;
|
this._inputCollapsed = this._toggleInput;
|
||||||
|
this._editBase = this.getAttribute("data-edit-base") || "";
|
||||||
|
this._editSuffix = this.getAttribute("data-edit-suffix") || "/edit";
|
||||||
|
|
||||||
this._setupTemplates();
|
this._setupTemplates();
|
||||||
this._bindEventHandlers();
|
this._bindEventHandlers();
|
||||||
@@ -264,6 +267,9 @@ export class MultiSelectSimple extends HTMLElement {
|
|||||||
<span class="${MSS_SELECTED_ITEM_PILL_CLASS} flex items-center">
|
<span class="${MSS_SELECTED_ITEM_PILL_CLASS} flex items-center">
|
||||||
<span data-ref="textEl" class="${MSS_SELECTED_ITEM_TEXT_CLASS}"></span>
|
<span data-ref="textEl" class="${MSS_SELECTED_ITEM_TEXT_CLASS}"></span>
|
||||||
<span data-ref="detailEl" class="${MSS_SELECTED_ITEM_PILL_DETAIL_CLASS} hidden"></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}">×</button>
|
<button type="button" data-ref="deleteBtn" class="${MSS_SELECTED_ITEM_DELETE_BTN_CLASS}">×</button>
|
||||||
</span>
|
</span>
|
||||||
`;
|
`;
|
||||||
@@ -582,6 +588,7 @@ export class MultiSelectSimple extends HTMLElement {
|
|||||||
const pillEl = fragment.firstElementChild;
|
const pillEl = fragment.firstElementChild;
|
||||||
const textEl = pillEl.querySelector('[data-ref="textEl"]');
|
const textEl = pillEl.querySelector('[data-ref="textEl"]');
|
||||||
const detailEl = pillEl.querySelector('[data-ref="detailEl"]'); // This now uses MSS_SELECTED_ITEM_PILL_DETAIL_CLASS
|
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"]');
|
const deleteBtn = pillEl.querySelector('[data-ref="deleteBtn"]');
|
||||||
textEl.textContent = this._normalizeText(itemData.name);
|
textEl.textContent = this._normalizeText(itemData.name);
|
||||||
const detailText = this._normalizeText(itemData.additional_data);
|
const detailText = this._normalizeText(itemData.additional_data);
|
||||||
@@ -604,6 +611,19 @@ export class MultiSelectSimple extends HTMLElement {
|
|||||||
pillEl.classList.add("bg-red-100");
|
pillEl.classList.add("bg-red-100");
|
||||||
pillEl.style.position = "relative";
|
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.setAttribute("aria-label", isRemoved ? `Undo remove ${itemData.name}` : `Remove ${itemData.name}`);
|
||||||
deleteBtn.dataset.id = itemId;
|
deleteBtn.dataset.id = itemId;
|
||||||
deleteBtn.disabled = this.hasAttribute("disabled");
|
deleteBtn.disabled = this.hasAttribute("disabled");
|
||||||
|
|||||||
Reference in New Issue
Block a user