mirror of
https://github.com/Theodor-Springmann-Stiftung/musenalm.git
synced 2026-02-04 10:35:30 +00:00
Almanach list
This commit is contained in:
@@ -49,6 +49,13 @@ func (p *PersonEditPage) Setup(router *router.Router[*core.RequestEvent], app co
|
|||||||
type PersonEditResult struct {
|
type PersonEditResult struct {
|
||||||
Agent *dbmodels.Agent
|
Agent *dbmodels.Agent
|
||||||
User *dbmodels.User
|
User *dbmodels.User
|
||||||
|
Prev *dbmodels.Agent
|
||||||
|
Next *dbmodels.Agent
|
||||||
|
Entries []*dbmodels.Entry
|
||||||
|
EntryTypes map[string][]string
|
||||||
|
Contents []*dbmodels.Content
|
||||||
|
ContentEntries map[string]*dbmodels.Entry
|
||||||
|
ContentTypes map[string][]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPersonEditResult(app core.App, id string) (*PersonEditResult, error) {
|
func NewPersonEditResult(app core.App, id string) (*PersonEditResult, error) {
|
||||||
@@ -67,9 +74,37 @@ func NewPersonEditResult(app core.App, id string) (*PersonEditResult, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prev, next, err := agentNeighbors(app, agent.Id)
|
||||||
|
if err != nil {
|
||||||
|
app.Logger().Error("Failed to load agent neighbors", "agent", agent.Id, "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
entries, entryTypes, err := agentEntries(app, agent.Id)
|
||||||
|
if err != nil {
|
||||||
|
app.Logger().Error("Failed to load agent entries", "agent", agent.Id, "error", err)
|
||||||
|
}
|
||||||
|
if len(entries) > 0 {
|
||||||
|
dbmodels.Sort_Entries_Year_Title(entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
contents, contentEntries, contentTypes, err := agentContentsDetails(app, agent.Id)
|
||||||
|
if err != nil {
|
||||||
|
app.Logger().Error("Failed to load agent contents", "agent", agent.Id, "error", err)
|
||||||
|
}
|
||||||
|
if len(contents) > 0 {
|
||||||
|
dbmodels.Sort_Contents_Numbering(contents)
|
||||||
|
}
|
||||||
|
|
||||||
return &PersonEditResult{
|
return &PersonEditResult{
|
||||||
Agent: agent,
|
Agent: agent,
|
||||||
User: user,
|
User: user,
|
||||||
|
Prev: prev,
|
||||||
|
Next: next,
|
||||||
|
Entries: entries,
|
||||||
|
EntryTypes: entryTypes,
|
||||||
|
Contents: contents,
|
||||||
|
ContentEntries: contentEntries,
|
||||||
|
ContentTypes: contentTypes,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,6 +145,92 @@ func (p *PersonEditPage) renderError(engine *templating.Engine, app core.App, e
|
|||||||
return engine.Response200(e, p.Template, data, p.Layout)
|
return engine.Response200(e, p.Template, data, p.Layout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func agentNeighbors(app core.App, currentID string) (*dbmodels.Agent, *dbmodels.Agent, error) {
|
||||||
|
agents := []*dbmodels.Agent{}
|
||||||
|
if err := app.RecordQuery(dbmodels.AGENTS_TABLE).All(&agents); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if len(agents) == 0 {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
dbmodels.Sort_Agents_Name(agents)
|
||||||
|
for index, item := range agents {
|
||||||
|
if item.Id != currentID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var prev *dbmodels.Agent
|
||||||
|
var next *dbmodels.Agent
|
||||||
|
if index > 0 {
|
||||||
|
prev = agents[index-1]
|
||||||
|
}
|
||||||
|
if index+1 < len(agents) {
|
||||||
|
next = agents[index+1]
|
||||||
|
}
|
||||||
|
return prev, next, nil
|
||||||
|
}
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func agentEntries(app core.App, agentID string) ([]*dbmodels.Entry, map[string][]string, error) {
|
||||||
|
relations, err := dbmodels.REntriesAgents_Agent(app, agentID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if len(relations) == 0 {
|
||||||
|
return []*dbmodels.Entry{}, map[string][]string{}, nil
|
||||||
|
}
|
||||||
|
entryIds := make([]any, 0, len(relations))
|
||||||
|
typeMap := make(map[string][]string)
|
||||||
|
for _, relation := range relations {
|
||||||
|
entryIds = append(entryIds, relation.Entry())
|
||||||
|
typeMap[relation.Entry()] = append(typeMap[relation.Entry()], relation.Type())
|
||||||
|
}
|
||||||
|
entries, err := dbmodels.Entries_IDs(app, entryIds)
|
||||||
|
if err != nil {
|
||||||
|
return nil, typeMap, err
|
||||||
|
}
|
||||||
|
return entries, typeMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func agentContentsDetails(app core.App, agentID string) ([]*dbmodels.Content, map[string]*dbmodels.Entry, map[string][]string, error) {
|
||||||
|
relations, err := dbmodels.RContentsAgents_Agent(app, agentID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
if len(relations) == 0 {
|
||||||
|
return []*dbmodels.Content{}, map[string]*dbmodels.Entry{}, map[string][]string{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
contentIDs := make([]any, 0, len(relations))
|
||||||
|
typeMap := make(map[string][]string)
|
||||||
|
for _, relation := range relations {
|
||||||
|
contentIDs = append(contentIDs, relation.Content())
|
||||||
|
typeMap[relation.Content()] = append(typeMap[relation.Content()], relation.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
contents, err := dbmodels.Contents_IDs(app, contentIDs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
entryIDs := []any{}
|
||||||
|
for _, content := range contents {
|
||||||
|
entryIDs = append(entryIDs, content.Entry())
|
||||||
|
}
|
||||||
|
|
||||||
|
entries, err := dbmodels.Entries_IDs(app, entryIDs)
|
||||||
|
if err != nil {
|
||||||
|
return contents, map[string]*dbmodels.Entry{}, typeMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
entryMap := make(map[string]*dbmodels.Entry, len(entries))
|
||||||
|
for _, entry := range entries {
|
||||||
|
entryMap[entry.Id] = entry
|
||||||
|
}
|
||||||
|
|
||||||
|
return contents, entryMap, typeMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
type personEditForm struct {
|
type personEditForm struct {
|
||||||
CSRFToken string `form:"csrf_token"`
|
CSRFToken string `form:"csrf_token"`
|
||||||
LastEdited string `form:"last_edited"`
|
LastEdited string `form:"last_edited"`
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
URL_REIHE_EDIT = "edit"
|
URL_REIHE_EDIT = "edit"
|
||||||
|
URL_REIHE_DELETE = "edit/delete"
|
||||||
TEMPLATE_REIHE_EDIT = "/reihe/edit/"
|
TEMPLATE_REIHE_EDIT = "/reihe/edit/"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -43,12 +44,20 @@ func (p *ReiheEditPage) Setup(router *router.Router[*core.RequestEvent], app cor
|
|||||||
rg.BindFunc(middleware.IsAdminOrEditor())
|
rg.BindFunc(middleware.IsAdminOrEditor())
|
||||||
rg.GET(URL_REIHE_EDIT, p.GET(engine, app))
|
rg.GET(URL_REIHE_EDIT, p.GET(engine, app))
|
||||||
rg.POST(URL_REIHE_EDIT, p.POST(engine, app))
|
rg.POST(URL_REIHE_EDIT, p.POST(engine, app))
|
||||||
|
rg.POST(URL_REIHE_DELETE, p.POSTDelete(engine, app))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReiheEditResult struct {
|
type ReiheEditResult struct {
|
||||||
Series *dbmodels.Series
|
Series *dbmodels.Series
|
||||||
User *dbmodels.User
|
User *dbmodels.User
|
||||||
|
Prev *dbmodels.Series
|
||||||
|
Next *dbmodels.Series
|
||||||
|
Entries []*dbmodels.Entry
|
||||||
|
Contents []*dbmodels.Content
|
||||||
|
ContentEntries map[string]*dbmodels.Entry
|
||||||
|
ContentTypes map[string][]string
|
||||||
|
PreferredEntries []*dbmodels.Entry
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewReiheEditResult(app core.App, id string) (*ReiheEditResult, error) {
|
func NewReiheEditResult(app core.App, id string) (*ReiheEditResult, error) {
|
||||||
@@ -67,9 +76,45 @@ func NewReiheEditResult(app core.App, id string) (*ReiheEditResult, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prev, next, err := seriesNeighbors(app, series.Id)
|
||||||
|
if err != nil {
|
||||||
|
app.Logger().Error("Failed to load series neighbors", "series", series.Id, "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
entries, _, err := Entries_Series_IDs(app, []any{series.Id})
|
||||||
|
if err != nil {
|
||||||
|
app.Logger().Error("Failed to load series entries", "series", series.Id, "error", err)
|
||||||
|
}
|
||||||
|
if len(entries) > 0 {
|
||||||
|
dbmodels.Sort_Entries_Year_Title(entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
contents, contentEntries, contentTypes, err := seriesContentsDetails(app, entries)
|
||||||
|
if err != nil {
|
||||||
|
app.Logger().Error("Failed to load series contents", "series", series.Id, "error", err)
|
||||||
|
}
|
||||||
|
if len(contents) > 0 {
|
||||||
|
dbmodels.Sort_Contents_Numbering(contents)
|
||||||
|
}
|
||||||
|
|
||||||
|
preferredEntries, err := preferredSeriesEntries(app, series.Id)
|
||||||
|
if err != nil {
|
||||||
|
app.Logger().Error("Failed to load preferred series entries", "series", series.Id, "error", err)
|
||||||
|
}
|
||||||
|
if len(preferredEntries) > 0 {
|
||||||
|
dbmodels.Sort_Entries_Year_Title(preferredEntries)
|
||||||
|
}
|
||||||
|
|
||||||
return &ReiheEditResult{
|
return &ReiheEditResult{
|
||||||
Series: series,
|
Series: series,
|
||||||
User: user,
|
User: user,
|
||||||
|
Prev: prev,
|
||||||
|
Next: next,
|
||||||
|
Entries: entries,
|
||||||
|
Contents: contents,
|
||||||
|
ContentEntries: contentEntries,
|
||||||
|
ContentTypes: contentTypes,
|
||||||
|
PreferredEntries: preferredEntries,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,6 +155,183 @@ func (p *ReiheEditPage) renderError(engine *templating.Engine, app core.App, e *
|
|||||||
return engine.Response200(e, p.Template, data, p.Layout)
|
return engine.Response200(e, p.Template, data, p.Layout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type reiheDeletePayload struct {
|
||||||
|
CSRFToken string `json:"csrf_token"`
|
||||||
|
LastEdited string `json:"last_edited"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ReiheEditPage) POSTDelete(engine *templating.Engine, app core.App) HandleFunc {
|
||||||
|
return func(e *core.RequestEvent) error {
|
||||||
|
id := e.Request.PathValue("id")
|
||||||
|
req := templating.NewRequest(e)
|
||||||
|
|
||||||
|
payload := reiheDeletePayload{}
|
||||||
|
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(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
series, err := dbmodels.Series_MusenalmID(app, id)
|
||||||
|
if err != nil {
|
||||||
|
return e.JSON(http.StatusNotFound, map[string]any{
|
||||||
|
"error": "Reihe 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 !series.Updated().Time().Equal(lastEdited.Time()) {
|
||||||
|
return e.JSON(http.StatusConflict, map[string]any{
|
||||||
|
"error": "Die Reihe wurde inzwischen geändert. Bitte Seite neu laden.",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
preferredEntries, err := preferredSeriesEntries(app, series.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 preferredEntries {
|
||||||
|
if err := deleteEntryRelations(tx, entry.Id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := deleteEntryItems(tx, entry.Id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := deleteEntryContents(tx, entry.Id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
record, err := tx.FindRecordById(dbmodels.ENTRIES_TABLE, entry.Id)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := tx.Delete(record); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
relations, err := dbmodels.REntriesSeries_Seriess(tx, []any{series.Id})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
relationsTable := dbmodels.RelationTableName(dbmodels.ENTRIES_TABLE, dbmodels.SERIES_TABLE)
|
||||||
|
for _, relation := range relations {
|
||||||
|
record, err := tx.FindRecordById(relationsTable, relation.Id)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := tx.Delete(record); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
record, err := tx.FindRecordById(dbmodels.SERIES_TABLE, series.Id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return tx.Delete(record)
|
||||||
|
}); err != nil {
|
||||||
|
app.Logger().Error("Failed to delete series", "series_id", series.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": "/reihen",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func seriesNeighbors(app core.App, currentID string) (*dbmodels.Series, *dbmodels.Series, error) {
|
||||||
|
series := []*dbmodels.Series{}
|
||||||
|
if err := app.RecordQuery(dbmodels.SERIES_TABLE).All(&series); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if len(series) == 0 {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
dbmodels.Sort_Series_Title(series)
|
||||||
|
for index, item := range series {
|
||||||
|
if item.Id != currentID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var prev *dbmodels.Series
|
||||||
|
var next *dbmodels.Series
|
||||||
|
if index > 0 {
|
||||||
|
prev = series[index-1]
|
||||||
|
}
|
||||||
|
if index+1 < len(series) {
|
||||||
|
next = series[index+1]
|
||||||
|
}
|
||||||
|
return prev, next, nil
|
||||||
|
}
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func seriesContentsDetails(app core.App, entries []*dbmodels.Entry) ([]*dbmodels.Content, map[string]*dbmodels.Entry, map[string][]string, error) {
|
||||||
|
if len(entries) == 0 {
|
||||||
|
return []*dbmodels.Content{}, map[string]*dbmodels.Entry{}, map[string][]string{}, nil
|
||||||
|
}
|
||||||
|
entryMap := make(map[string]*dbmodels.Entry, len(entries))
|
||||||
|
for _, entry := range entries {
|
||||||
|
entryMap[entry.Id] = entry
|
||||||
|
}
|
||||||
|
|
||||||
|
contents := []*dbmodels.Content{}
|
||||||
|
typeMap := make(map[string][]string)
|
||||||
|
for _, entry := range entries {
|
||||||
|
entryContents, err := dbmodels.Contents_Entry(app, entry.Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
for _, content := range entryContents {
|
||||||
|
contents = append(contents, content)
|
||||||
|
typeMap[content.Id] = append(typeMap[content.Id], content.MusenalmType()...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return contents, entryMap, typeMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func preferredSeriesEntries(app core.App, seriesID string) ([]*dbmodels.Entry, error) {
|
||||||
|
relations, err := dbmodels.REntriesSeries_Seriess(app, []any{seriesID})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(relations) == 0 {
|
||||||
|
return []*dbmodels.Entry{}, nil
|
||||||
|
}
|
||||||
|
entryIDs := []any{}
|
||||||
|
for _, relation := range relations {
|
||||||
|
if strings.TrimSpace(relation.Type()) != preferredSeriesRelationType {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
entryIDs = append(entryIDs, relation.Entry())
|
||||||
|
}
|
||||||
|
if len(entryIDs) == 0 {
|
||||||
|
return []*dbmodels.Entry{}, nil
|
||||||
|
}
|
||||||
|
return dbmodels.Entries_IDs(app, entryIDs)
|
||||||
|
}
|
||||||
|
|
||||||
type reiheEditForm struct {
|
type reiheEditForm struct {
|
||||||
CSRFToken string `form:"csrf_token"`
|
CSRFToken string `form:"csrf_token"`
|
||||||
LastEdited string `form:"last_edited"`
|
LastEdited string `form:"last_edited"`
|
||||||
|
|||||||
@@ -491,13 +491,17 @@ class Ge extends HTMLElement {
|
|||||||
static get observedAttributes() {
|
static get observedAttributes() {
|
||||||
}
|
}
|
||||||
constructor() {
|
constructor() {
|
||||||
super(), this._showall = !1, this.shown = -1, this._headings = [], this._contents = [], this._checkbox = null;
|
super(), this._showall = !1, this.shown = -1, this._headings = [], this._contents = [], this._checkbox = null, this._disabled = /* @__PURE__ */ new Set(), this._defaultIndex = null;
|
||||||
}
|
}
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
this._headings = Array.from(this.querySelectorAll(".tab-list-head")), this._contents = Array.from(this.querySelectorAll(".tab-list-panel")), this.hookupEvtHandlers(), this.hideDependent(), this._headings.length === 1 && this.expand(0);
|
if (this._headings = Array.from(this.querySelectorAll(".tab-list-head")), this._contents = Array.from(this.querySelectorAll(".tab-list-panel")), this._readConfig(), this.hookupEvtHandlers(), this._applyDisabled(), this.hideDependent(), this._headings.length === 1) {
|
||||||
|
this.expand(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._defaultIndex !== null && this._expandFirstAvailable(this._defaultIndex);
|
||||||
}
|
}
|
||||||
expand(t) {
|
expand(t) {
|
||||||
t < 0 || t >= this._headings.length || (this.shown = t, this._contents.forEach((e, i) => {
|
t < 0 || t >= this._headings.length || this._disabled.has(t) || (this.shown = t, this._contents.forEach((e, i) => {
|
||||||
i === t ? (e.classList.remove("hidden"), this._headings[i].setAttribute("aria-pressed", "true")) : (e.classList.add("hidden"), this._headings[i].setAttribute("aria-pressed", "false"));
|
i === t ? (e.classList.remove("hidden"), this._headings[i].setAttribute("aria-pressed", "true")) : (e.classList.add("hidden"), this._headings[i].setAttribute("aria-pressed", "false"));
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -512,6 +516,32 @@ class Ge extends HTMLElement {
|
|||||||
for (let t of this._contents)
|
for (let t of this._contents)
|
||||||
t.classList.add("hidden");
|
t.classList.add("hidden");
|
||||||
}
|
}
|
||||||
|
_readConfig() {
|
||||||
|
const t = (this.getAttribute("data-disabled-indices") || "").trim(), e = (this.getAttribute("data-default-index") || "").trim();
|
||||||
|
if (this._disabled.clear(), t && t.split(",").map((i) => parseInt(i.trim(), 10)).filter((i) => Number.isFinite(i)).forEach((i) => this._disabled.add(i)), e !== "") {
|
||||||
|
const i = parseInt(e, 10);
|
||||||
|
this._defaultIndex = Number.isFinite(i) ? i : null;
|
||||||
|
} else
|
||||||
|
this._defaultIndex = null;
|
||||||
|
}
|
||||||
|
_applyDisabled() {
|
||||||
|
this._headings.forEach((t, e) => {
|
||||||
|
this._disabled.has(e) ? t.classList.add("pointer-events-none", "opacity-60") : t.classList.remove("pointer-events-none", "opacity-60");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_expandFirstAvailable(t) {
|
||||||
|
if (this._headings.length !== 0) {
|
||||||
|
if (!this._disabled.has(t)) {
|
||||||
|
this.expand(t);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (let e = 0; e < this._headings.length; e += 1)
|
||||||
|
if (!this._disabled.has(e)) {
|
||||||
|
this.expand(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
restore() {
|
restore() {
|
||||||
for (let t of this._headings)
|
for (let t of this._headings)
|
||||||
t.classList.add("cursor-pointer"), t.classList.add("select-none"), t.setAttribute("role", "button"), t.setAttribute("aria-pressed", "false"), t.setAttribute("tabindex", "0"), t.classList.remove("pointer-events-none"), t.classList.remove("!text-slate-900");
|
t.classList.add("cursor-pointer"), t.classList.add("select-none"), t.setAttribute("role", "button"), t.setAttribute("aria-pressed", "false"), t.setAttribute("tabindex", "0"), t.classList.remove("pointer-events-none"), t.classList.remove("!text-slate-900");
|
||||||
@@ -2966,9 +2996,44 @@ class li extends HTMLElement {
|
|||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const t = this.querySelector("form");
|
const t = this.querySelector("form");
|
||||||
t && typeof window.FormLoad == "function" && window.FormLoad(t);
|
t && typeof window.FormLoad == "function" && window.FormLoad(t), this._setupDelete();
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
_setupDelete() {
|
||||||
|
const t = this.querySelector("form");
|
||||||
|
if (!t)
|
||||||
|
return;
|
||||||
|
const e = t.getAttribute("data-delete-endpoint");
|
||||||
|
if (!e)
|
||||||
|
return;
|
||||||
|
const i = this.querySelector("[data-role='edit-delete-dialog']"), s = this.querySelector("[data-role='edit-delete']"), n = this.querySelector("[data-role='edit-delete-confirm']"), a = this.querySelector("[data-role='edit-delete-cancel']");
|
||||||
|
if (!i || !s || !n || !a)
|
||||||
|
return;
|
||||||
|
s.addEventListener("click", (o) => {
|
||||||
|
o.preventDefault(), typeof i.showModal == "function" && i.showModal();
|
||||||
|
});
|
||||||
|
const r = (o) => {
|
||||||
|
o && o.preventDefault(), i.open && i.close();
|
||||||
|
};
|
||||||
|
a.addEventListener("click", r), i.addEventListener("cancel", r), n.addEventListener("click", async (o) => {
|
||||||
|
o.preventDefault(), r();
|
||||||
|
const d = new FormData(t), c = {
|
||||||
|
csrf_token: d.get("csrf_token") || "",
|
||||||
|
last_edited: d.get("last_edited") || ""
|
||||||
|
}, h = await fetch(e, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify(c)
|
||||||
|
});
|
||||||
|
if (!h.ok)
|
||||||
|
return;
|
||||||
|
const u = await h.json().catch(() => null), m = (u == null ? void 0 : u.redirect) || "/";
|
||||||
|
window.location.assign(m);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
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 ri = "filter-list", oi = "scroll-button", di = "tool-tip", hi = "abbrev-tooltips", ci = "int-link", ui = "popup-image", mi = "tab-list", _i = "filter-pill", pi = "image-reel", fi = "multi-select-places", gi = "multi-select-simple", bi = "single-select-remote", Me = "reset-button", Ei = "div-manager", Si = "items-editor", vi = "almanach-edit-page", Li = "relations-editor", yi = "edit-page";
|
||||||
customElements.define(ci, je);
|
customElements.define(ci, je);
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -31,6 +31,35 @@
|
|||||||
<div class="">{{ $agent.Id }}</div>
|
<div class="">{{ $agent.Id }}</div>
|
||||||
</div>
|
</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="/person/{{ $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="/person/{{ $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="flex flex-col justify-end gap-y-6 pr-4">
|
||||||
<div class="">
|
<div class="">
|
||||||
<div class="font-bold text-sm mb-1"><i class="ri-calendar-line"></i> Zuletzt bearbeitet</div>
|
<div class="font-bold text-sm mb-1"><i class="ri-calendar-line"></i> Zuletzt bearbeitet</div>
|
||||||
@@ -122,6 +151,85 @@
|
|||||||
<label for="edit_comment" class="inputlabel">Bearbeitungsvermerk</label>
|
<label for="edit_comment" class="inputlabel">Bearbeitungsvermerk</label>
|
||||||
<textarea name="edit_comment" id="edit_comment" class="inputinput" autocomplete="off" rows="1">{{- $agent.Comment -}}</textarea>
|
<textarea name="edit_comment" id="edit_comment" class="inputinput" autocomplete="off" rows="1">{{- $agent.Comment -}}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mt-2">
|
||||||
|
<tab-list
|
||||||
|
data-default-index="{{ if gt (len $model.result.Entries) 0 }}0{{ else if gt (len $model.result.Contents) 0 }}1{{ end }}"
|
||||||
|
data-disabled-indices="{{ if and (eq (len $model.result.Entries) 0) (eq (len $model.result.Contents) 0) }}0,1{{ else if eq (len $model.result.Entries) 0 }}0{{ else if eq (len $model.result.Contents) 0 }}1{{ 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 class="tab-list-head flex items-center gap-2">
|
||||||
|
<i class="ri-article-line"></i>
|
||||||
|
<span>Verknüpfte Inhalte</span>
|
||||||
|
<span class="text-xs bg-stone-200 text-gray-700 px-2 py-0.5 rounded-sm">{{ len $model.result.Contents }}</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-3 pl-0 pr-0 m-0 ml-0 list-none">
|
||||||
|
{{- range $entry := $model.result.Entries -}}
|
||||||
|
{{- $entryTypes := index $model.result.EntryTypes $entry.Id -}}
|
||||||
|
<li class="flex items-baseline justify-between gap-3 ml-0 pl-0">
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<a href="/almanach/{{ $entry.MusenalmID }}" class="no-underline hover:text-slate-900">
|
||||||
|
{{- $entry.PreferredTitle -}}
|
||||||
|
</a>
|
||||||
|
{{- if $entryTypes -}}
|
||||||
|
<div class="text-xs text-gray-600">
|
||||||
|
Rolle:
|
||||||
|
{{- range $i, $t := $entryTypes -}}
|
||||||
|
{{- if $i }}, {{ end -}}{{ $t -}}
|
||||||
|
{{- end -}}
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
|
</div>
|
||||||
|
<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>
|
||||||
|
<div class="tab-list-panel text-sm text-gray-700 max-h-96 overflow-auto pr-1 pl-0 ml-0">
|
||||||
|
{{- if $model.result.Contents -}}
|
||||||
|
<ul class="flex flex-col gap-3 pl-0 pr-0 m-0 ml-0 list-none">
|
||||||
|
{{- range $content := $model.result.Contents -}}
|
||||||
|
{{- $entry := index $model.result.ContentEntries $content.Entry -}}
|
||||||
|
{{- $types := index $model.result.ContentTypes $content.Id -}}
|
||||||
|
<li class="flex flex-col gap-1 ml-0 pl-0">
|
||||||
|
<a href="/beitrag/{{ $content.MusenalmID }}" class="no-underline hover:text-slate-900 font-semibold">
|
||||||
|
{{- if $content.PreferredTitle -}}{{ $content.PreferredTitle }}{{- else -}}Inhalt #{{ $content.MusenalmID }}{{- end -}}
|
||||||
|
</a>
|
||||||
|
<div class="text-xs text-gray-600 flex flex-wrap gap-3">
|
||||||
|
{{- if $entry -}}
|
||||||
|
<span>Band: <a href="/almanach/{{ $entry.MusenalmID }}" class="no-underline hover:text-slate-900">{{ $entry.PreferredTitle }}</a></span>
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $types -}}
|
||||||
|
<span>
|
||||||
|
Rolle:
|
||||||
|
{{- range $i, $t := $types -}}
|
||||||
|
{{- if $i }}, {{ end -}}{{ $t -}}
|
||||||
|
{{- end -}}
|
||||||
|
</span>
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $content.MusenalmPagination -}}
|
||||||
|
<span>Seite: {{ $content.MusenalmPagination }}</span>
|
||||||
|
{{- end -}}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{{- end -}}
|
||||||
|
</ul>
|
||||||
|
{{- else -}}
|
||||||
|
<div class="italic text-gray-500">Keine Inhalte verknüpft.</div>
|
||||||
|
{{- end -}}
|
||||||
|
</div>
|
||||||
|
</tab-list>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,35 @@
|
|||||||
<div class="">{{ $series.Id }}</div>
|
<div class="">{{ $series.Id }}</div>
|
||||||
</div>
|
</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.Title }}</div>
|
||||||
|
<a
|
||||||
|
href="/reihe/{{ $model.result.Prev.MusenalmID }}/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.Title }}</div>
|
||||||
|
<a
|
||||||
|
href="/reihe/{{ $model.result.Next.MusenalmID }}/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="flex flex-col justify-end gap-y-6 pr-4">
|
||||||
<div class="">
|
<div class="">
|
||||||
<div class="font-bold text-sm mb-1"><i class="ri-calendar-line"></i> Zuletzt bearbeitet</div>
|
<div class="font-bold text-sm mb-1"><i class="ri-calendar-line"></i> Zuletzt bearbeitet</div>
|
||||||
@@ -58,7 +87,8 @@
|
|||||||
class="w-full dbform"
|
class="w-full dbform"
|
||||||
id="changeseriesform"
|
id="changeseriesform"
|
||||||
method="POST"
|
method="POST"
|
||||||
action="/reihe/{{ $series.MusenalmID }}/edit">
|
action="/reihe/{{ $series.MusenalmID }}/edit"
|
||||||
|
data-delete-endpoint="/reihe/{{ $series.MusenalmID }}/edit/delete">
|
||||||
<input type="hidden" name="csrf_token" value="{{ $model.csrf_token }}" />
|
<input type="hidden" name="csrf_token" value="{{ $model.csrf_token }}" />
|
||||||
<input type="hidden" name="last_edited" value="{{ $series.Updated }}" />
|
<input type="hidden" name="last_edited" value="{{ $series.Updated }}" />
|
||||||
|
|
||||||
@@ -101,6 +131,74 @@
|
|||||||
<label for="edit_comment" class="inputlabel">Bearbeitungsvermerk</label>
|
<label for="edit_comment" class="inputlabel">Bearbeitungsvermerk</label>
|
||||||
<textarea name="edit_comment" id="edit_comment" class="inputinput" autocomplete="off" rows="1">{{- $series.Comment -}}</textarea>
|
<textarea name="edit_comment" id="edit_comment" class="inputinput" autocomplete="off" rows="1">{{- $series.Comment -}}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mt-2">
|
||||||
|
<tab-list
|
||||||
|
data-default-index="{{ if gt (len $model.result.Entries) 0 }}0{{ else if gt (len $model.result.Contents) 0 }}1{{ end }}"
|
||||||
|
data-disabled-indices="{{ if and (eq (len $model.result.Entries) 0) (eq (len $model.result.Contents) 0) }}0,1{{ else if eq (len $model.result.Entries) 0 }}0{{ else if eq (len $model.result.Contents) 0 }}1{{ 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 class="tab-list-head flex items-center gap-2">
|
||||||
|
<i class="ri-article-line"></i>
|
||||||
|
<span>Verknüpfte Inhalte</span>
|
||||||
|
<span class="text-xs bg-stone-200 text-gray-700 px-2 py-0.5 rounded-sm">{{ len $model.result.Contents }}</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>
|
||||||
|
<div class="tab-list-panel text-sm text-gray-700 max-h-96 overflow-auto pr-1 pl-0 ml-0">
|
||||||
|
{{- if $model.result.Contents -}}
|
||||||
|
<ul class="flex flex-col gap-3 pl-0 pr-0 m-0 ml-0 list-none">
|
||||||
|
{{- range $content := $model.result.Contents -}}
|
||||||
|
{{- $entry := index $model.result.ContentEntries $content.Entry -}}
|
||||||
|
{{- $types := index $model.result.ContentTypes $content.Id -}}
|
||||||
|
<li class="flex flex-col gap-1 ml-0 pl-0">
|
||||||
|
<a href="/beitrag/{{ $content.MusenalmID }}" class="no-underline hover:text-slate-900 font-semibold">
|
||||||
|
{{- if $content.PreferredTitle -}}{{ $content.PreferredTitle }}{{- else -}}Inhalt #{{ $content.MusenalmID }}{{- end -}}
|
||||||
|
</a>
|
||||||
|
<div class="text-xs text-gray-600 flex flex-wrap gap-3">
|
||||||
|
{{- if $entry -}}
|
||||||
|
<span>Band: <a href="/almanach/{{ $entry.MusenalmID }}" class="no-underline hover:text-slate-900">{{ $entry.PreferredTitle }}</a></span>
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $types -}}
|
||||||
|
<span>
|
||||||
|
Typ:
|
||||||
|
{{- range $i, $t := $types -}}
|
||||||
|
{{- if $i }}, {{ end -}}{{ $t -}}
|
||||||
|
{{- end -}}
|
||||||
|
</span>
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $content.MusenalmPagination -}}
|
||||||
|
<span>Seite: {{ $content.MusenalmPagination }}</span>
|
||||||
|
{{- end -}}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{{- end -}}
|
||||||
|
</ul>
|
||||||
|
{{- else -}}
|
||||||
|
<div class="italic text-gray-500">Keine Inhalte verknüpft.</div>
|
||||||
|
{{- end -}}
|
||||||
|
</div>
|
||||||
|
</tab-list>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -115,6 +213,13 @@
|
|||||||
<i class="ri-loop-left-line"></i>
|
<i class="ri-loop-left-line"></i>
|
||||||
<span>Reset</span>
|
<span>Reset</span>
|
||||||
</a>
|
</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>Reihe löschen</span>
|
||||||
|
</button>
|
||||||
<button type="submit" class="submitbutton w-40 flex items-center gap-2 justify-center">
|
<button type="submit" class="submitbutton w-40 flex items-center gap-2 justify-center">
|
||||||
<i class="ri-save-line"></i>
|
<i class="ri-save-line"></i>
|
||||||
<span>Speichern</span>
|
<span>Speichern</span>
|
||||||
@@ -123,4 +228,36 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
<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">Reihe löschen?</div>
|
||||||
|
<div class="text-sm font-bold text-gray-900 mt-1">{{ $series.Title }}</div>
|
||||||
|
<p class="text-sm text-gray-700 mt-2">
|
||||||
|
Alle Bände, Inhalte und Verknüpfungen der bevorzugten Reihentitel werden gelöscht.
|
||||||
|
</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.PreferredEntries -}}
|
||||||
|
<ul class="flex flex-col gap-2 pl-0 pr-0 m-0 list-none">
|
||||||
|
{{- range $entry := $model.result.PreferredEntries -}}
|
||||||
|
<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>
|
||||||
</edit-page>
|
</edit-page>
|
||||||
|
|||||||
@@ -5,6 +5,69 @@ export class EditPage extends HTMLElement {
|
|||||||
if (form && typeof window.FormLoad === "function") {
|
if (form && typeof window.FormLoad === "function") {
|
||||||
window.FormLoad(form);
|
window.FormLoad(form);
|
||||||
}
|
}
|
||||||
|
this._setupDelete();
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_setupDelete() {
|
||||||
|
const form = this.querySelector("form");
|
||||||
|
if (!form) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const deleteEndpoint = form.getAttribute("data-delete-endpoint");
|
||||||
|
if (!deleteEndpoint) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const dialog = this.querySelector("[data-role='edit-delete-dialog']");
|
||||||
|
const deleteButton = this.querySelector("[data-role='edit-delete']");
|
||||||
|
const confirmButton = this.querySelector("[data-role='edit-delete-confirm']");
|
||||||
|
const cancelButton = this.querySelector("[data-role='edit-delete-cancel']");
|
||||||
|
|
||||||
|
if (!dialog || !deleteButton || !confirmButton || !cancelButton) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteButton.addEventListener("click", (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (typeof dialog.showModal === "function") {
|
||||||
|
dialog.showModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const closeDialog = (event) => {
|
||||||
|
if (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
if (dialog.open) {
|
||||||
|
dialog.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
cancelButton.addEventListener("click", closeDialog);
|
||||||
|
dialog.addEventListener("cancel", closeDialog);
|
||||||
|
|
||||||
|
confirmButton.addEventListener("click", async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
closeDialog();
|
||||||
|
const formData = new FormData(form);
|
||||||
|
const payload = {
|
||||||
|
csrf_token: formData.get("csrf_token") || "",
|
||||||
|
last_edited: formData.get("last_edited") || "",
|
||||||
|
};
|
||||||
|
const response = await fetch(deleteEndpoint, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data = await response.json().catch(() => null);
|
||||||
|
const redirect = data?.redirect || "/";
|
||||||
|
window.location.assign(redirect);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,6 +102,18 @@
|
|||||||
@apply px-1.5 italic text-gray-600;
|
@apply px-1.5 italic text-gray-600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Reset global list indentation inside tab panels */
|
||||||
|
.tab-list-panel ul {
|
||||||
|
margin-left: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-list-panel li {
|
||||||
|
margin-left: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Disabled form controls in deleted relations */
|
/* Disabled form controls in deleted relations */
|
||||||
[data-rel-row].bg-red-50 select:disabled,
|
[data-rel-row].bg-red-50 select:disabled,
|
||||||
[data-rel-row].bg-red-50 input[type="checkbox"]:disabled:not([data-delete-toggle]) {
|
[data-rel-row].bg-red-50 input[type="checkbox"]:disabled:not([data-delete-toggle]) {
|
||||||
|
|||||||
@@ -8,16 +8,25 @@ export class TabList extends HTMLElement {
|
|||||||
this._headings = [];
|
this._headings = [];
|
||||||
this._contents = [];
|
this._contents = [];
|
||||||
this._checkbox = null;
|
this._checkbox = null;
|
||||||
|
this._disabled = new Set();
|
||||||
|
this._defaultIndex = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
this._headings = Array.from(this.querySelectorAll(".tab-list-head"));
|
this._headings = Array.from(this.querySelectorAll(".tab-list-head"));
|
||||||
this._contents = Array.from(this.querySelectorAll(".tab-list-panel"));
|
this._contents = Array.from(this.querySelectorAll(".tab-list-panel"));
|
||||||
|
this._readConfig();
|
||||||
this.hookupEvtHandlers();
|
this.hookupEvtHandlers();
|
||||||
|
this._applyDisabled();
|
||||||
this.hideDependent();
|
this.hideDependent();
|
||||||
|
|
||||||
if (this._headings.length === 1) {
|
if (this._headings.length === 1) {
|
||||||
this.expand(0);
|
this.expand(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._defaultIndex !== null) {
|
||||||
|
this._expandFirstAvailable(this._defaultIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,6 +34,9 @@ export class TabList extends HTMLElement {
|
|||||||
if (index < 0 || index >= this._headings.length) {
|
if (index < 0 || index >= this._headings.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (this._disabled.has(index)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.shown = index;
|
this.shown = index;
|
||||||
|
|
||||||
@@ -67,6 +79,53 @@ export class TabList extends HTMLElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_readConfig() {
|
||||||
|
const disabledRaw = (this.getAttribute("data-disabled-indices") || "").trim();
|
||||||
|
const defaultRaw = (this.getAttribute("data-default-index") || "").trim();
|
||||||
|
|
||||||
|
this._disabled.clear();
|
||||||
|
if (disabledRaw) {
|
||||||
|
disabledRaw
|
||||||
|
.split(",")
|
||||||
|
.map((value) => parseInt(value.trim(), 10))
|
||||||
|
.filter((value) => Number.isFinite(value))
|
||||||
|
.forEach((value) => this._disabled.add(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defaultRaw !== "") {
|
||||||
|
const parsed = parseInt(defaultRaw, 10);
|
||||||
|
this._defaultIndex = Number.isFinite(parsed) ? parsed : null;
|
||||||
|
} else {
|
||||||
|
this._defaultIndex = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_applyDisabled() {
|
||||||
|
this._headings.forEach((heading, index) => {
|
||||||
|
if (this._disabled.has(index)) {
|
||||||
|
heading.classList.add("pointer-events-none", "opacity-60");
|
||||||
|
} else {
|
||||||
|
heading.classList.remove("pointer-events-none", "opacity-60");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_expandFirstAvailable(preferredIndex) {
|
||||||
|
if (this._headings.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this._disabled.has(preferredIndex)) {
|
||||||
|
this.expand(preferredIndex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < this._headings.length; i += 1) {
|
||||||
|
if (!this._disabled.has(i)) {
|
||||||
|
this.expand(i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
restore() {
|
restore() {
|
||||||
for (let heading of this._headings) {
|
for (let heading of this._headings) {
|
||||||
heading.classList.add("cursor-pointer");
|
heading.classList.add("cursor-pointer");
|
||||||
|
|||||||
Reference in New Issue
Block a user