mirror of
https://github.com/Theodor-Springmann-Stiftung/musenalm.git
synced 2026-02-04 02:25:30 +00:00
+Faster loading /baende
This commit is contained in:
@@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/Theodor-Springmann-Stiftung/musenalm/pagemodels"
|
"github.com/Theodor-Springmann-Stiftung/musenalm/pagemodels"
|
||||||
"github.com/Theodor-Springmann-Stiftung/musenalm/templating"
|
"github.com/Theodor-Springmann-Stiftung/musenalm/templating"
|
||||||
"github.com/pocketbase/dbx"
|
"github.com/pocketbase/dbx"
|
||||||
|
|
||||||
"github.com/pocketbase/pocketbase/core"
|
"github.com/pocketbase/pocketbase/core"
|
||||||
"github.com/pocketbase/pocketbase/tools/router"
|
"github.com/pocketbase/pocketbase/tools/router"
|
||||||
)
|
)
|
||||||
@@ -20,6 +21,7 @@ const (
|
|||||||
URL_BAENDE = "/baende/"
|
URL_BAENDE = "/baende/"
|
||||||
URL_BAENDE_RESULTS = "/baende/results/"
|
URL_BAENDE_RESULTS = "/baende/results/"
|
||||||
TEMPLATE_BAENDE = "/baende/"
|
TEMPLATE_BAENDE = "/baende/"
|
||||||
|
URL_BAENDE_DETAILS = "/baende/details/{id}"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -48,12 +50,26 @@ type BaendeResult struct {
|
|||||||
Items map[string][]*dbmodels.Item
|
Items map[string][]*dbmodels.Item
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BaendeDetailsResult struct {
|
||||||
|
Entry *dbmodels.Entry
|
||||||
|
Series []*dbmodels.Series
|
||||||
|
Places []*dbmodels.Place
|
||||||
|
Agents []*dbmodels.Agent
|
||||||
|
Items []*dbmodels.Item
|
||||||
|
SeriesRels []*dbmodels.REntriesSeries
|
||||||
|
AgentRels []*dbmodels.REntriesAgents
|
||||||
|
IsAdmin bool
|
||||||
|
CSRFToken string
|
||||||
|
}
|
||||||
|
|
||||||
func (p *BaendePage) Setup(router *router.Router[*core.RequestEvent], ia pagemodels.IApp, engine *templating.Engine) error {
|
func (p *BaendePage) Setup(router *router.Router[*core.RequestEvent], ia pagemodels.IApp, engine *templating.Engine) error {
|
||||||
app := ia.Core()
|
app := ia.Core()
|
||||||
rg := router.Group(URL_BAENDE)
|
rg := router.Group(URL_BAENDE)
|
||||||
rg.BindFunc(middleware.Authenticated(app))
|
rg.BindFunc(middleware.Authenticated(app))
|
||||||
rg.GET("", p.handlePage(engine, app))
|
rg.GET("", p.handlePage(engine, app))
|
||||||
rg.GET("results/", p.handleResults(engine, app))
|
rg.GET("results/", p.handleResults(engine, app))
|
||||||
|
rg.GET("details/{id}", p.handleDetails(engine, app))
|
||||||
|
rg.GET("row/{id}", p.handleRow(engine, app))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,6 +105,104 @@ func (p *BaendePage) handleResults(engine *templating.Engine, app core.App) Hand
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *BaendePage) handleRow(engine *templating.Engine, app core.App) HandleFunc {
|
||||||
|
return func(e *core.RequestEvent) error {
|
||||||
|
req := templating.NewRequest(e)
|
||||||
|
if req.User() == nil {
|
||||||
|
return e.Redirect(303, "/login/")
|
||||||
|
}
|
||||||
|
|
||||||
|
id := e.Request.PathValue("id")
|
||||||
|
if id == "" {
|
||||||
|
return engine.Response404(e, nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
entry, err := dbmodels.Entries_MusenalmID(app, id)
|
||||||
|
if err != nil {
|
||||||
|
return engine.Response404(e, err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
items, err := dbmodels.Items_Entry(app, entry.Id)
|
||||||
|
if err != nil {
|
||||||
|
app.Logger().Error("Failed to get items for entry", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := map[string]any{
|
||||||
|
"entry": entry,
|
||||||
|
"items": items,
|
||||||
|
"is_admin": req.IsAdmin(),
|
||||||
|
"csrf_token": req.Session().Token,
|
||||||
|
}
|
||||||
|
|
||||||
|
return engine.Response200(e, "/baende/row/", data, "fragment")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *BaendePage) handleDetails(engine *templating.Engine, app core.App) HandleFunc {
|
||||||
|
return func(e *core.RequestEvent) error {
|
||||||
|
req := templating.NewRequest(e)
|
||||||
|
if req.User() == nil {
|
||||||
|
return e.Redirect(303, "/login/")
|
||||||
|
}
|
||||||
|
|
||||||
|
id := e.Request.PathValue("id")
|
||||||
|
if id == "" {
|
||||||
|
return engine.Response404(e, nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
entry, err := dbmodels.Entries_MusenalmID(app, id)
|
||||||
|
if err != nil {
|
||||||
|
return engine.Response404(e, err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
entryIDs := []any{entry.Id}
|
||||||
|
|
||||||
|
series, relations, err := Series_Entries(app, []*dbmodels.Entry{entry})
|
||||||
|
if err != nil {
|
||||||
|
app.Logger().Error("Failed to get series for entry", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
agents, arelations, err := Agents_Entries_IDs(app, entryIDs)
|
||||||
|
if err != nil {
|
||||||
|
app.Logger().Error("Failed to get agents for entry", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
toStringAny := func(ss []string) []any {
|
||||||
|
res := make([]any, len(ss))
|
||||||
|
for i, s := range ss {
|
||||||
|
res[i] = s
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
places, err := dbmodels.Places_IDs(app, toStringAny(entry.Places()))
|
||||||
|
if err != nil {
|
||||||
|
app.Logger().Error("Failed to get places for entry", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
items, err := dbmodels.Items_Entry(app, entry.Id)
|
||||||
|
if err != nil {
|
||||||
|
app.Logger().Error("Failed to get items for entry", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := map[string]any{
|
||||||
|
"result": &BaendeDetailsResult{
|
||||||
|
Entry: entry,
|
||||||
|
Series: series,
|
||||||
|
Places: places,
|
||||||
|
Agents: agents,
|
||||||
|
Items: items,
|
||||||
|
SeriesRels: relations,
|
||||||
|
AgentRels: arelations,
|
||||||
|
IsAdmin: req.IsAdmin(),
|
||||||
|
CSRFToken: req.Session().Token,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return engine.Response200(e, "/baende/details/", data, "fragment")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (p *BaendePage) buildResultData(app core.App, e *core.RequestEvent, req *templating.Request) (map[string]any, error) {
|
func (p *BaendePage) buildResultData(app core.App, e *core.RequestEvent, req *templating.Request) (map[string]any, error) {
|
||||||
data := map[string]any{}
|
data := map[string]any{}
|
||||||
|
|
||||||
@@ -177,13 +291,28 @@ func (p *BaendePage) buildResultData(app core.App, e *core.RequestEvent, req *te
|
|||||||
}
|
}
|
||||||
|
|
||||||
itemsMap := map[string][]*dbmodels.Item{}
|
itemsMap := map[string][]*dbmodels.Item{}
|
||||||
for _, entry := range entries {
|
if len(entryIDs) > 0 {
|
||||||
items, err := dbmodels.Items_Entry(app, entry.Id)
|
// 1. Fetch all items related to any of the entry IDs in a single query.
|
||||||
|
allItems, err := dbmodels.Items_Entries(app, entryIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return data, err
|
return data, err
|
||||||
}
|
}
|
||||||
if len(items) > 0 {
|
|
||||||
itemsMap[entry.Id] = items
|
// 2. Create a lookup map for the entries we are interested in.
|
||||||
|
interestedEntries := make(map[string]struct{})
|
||||||
|
for _, id := range entryIDs {
|
||||||
|
interestedEntries[id.(string)] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Group the fetched items by their associated entry ID.
|
||||||
|
for _, item := range allItems {
|
||||||
|
// An item can be related to multiple entries. We need to check which of its entries are in our current list.
|
||||||
|
for _, entryID := range item.Entries() {
|
||||||
|
// If the item's entry ID is in our list of interested entries, add the item to the map.
|
||||||
|
if _, ok := interestedEntries[entryID]; ok {
|
||||||
|
itemsMap[entryID] = append(itemsMap[entryID], item)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,25 @@ func (a *Item) Entry() string {
|
|||||||
return a.GetString(ENTRIES_TABLE)
|
return a.GetString(ENTRIES_TABLE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *Item) Entries() []string {
|
||||||
|
// Get the raw string value of the 'entries' field
|
||||||
|
raw := i.GetString(ENTRIES_TABLE)
|
||||||
|
|
||||||
|
// Check if it's likely a JSON array (starts with '[')
|
||||||
|
if len(raw) > 0 && raw[0] == '[' {
|
||||||
|
// If it is JSON, GetStringSlice will handle unmarshalling it
|
||||||
|
return i.GetStringSlice(ENTRIES_TABLE)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's not a JSON array, it's a single ID. Return it as a slice.
|
||||||
|
if raw != "" {
|
||||||
|
return []string{raw}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return an empty slice if the field is empty
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
func (a *Item) SetEntry(entry string) {
|
func (a *Item) SetEntry(entry string) {
|
||||||
a.Set(ENTRIES_TABLE, entry)
|
a.Set(ENTRIES_TABLE, entry)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
package dbmodels
|
package dbmodels
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/pocketbase/dbx"
|
"github.com/pocketbase/dbx"
|
||||||
"github.com/pocketbase/pocketbase/core"
|
"github.com/pocketbase/pocketbase/core"
|
||||||
)
|
)
|
||||||
@@ -240,6 +244,37 @@ func Items_Entry(app core.App, id string) ([]*Item, error) {
|
|||||||
return ret, err
|
return ret, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Items_Entries(app core.App, ids []any) ([]*Item, error) {
|
||||||
|
if len(ids) == 0 {
|
||||||
|
return []*Item{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
params := dbx.Params{}
|
||||||
|
idPlaceholders := make([]string, len(ids))
|
||||||
|
for i, id := range ids {
|
||||||
|
placeholder := "p" + strconv.Itoa(i)
|
||||||
|
params[placeholder] = id
|
||||||
|
idPlaceholders[i] = "{:" + placeholder + "}"
|
||||||
|
}
|
||||||
|
inClause := strings.Join(idPlaceholders, ", ")
|
||||||
|
|
||||||
|
fullExpression := fmt.Sprintf(
|
||||||
|
"%s IN (%s) OR (json_valid(%s) = 1 AND EXISTS (SELECT 1 FROM json_each(%s) WHERE value IN (%s)))",
|
||||||
|
ENTRIES_TABLE,
|
||||||
|
inClause,
|
||||||
|
ENTRIES_TABLE,
|
||||||
|
ENTRIES_TABLE,
|
||||||
|
inClause,
|
||||||
|
)
|
||||||
|
|
||||||
|
var ret []*Item
|
||||||
|
err := app.RecordQuery(ITEMS_TABLE).
|
||||||
|
Where(dbx.NewExp(fullExpression, params)).
|
||||||
|
All(&ret)
|
||||||
|
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
|
||||||
func Contents_MusenalmID(app core.App, id string) (*Content, error) {
|
func Contents_MusenalmID(app core.App, id string) (*Content, error) {
|
||||||
ret, err := TableByField[Content](app, CONTENTS_TABLE, MUSENALMID_FIELD, id)
|
ret, err := TableByField[Content](app, CONTENTS_TABLE, MUSENALMID_FIELD, id)
|
||||||
return &ret, err
|
return &ret, err
|
||||||
|
|||||||
@@ -15,7 +15,13 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{{- range $_, $entry := $model.result.Entries -}}
|
{{- range $_, $entry := $model.result.Entries -}}
|
||||||
<tr class="border-b align-top cursor-pointer transition-colors odd:bg-white even:bg-stone-50/60 hover:bg-stone-100" data-role="baende-row" data-entry-id="{{ $entry.MusenalmID }}">
|
<tr class="border-b align-top cursor-pointer transition-colors odd:bg-white even:bg-stone-50/60 hover:bg-stone-100"
|
||||||
|
data-role="baende-row"
|
||||||
|
data-entry-id="{{ $entry.MusenalmID }}"
|
||||||
|
hx-get="/baende/details/{{ $entry.MusenalmID }}"
|
||||||
|
hx-target="this"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
>
|
||||||
<td class="py-2 pr-4 pl-2 whitespace-nowrap w-[10rem]">
|
<td class="py-2 pr-4 pl-2 whitespace-nowrap w-[10rem]">
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<span class="inline-flex items-center rounded-xs bg-stone-100 px-2.5 py-0.5 text-xs font-semibold text-slate-700">Alm {{ $entry.MusenalmID }}</span>
|
<span class="inline-flex items-center rounded-xs bg-stone-100 px-2.5 py-0.5 text-xs font-semibold text-slate-700">Alm {{ $entry.MusenalmID }}</span>
|
||||||
@@ -167,238 +173,6 @@
|
|||||||
{{- end -}}
|
{{- end -}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="hidden bg-stone-50/60 border-b last:border-b-0" data-role="baende-details" data-entry-id="{{ $entry.MusenalmID }}">
|
|
||||||
<td class="py-2 pr-4 pl-2 whitespace-nowrap w-[10rem] align-top">
|
|
||||||
<div class="flex flex-col gap-1 pt-1">
|
|
||||||
<span class="inline-flex items-center rounded-xs bg-stone-100 px-2.5 py-0.5 text-xs font-semibold text-slate-700">Alm {{ $entry.MusenalmID }}</span>
|
|
||||||
{{- if $entry.References -}}
|
|
||||||
<span class="inline-flex items-center rounded-xs bg-stone-100 px-2.5 py-0.5 text-xs text-slate-700 max-w-[10rem] whitespace-normal break-words" title="{{ $entry.References }}">{{ $entry.References }}</span>
|
|
||||||
{{- end -}}
|
|
||||||
<div class="flex flex-wrap items-center gap-1.5 pt-2">
|
|
||||||
<tool-tip position="top" class="inline">
|
|
||||||
<a href="/almanach/{{ $entry.MusenalmID }}" class="no-underline inline-flex items-center gap-1 rounded-xs bg-stone-100 px-2 py-1 text-xs font-semibold text-slate-700 hover:bg-stone-200 hover:text-slate-900">
|
|
||||||
<i class="ri-eye-line"></i>
|
|
||||||
</a>
|
|
||||||
<div class="data-tip">Ansehen</div>
|
|
||||||
</tool-tip>
|
|
||||||
{{- if (IsAdminOrEditor $model.request.user) -}}
|
|
||||||
<tool-tip position="top" class="inline">
|
|
||||||
<a href="/almanach/{{ $entry.MusenalmID }}/edit" class="no-underline inline-flex items-center gap-1 rounded-xs bg-stone-100 px-2 py-1 text-xs font-semibold text-slate-700 hover:bg-stone-200 hover:text-slate-900">
|
|
||||||
<i class="ri-edit-line"></i>
|
|
||||||
</a>
|
|
||||||
<div class="data-tip">Bearbeiten</div>
|
|
||||||
</tool-tip>
|
|
||||||
<form method="POST" action="/almanach/{{ $entry.MusenalmID }}/edit/delete" class="inline" onsubmit="return confirm('Band wirklich löschen?');">
|
|
||||||
<input type="hidden" name="csrf_token" value="{{ $model.csrf_token }}" />
|
|
||||||
<tool-tip position="top" class="inline">
|
|
||||||
<button type="submit" class="inline-flex items-center gap-1 rounded-xs bg-red-50 px-2 py-1 text-xs font-semibold text-red-700 hover:bg-red-100 hover:text-red-900">
|
|
||||||
<i class="ri-delete-bin-line"></i>
|
|
||||||
</button>
|
|
||||||
<div class="data-tip">Löschen</div>
|
|
||||||
</tool-tip>
|
|
||||||
</form>
|
|
||||||
{{- end -}}
|
|
||||||
<tool-tip position="top" class="inline">
|
|
||||||
<button type="button" class="inline-flex items-center gap-1 rounded-xs bg-stone-100 px-2 py-1 text-xs font-semibold text-slate-700 hover:bg-stone-200 hover:text-slate-900" data-role="baende-collapse" data-entry-id="{{ $entry.MusenalmID }}">
|
|
||||||
<i class="ri-arrow-up-s-line"></i>
|
|
||||||
</button>
|
|
||||||
<div class="data-tip">Zuklappen</div>
|
|
||||||
</tool-tip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td class="py-2 pr-4 pl-2" colspan="6">
|
|
||||||
<div class="rounded-md border border-slate-200 bg-white shadow-sm p-3 text-base">
|
|
||||||
<div class="flex items-start justify-between gap-4 mb-3">
|
|
||||||
<div class="font-serif text-base font-semibold text-slate-900 inline-flex items-center">
|
|
||||||
{{- if $entry.PreferredTitle -}}
|
|
||||||
{{ $entry.PreferredTitle }}
|
|
||||||
{{- else if ne $entry.Year 0 -}}
|
|
||||||
{{ $entry.Year }}
|
|
||||||
{{- else -}}
|
|
||||||
[o.J.]
|
|
||||||
{{- end -}}
|
|
||||||
<tool-tip position="top" class="inline-flex items-center">
|
|
||||||
<i class="status-icon ml-1 {{- if eq $entry.EditState "Edited" }} ri-checkbox-circle-line{{- else if eq $entry.EditState "Seen" }} ri-information-line{{- else if eq $entry.EditState "Review" }} ri-search-line{{- else if eq $entry.EditState "ToDo" }} ri-list-check{{- else }} ri-forbid-2-line{{- end }}" data-status="{{ $entry.EditState }}"></i>
|
|
||||||
<div class="data-tip">
|
|
||||||
{{- if eq $entry.EditState "Unknown" -}}
|
|
||||||
Gesucht
|
|
||||||
{{- else if eq $entry.EditState "ToDo" -}}
|
|
||||||
Zu erledigen
|
|
||||||
{{- else if eq $entry.EditState "Review" -}}
|
|
||||||
Überprüfen
|
|
||||||
{{- else if eq $entry.EditState "Seen" -}}
|
|
||||||
Autopsiert
|
|
||||||
{{- else if eq $entry.EditState "Edited" -}}
|
|
||||||
Vollständig Erfasst
|
|
||||||
{{- else -}}
|
|
||||||
{{ $entry.EditState }}
|
|
||||||
{{- end -}}
|
|
||||||
</div>
|
|
||||||
</tool-tip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-[1fr_18rem] gap-6 text-sm font-sans text-gray-700">
|
|
||||||
<div class="flex flex-col gap-3">
|
|
||||||
{{- if or $entry.TitleStmt $entry.SubtitleStmt $entry.VariantTitle $entry.ParallelTitle $entry.IncipitStmt -}}
|
|
||||||
<div>
|
|
||||||
<div class="text-xs uppercase tracking-wide text-gray-500">Titelangaben</div>
|
|
||||||
<div>
|
|
||||||
{{- if $entry.TitleStmt -}}<div><span class="font-semibold text-gray-500">Titel:</span> {{ $entry.TitleStmt }}</div>{{- end -}}
|
|
||||||
{{- if $entry.SubtitleStmt -}}<div><span class="font-semibold text-gray-500">Untertitel:</span> {{ $entry.SubtitleStmt }}</div>{{- end -}}
|
|
||||||
{{- if $entry.VariantTitle -}}<div><span class="font-semibold text-gray-500">Varianten:</span> {{ $entry.VariantTitle }}</div>{{- end -}}
|
|
||||||
{{- if $entry.ParallelTitle -}}<div><span class="font-semibold text-gray-500">Parallel:</span> {{ $entry.ParallelTitle }}</div>{{- end -}}
|
|
||||||
{{- if $entry.IncipitStmt -}}<div><span class="font-semibold text-gray-500">Incipit:</span> {{ $entry.IncipitStmt }}</div>{{- end -}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{- end -}}
|
|
||||||
{{- if or $entry.ResponsibilityStmt $entry.PublicationStmt $entry.PlaceStmt -}}
|
|
||||||
<div>
|
|
||||||
<div class="text-xs uppercase tracking-wide text-gray-500">Erscheinung</div>
|
|
||||||
<div>
|
|
||||||
{{- if and $entry.ResponsibilityStmt (not (eq $entry.ResponsibilityStmt "unbezeichnet")) -}}<div><span class="font-semibold text-gray-500">Herausgaberangabe:</span> {{ $entry.ResponsibilityStmt }}</div>{{- end -}}
|
|
||||||
{{- if $entry.PublicationStmt -}}<div><span class="font-semibold text-gray-500">Publikationsangabe:</span> {{ $entry.PublicationStmt }}</div>{{- end -}}
|
|
||||||
{{- if $entry.PlaceStmt -}}<div><span class="font-semibold text-gray-500">Ortsangabe:</span> {{ $entry.PlaceStmt }}</div>{{- end -}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{- end -}}
|
|
||||||
{{- if $entry.Annotation -}}
|
|
||||||
<div>
|
|
||||||
<div class="text-xs uppercase tracking-wide text-gray-500">Annotation</div>
|
|
||||||
<div>{{ $entry.Annotation }}</div>
|
|
||||||
</div>
|
|
||||||
{{- end -}}
|
|
||||||
{{- $srels := index $model.result.EntriesSeries $entry.Id -}}
|
|
||||||
{{- if $srels -}}
|
|
||||||
<div>
|
|
||||||
<div class="text-xs uppercase tracking-wide text-gray-500">Reihen</div>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
{{- range $_, $rel := $srels -}}
|
|
||||||
{{- $s := index $model.result.Series $rel.Series -}}
|
|
||||||
{{- if $s -}}
|
|
||||||
<div>
|
|
||||||
{{- if not (eq $rel.Type "Bevorzugter Reihentitel") -}}
|
|
||||||
<span class="mr-2 px-2 py-0.5 rounded text-xs font-sans bg-slate-200 inline-block">{{ $rel.Type }}</span>
|
|
||||||
{{- end -}}
|
|
||||||
<a href="/reihe/{{ $s.MusenalmID }}">{{ $s.Title }}</a>
|
|
||||||
</div>
|
|
||||||
{{- end -}}
|
|
||||||
{{- end -}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{- end -}}
|
|
||||||
{{- if $entry.Places -}}
|
|
||||||
<div>
|
|
||||||
<div class="text-xs uppercase tracking-wide text-gray-500">Orte</div>
|
|
||||||
<div>
|
|
||||||
{{- $sep := false -}}
|
|
||||||
{{- range $_, $pid := $entry.Places -}}
|
|
||||||
{{- $p := index $model.result.Places $pid -}}
|
|
||||||
{{- if $p -}}
|
|
||||||
{{- if $sep }}, {{ end -}}
|
|
||||||
<a href="/reihen?place={{ $p.Id }}&hidden=true">{{ $p.Name }}</a>
|
|
||||||
{{- $sep = true -}}
|
|
||||||
{{- end -}}
|
|
||||||
{{- end -}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{- end -}}
|
|
||||||
{{- $arels := index $model.result.EntriesAgents $entry.Id -}}
|
|
||||||
{{- if $arels -}}
|
|
||||||
<div>
|
|
||||||
<div class="text-xs uppercase tracking-wide text-gray-500">Personen</div>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
{{- range $_, $arel := $arels -}}
|
|
||||||
{{- $a := index $model.result.Agents $arel.Agent -}}
|
|
||||||
{{- if $a -}}
|
|
||||||
<div>
|
|
||||||
<a href="/person/{{ $a.Id }}">{{ $a.Name }}</a>
|
|
||||||
<span class="ml-2 px-2 py-0.5 rounded text-xs font-sans bg-slate-200 inline-block">{{ $arel.Type }}</span>
|
|
||||||
</div>
|
|
||||||
{{- end -}}
|
|
||||||
{{- end -}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{- end -}}
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-3">
|
|
||||||
{{- if $entry.Comment -}}
|
|
||||||
<div>
|
|
||||||
<div class="text-xs uppercase tracking-wide text-gray-500">Kommentar</div>
|
|
||||||
<div>{{ $entry.Comment }}</div>
|
|
||||||
</div>
|
|
||||||
{{- end -}}
|
|
||||||
{{- if $entry.Language -}}
|
|
||||||
<div>
|
|
||||||
<div class="text-xs uppercase tracking-wide text-gray-500">Sprachen</div>
|
|
||||||
<div>
|
|
||||||
{{- range $i, $lang := $entry.Language -}}{{- if $i }}, {{ end -}}{{ LanguageNameGerman $lang }}{{- end -}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{- end -}}
|
|
||||||
{{- if or $entry.Extent $entry.Dimensions -}}
|
|
||||||
<div>
|
|
||||||
<div class="text-xs uppercase tracking-wide text-gray-500">Umfang / Maße</div>
|
|
||||||
<div>
|
|
||||||
{{- if $entry.Extent -}}<div><span class="font-semibold text-gray-500 block">Struktur</span>{{ $entry.Extent }}</div>{{- end -}}
|
|
||||||
{{- if $entry.Dimensions -}}<div><span class="font-semibold text-gray-500">Maße:</span> {{ $entry.Dimensions }}</div>{{- end -}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{- end -}}
|
|
||||||
{{- if $entry.References -}}
|
|
||||||
<div>
|
|
||||||
<div class="text-xs uppercase tracking-wide text-gray-500">Nachweise</div>
|
|
||||||
<div>{{ $entry.References }}</div>
|
|
||||||
</div>
|
|
||||||
{{- end -}}
|
|
||||||
{{- if ne $entry.Year 0 -}}
|
|
||||||
<div>
|
|
||||||
<div class="text-xs uppercase tracking-wide text-gray-500">Jahr</div>
|
|
||||||
<div>{{ $entry.Year }}</div>
|
|
||||||
</div>
|
|
||||||
{{- end -}}
|
|
||||||
{{- $items := index $model.result.Items $entry.Id -}}
|
|
||||||
{{- if $items -}}
|
|
||||||
<div>
|
|
||||||
<div class="text-xs uppercase tracking-wide text-gray-500">Exemplare</div>
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
{{- range $_, $item := $items -}}
|
|
||||||
<div class="rounded-md border border-slate-200 bg-white px-3 py-2 shadow-sm">
|
|
||||||
<div class="flex items-start gap-3">
|
|
||||||
<div class="inline-flex flex-col items-center justify-center rounded-xs border border-slate-200 bg-white text-xs min-w-[6rem]">
|
|
||||||
{{- if $item.Identifier -}}
|
|
||||||
<div class="px-2 py-1 font-semibold text-slate-900 text-center">{{ $item.Identifier }}</div>
|
|
||||||
{{- end -}}
|
|
||||||
{{- if $item.Media -}}
|
|
||||||
<div class="w-full border-t border-slate-200 px-2 py-1 text-gray-600 text-center leading-snug">
|
|
||||||
{{- range $i, $media := $item.Media -}}{{- if $i }}, {{ end -}}{{ $media }}{{- end -}}
|
|
||||||
</div>
|
|
||||||
{{- end -}}
|
|
||||||
</div>
|
|
||||||
<div class="flex-1">
|
|
||||||
{{- if or $item.Location $item.Owner -}}
|
|
||||||
<div class="text-sm text-gray-600">
|
|
||||||
{{- if $item.Location -}}<span>{{ $item.Location }}</span>{{- end -}}
|
|
||||||
{{- if $item.Owner -}}<span class="ml-2">{{ $item.Owner }}</span>{{- end -}}
|
|
||||||
</div>
|
|
||||||
{{- end -}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{- if or $item.Location $item.Owner -}}
|
|
||||||
<div class="sr-only">
|
|
||||||
{{- if $item.Location -}}<span>{{ $item.Location }}</span>{{- end -}}
|
|
||||||
{{- if $item.Owner -}}<span class="ml-2">{{ $item.Owner }}</span>{{- end -}}
|
|
||||||
</div>
|
|
||||||
{{- end -}}
|
|
||||||
</div>
|
|
||||||
{{- end -}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{- end -}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -421,32 +195,5 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
document.querySelectorAll('[data-role="baende-row"]').forEach((row) => {
|
|
||||||
row.addEventListener("click", (event) => {
|
|
||||||
if (event.target.closest("a, button, form, tool-tip")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const entryId = row.getAttribute("data-entry-id");
|
|
||||||
if (!entryId) return;
|
|
||||||
const details = document.querySelector(`[data-role="baende-details"][data-entry-id="${entryId}"]`);
|
|
||||||
if (!details) return;
|
|
||||||
row.classList.add("hidden");
|
|
||||||
details.classList.remove("hidden");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
document.querySelectorAll('[data-role="baende-collapse"]').forEach((button) => {
|
|
||||||
button.addEventListener("click", (event) => {
|
|
||||||
event.stopPropagation();
|
|
||||||
const entryId = button.getAttribute("data-entry-id");
|
|
||||||
if (!entryId) return;
|
|
||||||
const row = document.querySelector(`[data-role="baende-row"][data-entry-id="${entryId}"]`);
|
|
||||||
const details = document.querySelector(`[data-role="baende-details"][data-entry-id="${entryId}"]`);
|
|
||||||
if (!row || !details) return;
|
|
||||||
row.classList.remove("hidden");
|
|
||||||
details.classList.add("hidden");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
239
views/routes/baende/details/body.gohtml
Normal file
239
views/routes/baende/details/body.gohtml
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
{{ $result := .result }}
|
||||||
|
{{ $entry := $result.Entry }}
|
||||||
|
<tr class="bg-stone-50/60 border-b last:border-b-0" data-role="baende-details" data-entry-id="{{ $entry.MusenalmID }}">
|
||||||
|
<td class="py-2 pr-4 pl-2 whitespace-nowrap w-[10rem] align-top">
|
||||||
|
<div class="flex flex-col gap-1 pt-1">
|
||||||
|
<span class="inline-flex items-center rounded-xs bg-stone-100 px-2.5 py-0.5 text-xs font-semibold text-slate-700">Alm {{ $entry.MusenalmID }}</span>
|
||||||
|
{{- if $entry.References -}}
|
||||||
|
<span class="inline-flex items-center rounded-xs bg-stone-100 px-2.5 py-0.5 text-xs text-slate-700 max-w-[10rem] whitespace-normal break-words" title="{{ $entry.References }}">{{ $entry.References }}</span>
|
||||||
|
{{- end -}}
|
||||||
|
<div class="flex flex-wrap items-center gap-1.5 pt-2">
|
||||||
|
<tool-tip position="top" class="inline">
|
||||||
|
<a href="/almanach/{{ $entry.MusenalmID }}" class="no-underline inline-flex items-center gap-1 rounded-xs bg-stone-100 px-2 py-1 text-xs font-semibold text-slate-700 hover:bg-stone-200 hover:text-slate-900">
|
||||||
|
<i class="ri-eye-line"></i>
|
||||||
|
</a>
|
||||||
|
<div class="data-tip">Ansehen</div>
|
||||||
|
</tool-tip>
|
||||||
|
{{- if $result.IsAdmin -}}
|
||||||
|
<tool-tip position="top" class="inline">
|
||||||
|
<a href="/almanach/{{ $entry.MusenalmID }}/edit" class="no-underline inline-flex items-center gap-1 rounded-xs bg-stone-100 px-2 py-1 text-xs font-semibold text-slate-700 hover:bg-stone-200 hover:text-slate-900">
|
||||||
|
<i class="ri-edit-line"></i>
|
||||||
|
</a>
|
||||||
|
<div class="data-tip">Bearbeiten</div>
|
||||||
|
</tool-tip>
|
||||||
|
<form method="POST" action="/almanach/{{ $entry.MusenalmID }}/edit/delete" class="inline" onsubmit="return confirm('Band wirklich löschen?');">
|
||||||
|
<input type="hidden" name="csrf_token" value="{{ $result.CSRFToken }}" />
|
||||||
|
<tool-tip position="top" class="inline">
|
||||||
|
<button type="submit" class="inline-flex items-center gap-1 rounded-xs bg-red-50 px-2 py-1 text-xs font-semibold text-red-700 hover:bg-red-100 hover:text-red-900">
|
||||||
|
<i class="ri-delete-bin-line"></i>
|
||||||
|
|
||||||
|
</button>
|
||||||
|
<div class="data-tip">Löschen</div>
|
||||||
|
</tool-tip>
|
||||||
|
</form>
|
||||||
|
{{- end -}}
|
||||||
|
<tool-tip position="top" class="inline">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="inline-flex items-center gap-1 rounded-xs bg-stone-100 px-2 py-1 text-xs font-semibold text-slate-700 hover:bg-stone-200 hover:text-slate-900"
|
||||||
|
hx-get="/baende/row/{{ $entry.MusenalmID }}"
|
||||||
|
hx-target="closest tr"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
>
|
||||||
|
<i class="ri-arrow-up-s-line"></i>
|
||||||
|
</button>
|
||||||
|
<div class="data-tip">Zuklappen</div>
|
||||||
|
</tool-tip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="py-2 pr-4 pl-2" colspan="6">
|
||||||
|
<div class="rounded-md border border-slate-200 bg-white shadow-sm p-3 text-base">
|
||||||
|
<div class="flex items-start justify-between gap-4 mb-3">
|
||||||
|
<div class="font-sans text-base font-semibold text-slate-900 inline-flex items-center">
|
||||||
|
{{- if $entry.PreferredTitle -}}
|
||||||
|
{{ $entry.PreferredTitle }}
|
||||||
|
{{- else if ne $entry.Year 0 -}}
|
||||||
|
{{ $entry.Year }}
|
||||||
|
{{- else -}}
|
||||||
|
[o.J.]
|
||||||
|
{{- end -}}
|
||||||
|
<tool-tip position="top" class="inline-flex items-center">
|
||||||
|
<i class="status-icon ml-1 {{- if eq $entry.EditState "Edited" }} ri-checkbox-circle-line{{- else if eq $entry.EditState "Seen" }} ri-information-line{{- else if eq $entry.EditState "Review" }} ri-search-line{{- else if eq $entry.EditState "ToDo" }} ri-list-check{{- else }} ri-forbid-2-line{{- end }}" data-status="{{ $entry.EditState }}"></i>
|
||||||
|
<div class="data-tip">
|
||||||
|
{{- if eq $entry.EditState "Unknown" -}}
|
||||||
|
Gesucht
|
||||||
|
{{- else if eq $entry.EditState "ToDo" -}}
|
||||||
|
Zu erledigen
|
||||||
|
{{- else if eq $entry.EditState "Review" -}}
|
||||||
|
Überprüfen
|
||||||
|
{{- else if eq $entry.EditState "Seen" -}}
|
||||||
|
Autopsiert
|
||||||
|
{{- else if eq $entry.EditState "Edited" -}}
|
||||||
|
Vollständig Erfasst
|
||||||
|
{{- else -}}
|
||||||
|
{{ $entry.EditState }}
|
||||||
|
{{- end -}}
|
||||||
|
</div>
|
||||||
|
</tool-tip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-[1fr_18rem] gap-6 text-sm font-sans text-gray-700">
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
{{- if or $entry.TitleStmt $entry.SubtitleStmt $entry.VariantTitle $entry.ParallelTitle $entry.IncipitStmt -}}
|
||||||
|
<div>
|
||||||
|
<div class="text-xs uppercase tracking-wide text-gray-500">Titelangaben</div>
|
||||||
|
<div>
|
||||||
|
{{- if $entry.TitleStmt -}}<div><span class="font-semibold text-gray-500">Titel:</span> {{ $entry.TitleStmt }}</div>{{- end -}}
|
||||||
|
{{- if $entry.SubtitleStmt -}}<div><span class="font-semibold text-gray-500">Untertitel:</span> {{ $entry.SubtitleStmt }}</div>{{- end -}}
|
||||||
|
{{- if $entry.VariantTitle -}}<div><span class="font-semibold text-gray-500">Varianten:</span> {{ $entry.VariantTitle }}</div>{{- end -}}
|
||||||
|
{{- if $entry.ParallelTitle -}}<div><span class="font-semibold text-gray-500">Parallel:</span> {{ $entry.ParallelTitle }}</div>{{- end -}}
|
||||||
|
{{- if $entry.IncipitStmt -}}<div><span class="font-semibold text-gray-500">Incipit:</span> {{ $entry.IncipitStmt }}</div>{{- end -}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
|
{{- if or $entry.ResponsibilityStmt $entry.PublicationStmt $entry.PlaceStmt -}}
|
||||||
|
<div>
|
||||||
|
<div class="text-xs uppercase tracking-wide text-gray-500">Erscheinung</div>
|
||||||
|
<div>
|
||||||
|
{{- if and $entry.ResponsibilityStmt (not (eq $entry.ResponsibilityStmt "unbezeichnet")) -}}<div><span class="font-semibold text-gray-500">Herausgaberangabe:</span> {{ $entry.ResponsibilityStmt }}</div>{{- end -}}
|
||||||
|
{{- if $entry.PublicationStmt -}}<div><span class="font-semibold text-gray-500">Publikationsangabe:</span> {{ $entry.PublicationStmt }}</div>{{- end -}}
|
||||||
|
{{- if $entry.PlaceStmt -}}<div><span class="font-semibold text-gray-500">Ortsangabe:</span> {{ $entry.PlaceStmt }}</div>{{- end -}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $entry.Annotation -}}
|
||||||
|
<div>
|
||||||
|
<div class="text-xs uppercase tracking-wide text-gray-500">Annotation</div>
|
||||||
|
<div>{{ $entry.Annotation }}</div>
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $result.SeriesRels -}}
|
||||||
|
<div>
|
||||||
|
<div class="text-xs uppercase tracking-wide text-gray-500">Reihen</div>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
{{- range $_, $rel := $result.SeriesRels -}}
|
||||||
|
{{- range $_, $s := $result.Series -}}
|
||||||
|
{{- if eq $s.Id $rel.Series -}}
|
||||||
|
<div>
|
||||||
|
{{- if not (eq $rel.Type "Bevorzugter Reihentitel") -}}
|
||||||
|
<span class="mr-2 px-2 py-0.5 rounded text-xs font-sans bg-slate-200 inline-block">{{ $rel.Type }}</span>
|
||||||
|
{{- end -}}
|
||||||
|
<a href="/reihe/{{ $s.MusenalmID }}">{{ $s.Title }}</a>
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $entry.Places -}}
|
||||||
|
<div>
|
||||||
|
<div class="text-xs uppercase tracking-wide text-gray-500">Orte</div>
|
||||||
|
<div>
|
||||||
|
{{- $sep := false -}}
|
||||||
|
{{- range $_, $p := $result.Places -}}
|
||||||
|
{{- if $sep }}, {{ end -}}
|
||||||
|
<a href="/reihen?place={{ $p.Id }}&hidden=true">{{ $p.Name }}</a>
|
||||||
|
{{- $sep = true -}}
|
||||||
|
{{- end -}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $result.AgentRels -}}
|
||||||
|
<div>
|
||||||
|
<div class="text-xs uppercase tracking-wide text-gray-500">Personen</div>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
{{- range $_, $arel := $result.AgentRels -}}
|
||||||
|
{{- range $_, $a := $result.Agents -}}
|
||||||
|
{{- if eq $a.Id $arel.Agent -}}
|
||||||
|
<div>
|
||||||
|
<a href="/person/{{ $a.Id }}">{{ $a.Name }}</a>
|
||||||
|
<span class="ml-2 px-2 py-0.5 rounded text-xs font-sans bg-slate-200 inline-block">{{ $arel.Type }}</span>
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
{{- if $entry.Comment -}}
|
||||||
|
<div>
|
||||||
|
<div class="text-xs uppercase tracking-wide text-gray-500">Kommentar</div>
|
||||||
|
<div>{{ $entry.Comment }}</div>
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $entry.Language -}}
|
||||||
|
<div>
|
||||||
|
<div class="text-xs uppercase tracking-wide text-gray-500">Sprachen</div>
|
||||||
|
<div>
|
||||||
|
{{- range $i, $lang := $entry.Language -}}{{- if $i }}, {{ end -}}{{ LanguageNameGerman $lang }}{{- end -}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
|
{{- if or $entry.Extent $entry.Dimensions -}}
|
||||||
|
<div>
|
||||||
|
<div class="text-xs uppercase tracking-wide text-gray-500">Umfang / Maße</div>
|
||||||
|
<div>
|
||||||
|
{{- if $entry.Extent -}}<div><span class="font-semibold text-gray-500 block">Struktur</span>{{ $entry.Extent }}</div>{{- end -}}
|
||||||
|
{{- if $entry.Dimensions -}}<div><span class="font-semibold text-gray-500">Maße:</span> {{ $entry.Dimensions }}</div>{{- end -}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $entry.References -}}
|
||||||
|
<div>
|
||||||
|
<div class="text-xs uppercase tracking-wide text-gray-500">Nachweise</div>
|
||||||
|
<div>{{ $entry.References }}</div>
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
|
{{- if ne $entry.Year 0 -}}
|
||||||
|
<div>
|
||||||
|
<div class="text-xs uppercase tracking-wide text-gray-500">Jahr</div>
|
||||||
|
<div>{{ $entry.Year }}</div>
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $result.Items -}}
|
||||||
|
<div>
|
||||||
|
<div class="text-xs uppercase tracking-wide text-gray-500">Exemplare</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
{{- range $_, $item := $result.Items -}}
|
||||||
|
<div class="rounded-md border border-slate-200 bg-white px-3 py-2 shadow-sm">
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<div class="inline-flex flex-col items-center justify-center rounded-xs border border-slate-200 bg-white text-xs min-w-[6rem]">
|
||||||
|
{{- if $item.Identifier -}}
|
||||||
|
<div class="px-2 py-1 font-semibold text-slate-900 text-center">{{ $item.Identifier }}</div>
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $item.Media -}}
|
||||||
|
<div class="w-full border-t border-slate-200 px-2 py-1 text-gray-600 text-center leading-snug">
|
||||||
|
{{- range $i, $media := $item.Media -}}{{- if $i }}, {{ end -}}{{ $media }}{{- end -}}
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
{{- if or $item.Location $item.Owner -}}
|
||||||
|
<div class="text-sm text-gray-600">
|
||||||
|
{{- if $item.Location -}}<span>{{ $item.Location }}</span>{{- end -}}
|
||||||
|
{{- if $item.Owner -}}<span class="ml-2">{{ $item.Owner }}</span>{{- end -}}
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{- if or $item.Location $item.Owner -}}
|
||||||
|
<div class="sr-only">
|
||||||
|
{{- if $item.Location -}}<span>{{ $item.Location }}</span>{{- end -}}
|
||||||
|
{{- if $item.Owner -}}<span class="ml-2">{{ $item.Owner }}</span>{{- end -}}
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<script src="/assets/js/collapse.js"></script>
|
||||||
0
views/routes/baende/details/head.gohtml
Normal file
0
views/routes/baende/details/head.gohtml
Normal file
160
views/routes/baende/row/body.gohtml
Normal file
160
views/routes/baende/row/body.gohtml
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
{{ $entry := .entry }}
|
||||||
|
{{ $model := . }}
|
||||||
|
<tr class="border-b align-top cursor-pointer transition-colors odd:bg-white even:bg-stone-50/60 hover:bg-stone-100"
|
||||||
|
data-role="baende-row"
|
||||||
|
data-entry-id="{{ $entry.MusenalmID }}"
|
||||||
|
hx-get="/baende/details/{{ $entry.MusenalmID }}"
|
||||||
|
hx-target="this"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
>
|
||||||
|
<td class="py-2 pr-4 pl-2 whitespace-nowrap w-[10rem]">
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<span class="inline-flex items-center rounded-xs bg-stone-100 px-2.5 py-0.5 text-xs font-semibold text-slate-700">Alm {{ $entry.MusenalmID }}</span>
|
||||||
|
{{- if $entry.References -}}
|
||||||
|
<span class="inline-flex items-center rounded-xs bg-stone-100 px-2.5 py-0.5 text-xs text-slate-700 max-w-[10rem] whitespace-normal break-words" title="{{ $entry.References }}">{{ $entry.References }}</span>
|
||||||
|
{{- end -}}
|
||||||
|
<div class="flex flex-wrap items-center gap-1.5 pt-1">
|
||||||
|
<tool-tip position="top" class="inline">
|
||||||
|
<a href="/almanach/{{ $entry.MusenalmID }}" class="no-underline inline-flex items-center gap-1 rounded-xs bg-stone-100 px-2 py-1 text-xs font-semibold text-slate-700 hover:bg-stone-200 hover:text-slate-900">
|
||||||
|
<i class="ri-eye-line"></i>
|
||||||
|
</a>
|
||||||
|
<div class="data-tip">Ansehen</div>
|
||||||
|
</tool-tip>
|
||||||
|
{{- if .is_admin -}}
|
||||||
|
<tool-tip position="top" class="inline">
|
||||||
|
<a href="/almanach/{{ $entry.MusenalmID }}/edit" class="no-underline inline-flex items-center gap-1 rounded-xs bg-stone-100 px-2 py-1 text-xs font-semibold text-slate-700 hover:bg-stone-200 hover:text-slate-900">
|
||||||
|
<i class="ri-edit-line"></i>
|
||||||
|
</a>
|
||||||
|
<div class="data-tip">Bearbeiten</div>
|
||||||
|
</tool-tip>
|
||||||
|
<form method="POST" action="/almanach/{{ $entry.MusenalmID }}/edit/delete" class="inline" onsubmit="return confirm('Band wirklich löschen?');">
|
||||||
|
<input type="hidden" name="csrf_token" value="{{ $model.csrf_token }}" />
|
||||||
|
<tool-tip position="top" class="inline">
|
||||||
|
<button type="submit" class="inline-flex items-center gap-1 rounded-xs bg-red-50 px-2 py-1 text-xs font-semibold text-red-700 hover:bg-red-100 hover:text-red-900">
|
||||||
|
<i class="ri-delete-bin-line"></i>
|
||||||
|
</button>
|
||||||
|
<div class="data-tip">Löschen</div>
|
||||||
|
</tool-tip>
|
||||||
|
</form>
|
||||||
|
{{- end -}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="py-2 pr-4">
|
||||||
|
<div class="font-semibold text-slate-900 text-base leading-snug">
|
||||||
|
{{- if $entry.PreferredTitle -}}
|
||||||
|
{{ $entry.PreferredTitle }}
|
||||||
|
{{- else if ne $entry.Year 0 -}}
|
||||||
|
{{ $entry.Year }}
|
||||||
|
{{- else -}}
|
||||||
|
[o.J.]
|
||||||
|
{{- end -}}
|
||||||
|
<tool-tip position="top" class="inline">
|
||||||
|
<i class="status-icon ml-1 align-middle {{- if eq $entry.EditState "Edited" }} ri-checkbox-circle-line{{- else if eq $entry.EditState "Seen" }} ri-information-line{{- else if eq $entry.EditState "Review" }} ri-search-line{{- else if eq $entry.EditState "ToDo" }} ri-list-check{{- else }} ri-forbid-2-line{{- end }}" data-status="{{ $entry.EditState }}"></i>
|
||||||
|
<div class="data-tip">
|
||||||
|
{{- if eq $entry.EditState "Unknown" -}}
|
||||||
|
Gesucht
|
||||||
|
{{- else if eq $entry.EditState "ToDo" -}}
|
||||||
|
Zu erledigen
|
||||||
|
{{- else if eq $entry.EditState "Review" -}}
|
||||||
|
Überprüfen
|
||||||
|
{{- else if eq $entry.EditState "Seen" -}}
|
||||||
|
Autopsiert
|
||||||
|
{{- else if eq $entry.EditState "Edited" -}}
|
||||||
|
Vollständig Erfasst
|
||||||
|
{{- else -}}
|
||||||
|
{{ $entry.EditState }}
|
||||||
|
{{- end -}}
|
||||||
|
</div>
|
||||||
|
</tool-tip>
|
||||||
|
</div>
|
||||||
|
{{- if $entry.TitleStmt -}}
|
||||||
|
<div class="text-gray-700 text-base mt-1 leading-snug">
|
||||||
|
{{ $entry.TitleStmt }}
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
|
{{- if or $entry.SubtitleStmt $entry.VariantTitle $entry.ParallelTitle $entry.IncipitStmt -}}
|
||||||
|
<div class="flex flex-col gap-1 text-sm text-gray-700 mt-1">
|
||||||
|
{{- if $entry.SubtitleStmt -}}
|
||||||
|
<div><span class="font-semibold text-gray-500">Untertitel:</span> {{ $entry.SubtitleStmt }}</div>
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $entry.VariantTitle -}}
|
||||||
|
<div><span class="font-semibold text-gray-500">Varianten:</span> {{ $entry.VariantTitle }}</div>
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $entry.ParallelTitle -}}
|
||||||
|
<div><span class="font-semibold text-gray-500">Parallel:</span> {{ $entry.ParallelTitle }}</div>
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $entry.IncipitStmt -}}
|
||||||
|
<div><span class="font-semibold text-gray-500">Incipit:</span> {{ $entry.IncipitStmt }}</div>
|
||||||
|
{{- end -}}
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
|
</td>
|
||||||
|
<td class="py-2 pr-4 col-appearance">
|
||||||
|
{{- if or $entry.ResponsibilityStmt $entry.PublicationStmt $entry.PlaceStmt -}}
|
||||||
|
<div class="flex flex-col gap-2 text-sm text-gray-700">
|
||||||
|
{{- if and $entry.ResponsibilityStmt (not (eq $entry.ResponsibilityStmt "unbezeichnet")) -}}
|
||||||
|
<div>
|
||||||
|
<div class="text-xs font-semibold text-gray-500">Herausgaberangabe</div>
|
||||||
|
<div>{{ $entry.ResponsibilityStmt }}</div>
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $entry.PublicationStmt -}}
|
||||||
|
<div>
|
||||||
|
<div class="text-xs font-semibold text-gray-500">Publikationsangabe</div>
|
||||||
|
<div>{{ $entry.PublicationStmt }}</div>
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $entry.PlaceStmt -}}
|
||||||
|
<div>
|
||||||
|
<div class="text-xs font-semibold text-gray-500">Ortsangabe</div>
|
||||||
|
<div>{{ $entry.PlaceStmt }}</div>
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
|
</td>
|
||||||
|
<td class="py-2 pr-4 whitespace-nowrap col-year hidden">
|
||||||
|
{{- if ne $entry.Year 0 -}}
|
||||||
|
{{ $entry.Year }}
|
||||||
|
{{- end -}}
|
||||||
|
</td>
|
||||||
|
<td class="py-2 pr-4 col-language hidden">
|
||||||
|
{{- if $entry.Language -}}
|
||||||
|
{{- range $i, $lang := $entry.Language -}}
|
||||||
|
{{- if $i }}, {{ end -}}{{ LanguageNameGerman $lang }}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
</td>
|
||||||
|
<td class="py-2 pr-4 col-extent">
|
||||||
|
{{- if or $entry.Extent $entry.Dimensions -}}
|
||||||
|
<div class="flex flex-col gap-1 text-sm text-gray-700">
|
||||||
|
{{- if $entry.Extent -}}
|
||||||
|
<div><span class="font-semibold text-gray-500 block">Struktur</span>{{ $entry.Extent }}</div>
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $entry.Dimensions -}}
|
||||||
|
<div><span class="font-semibold text-gray-500">Maße:</span> {{ $entry.Dimensions }}</div>
|
||||||
|
{{- end -}}
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
|
</td>
|
||||||
|
<td class="py-2 pr-4 col-signatures">
|
||||||
|
{{- $items := .items -}}
|
||||||
|
{{- if $items -}}
|
||||||
|
<div class="flex flex-col gap-2 text-sm text-gray-700">
|
||||||
|
{{- range $_, $item := $items -}}
|
||||||
|
<div class="inline-flex flex-col items-center justify-center rounded-xs border border-slate-200 bg-white text-xs">
|
||||||
|
{{- if $item.Identifier -}}
|
||||||
|
<div class="px-2 py-1 font-semibold text-slate-900 text-center">{{ $item.Identifier }}</div>
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $item.Media -}}
|
||||||
|
<div class="w-full border-t border-slate-200 px-2 py-1 text-gray-600 text-center leading-snug">
|
||||||
|
{{- range $i, $media := $item.Media -}}{{- if $i }}, {{ end -}}{{ $media }}{{- end -}}
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
0
views/routes/baende/row/head.gohtml
Normal file
0
views/routes/baende/row/head.gohtml
Normal file
Reference in New Issue
Block a user