mirror of
https://github.com/Theodor-Springmann-Stiftung/musenalm.git
synced 2026-02-04 02:25:30 +00:00
+Create new beitrag
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/Theodor-Springmann-Stiftung/musenalm/app"
|
"github.com/Theodor-Springmann-Stiftung/musenalm/app"
|
||||||
"github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels"
|
"github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels"
|
||||||
@@ -22,6 +23,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
URL_ALMANACH_CONTENTS_EDIT = "contents/edit"
|
URL_ALMANACH_CONTENTS_EDIT = "contents/edit"
|
||||||
|
URL_ALMANACH_CONTENTS_NEW = "contents/new"
|
||||||
URL_ALMANACH_CONTENTS_ITEM_EDIT = "contents/{contentMusenalmId}/edit"
|
URL_ALMANACH_CONTENTS_ITEM_EDIT = "contents/{contentMusenalmId}/edit"
|
||||||
URL_ALMANACH_CONTENTS_DELETE = "contents/delete"
|
URL_ALMANACH_CONTENTS_DELETE = "contents/delete"
|
||||||
URL_ALMANACH_CONTENTS_EDIT_EXTENT = "contents/edit/extent"
|
URL_ALMANACH_CONTENTS_EDIT_EXTENT = "contents/edit/extent"
|
||||||
@@ -53,6 +55,7 @@ func (p *AlmanachContentsEditPage) Setup(router *router.Router[*core.RequestEven
|
|||||||
rg := router.Group(URL_ALMANACH)
|
rg := router.Group(URL_ALMANACH)
|
||||||
rg.BindFunc(middleware.IsAdminOrEditor())
|
rg.BindFunc(middleware.IsAdminOrEditor())
|
||||||
rg.GET(URL_ALMANACH_CONTENTS_EDIT, p.GET(engine, app))
|
rg.GET(URL_ALMANACH_CONTENTS_EDIT, p.GET(engine, app))
|
||||||
|
rg.GET(URL_ALMANACH_CONTENTS_NEW, p.GETNew(engine, app))
|
||||||
rg.GET(URL_ALMANACH_CONTENTS_ITEM_EDIT, p.GETItemEdit(engine, app))
|
rg.GET(URL_ALMANACH_CONTENTS_ITEM_EDIT, p.GETItemEdit(engine, app))
|
||||||
rg.POST(URL_ALMANACH_CONTENTS_EDIT, p.POSTSave(engine, app))
|
rg.POST(URL_ALMANACH_CONTENTS_EDIT, p.POSTSave(engine, app))
|
||||||
rg.POST(URL_ALMANACH_CONTENTS_DELETE, p.POSTDelete(engine, app))
|
rg.POST(URL_ALMANACH_CONTENTS_DELETE, p.POSTDelete(engine, app))
|
||||||
@@ -164,6 +167,43 @@ func (p *AlmanachContentsEditPage) GETItemEdit(engine *templating.Engine, app co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *AlmanachContentsEditPage) GETNew(engine *templating.Engine, app core.App) HandleFunc {
|
||||||
|
return func(e *core.RequestEvent) error {
|
||||||
|
id := e.Request.PathValue("id")
|
||||||
|
req := templating.NewRequest(e)
|
||||||
|
data := make(map[string]any)
|
||||||
|
result, err := NewAlmanachEditResult(app, id, BeitraegeFilterParameters{})
|
||||||
|
if err != nil {
|
||||||
|
engine.Response404(e, err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
contentCollection, err := app.FindCollectionByNameOrId(dbmodels.CONTENTS_TABLE)
|
||||||
|
if err != nil {
|
||||||
|
return engine.Response404(e, err, nil)
|
||||||
|
}
|
||||||
|
record := core.NewRecord(contentCollection)
|
||||||
|
tempID := fmt.Sprintf("tmp-%d", time.Now().UnixNano())
|
||||||
|
record.Id = tempID
|
||||||
|
content := dbmodels.NewContent(record)
|
||||||
|
content.SetEntry(result.Entry.Id)
|
||||||
|
content.SetEditState("Edited")
|
||||||
|
|
||||||
|
data["result"] = result
|
||||||
|
data["csrf_token"] = req.Session().Token
|
||||||
|
data["content"] = content
|
||||||
|
data["content_id"] = content.Id
|
||||||
|
data["content_types"] = dbmodels.CONTENT_TYPE_VALUES
|
||||||
|
data["musenalm_types"] = dbmodels.MUSENALM_TYPE_VALUES
|
||||||
|
data["pagination_values"] = paginationValuesSorted()
|
||||||
|
data["agent_relations"] = dbmodels.AGENT_RELATIONS
|
||||||
|
data["agents"] = map[string]*dbmodels.Agent{}
|
||||||
|
data["content_agents"] = []*dbmodels.RContentsAgents{}
|
||||||
|
data["is_new"] = true
|
||||||
|
|
||||||
|
return engine.Response200(e, TEMPLATE_ALMANACH_CONTENTS_ITEM_EDIT, data, p.Layout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (p *AlmanachContentsEditPage) renderError(engine *templating.Engine, app core.App, e *core.RequestEvent, message string) error {
|
func (p *AlmanachContentsEditPage) renderError(engine *templating.Engine, app core.App, e *core.RequestEvent, message string) error {
|
||||||
id := e.Request.PathValue("id")
|
id := e.Request.PathValue("id")
|
||||||
req := templating.NewRequest(e)
|
req := templating.NewRequest(e)
|
||||||
@@ -331,6 +371,8 @@ func (p *AlmanachContentsEditPage) POSTSave(engine *templating.Engine, app core.
|
|||||||
contentID = targetContentID
|
contentID = targetContentID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tempToCreated := map[string]string{}
|
||||||
|
var createdContents []*dbmodels.Content
|
||||||
var updatedContents []*dbmodels.Content
|
var updatedContents []*dbmodels.Content
|
||||||
if err := app.RunInTransaction(func(tx core.App) error {
|
if err := app.RunInTransaction(func(tx core.App) error {
|
||||||
if len(orderMap) > 0 {
|
if len(orderMap) > 0 {
|
||||||
@@ -381,6 +423,7 @@ func (p *AlmanachContentsEditPage) POSTSave(engine *templating.Engine, app core.
|
|||||||
if err := tx.Save(content); err != nil {
|
if err := tx.Save(content); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
tempToCreated[tempID] = content.Id
|
||||||
if relations, ok := relationsByContent[tempID]; ok {
|
if relations, ok := relationsByContent[tempID]; ok {
|
||||||
if err := applyContentAgentRelations(tx, content, relations); err != nil {
|
if err := applyContentAgentRelations(tx, content, relations); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -406,8 +449,12 @@ func (p *AlmanachContentsEditPage) POSTSave(engine *templating.Engine, app core.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if targetContentID != "" && (len(uploadedScans) > 0 || len(deleteScans) > 0 || len(scansOrder) > 0) {
|
effectiveContentID := targetContentID
|
||||||
record, err := tx.FindRecordById(dbmodels.CONTENTS_TABLE, targetContentID)
|
if mappedID, ok := tempToCreated[effectiveContentID]; ok {
|
||||||
|
effectiveContentID = mappedID
|
||||||
|
}
|
||||||
|
if effectiveContentID != "" && (len(uploadedScans) > 0 || len(deleteScans) > 0 || len(scansOrder) > 0) {
|
||||||
|
record, err := tx.FindRecordById(dbmodels.CONTENTS_TABLE, effectiveContentID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -492,6 +539,7 @@ func (p *AlmanachContentsEditPage) POSTSave(engine *templating.Engine, app core.
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
createdContents = append(createdContents, created...)
|
||||||
updatedContents = append(updatedContents, contents...)
|
updatedContents = append(updatedContents, contents...)
|
||||||
updatedContents = append(updatedContents, created...)
|
updatedContents = append(updatedContents, created...)
|
||||||
return nil
|
return nil
|
||||||
@@ -507,8 +555,19 @@ func (p *AlmanachContentsEditPage) POSTSave(engine *templating.Engine, app core.
|
|||||||
if shouldUpdateFTS {
|
if shouldUpdateFTS {
|
||||||
touched := updatedContents
|
touched := updatedContents
|
||||||
if len(contentInputs) > 0 {
|
if len(contentInputs) > 0 {
|
||||||
|
tempToCreated := map[string]string{}
|
||||||
|
for idx, tempID := range newContentIDs {
|
||||||
|
if idx >= len(createdContents) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
tempToCreated[tempID] = createdContents[idx].Id
|
||||||
|
}
|
||||||
touchedIDs := map[string]struct{}{}
|
touchedIDs := map[string]struct{}{}
|
||||||
for id := range contentInputs {
|
for id := range contentInputs {
|
||||||
|
if createdID, ok := tempToCreated[id]; ok {
|
||||||
|
touchedIDs[createdID] = struct{}{}
|
||||||
|
continue
|
||||||
|
}
|
||||||
touchedIDs[id] = struct{}{}
|
touchedIDs[id] = struct{}{}
|
||||||
}
|
}
|
||||||
filtered := make([]*dbmodels.Content, 0, len(touchedIDs))
|
filtered := make([]*dbmodels.Content, 0, len(touchedIDs))
|
||||||
@@ -644,9 +703,7 @@ func (p *AlmanachContentsEditPage) POSTDelete(engine *templating.Engine, app cor
|
|||||||
_ = dbmodels.DeleteFTS5Content(app, contentID)
|
_ = dbmodels.DeleteFTS5Content(app, contentID)
|
||||||
}(contentID)
|
}(contentID)
|
||||||
|
|
||||||
if len(remaining) > 0 {
|
// Only delete the FTS5 record for the removed content.
|
||||||
go updateContentsFTS5(app, entry, remaining)
|
|
||||||
}
|
|
||||||
|
|
||||||
if isHTMX {
|
if isHTMX {
|
||||||
success := `<div hx-swap-oob="innerHTML:#user-message"><div class="text-green-800 text-sm mt-2 rounded-xs bg-green-200 p-2 font-bold border-green-700 shadow border mb-3"><i class="ri-checkbox-circle-fill"></i> Beitrag geloescht.</div></div>`
|
success := `<div hx-swap-oob="innerHTML:#user-message"><div class="text-green-800 text-sm mt-2 rounded-xs bg-green-200 p-2 font-bold border-green-700 shadow border mb-3"><i class="ri-checkbox-circle-fill"></i> Beitrag geloescht.</div></div>`
|
||||||
|
|||||||
@@ -5881,7 +5881,9 @@ const ft = class ft extends HTMLElement {
|
|||||||
"transition-all",
|
"transition-all",
|
||||||
"duration-200",
|
"duration-200",
|
||||||
"font-sans"
|
"font-sans"
|
||||||
].join(" "), this.appendChild(this._tooltipBox), this._updatePosition(), this.addEventListener("mouseenter", () => this._showTooltip()), this.addEventListener("mouseleave", () => this._hideTooltip()), this._dataTipElem && (this._observer = new MutationObserver(() => {
|
].join(" "), this.appendChild(this._tooltipBox), this._updatePosition(), this.addEventListener("mouseenter", () => this._showTooltip()), this.addEventListener("mouseleave", () => this._hideTooltip()), this.addEventListener("pointerdown", () => this._forceHide()), this.addEventListener("mousedown", () => this._forceHide()), this.addEventListener("click", () => this._forceHide()), this.addEventListener("keydown", (e) => {
|
||||||
|
(e.key === "Enter" || e.key === " ") && this._forceHide();
|
||||||
|
}), this._dataTipElem && (this._observer = new MutationObserver(() => {
|
||||||
this._tooltipBox && (this._tooltipBox.innerHTML = this._dataTipElem.innerHTML);
|
this._tooltipBox && (this._tooltipBox.innerHTML = this._dataTipElem.innerHTML);
|
||||||
}), this._observer.observe(this._dataTipElem, {
|
}), this._observer.observe(this._dataTipElem, {
|
||||||
childList: !0,
|
childList: !0,
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -177,6 +177,13 @@
|
|||||||
<span id="content-filter-count" class="text-sm text-gray-600 whitespace-nowrap ml-auto" data-role="content-filter-count">
|
<span id="content-filter-count" class="text-sm text-gray-600 whitespace-nowrap ml-auto" data-role="content-filter-count">
|
||||||
{{ len $model.result.Contents }} Einträge
|
{{ len $model.result.Contents }} Einträge
|
||||||
</span>
|
</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="content-action-button"
|
||||||
|
onclick="window.location.assign('/almanach/{{ $model.result.Entry.MusenalmID }}/contents/new')">
|
||||||
|
<i class="ri-add-line"></i>
|
||||||
|
<span>Neuer Beitrag</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-0 mt-2"
|
<div class="flex flex-col gap-0 mt-2"
|
||||||
data-role="contents-list"
|
data-role="contents-list"
|
||||||
@@ -197,6 +204,15 @@
|
|||||||
) -}}
|
) -}}
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="px-4 py-2 mt-2 flex items-center">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="content-action-button"
|
||||||
|
onclick="window.location.assign('/almanach/{{ $model.result.Entry.MusenalmID }}/contents/new')">
|
||||||
|
<i class="ri-add-line"></i>
|
||||||
|
<span>Neuer Beitrag</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<dialog id="content-delete-dialog" class="dbform fixed inset-0 m-auto rounded-md border border-slate-200 p-0 shadow-xl backdrop:bg-black/40">
|
<dialog id="content-delete-dialog" class="dbform fixed inset-0 m-auto rounded-md border border-slate-200 p-0 shadow-xl backdrop:bg-black/40">
|
||||||
|
|||||||
@@ -10,13 +10,18 @@
|
|||||||
<h1 class="text-2xl w-full font-bold text-slate-900 mb-1">
|
<h1 class="text-2xl w-full font-bold text-slate-900 mb-1">
|
||||||
{{- if $model.result -}}
|
{{- if $model.result -}}
|
||||||
{{- $model.result.Entry.PreferredTitle -}}<br>
|
{{- $model.result.Entry.PreferredTitle -}}<br>
|
||||||
<span class="text-base font-semibold text-slate-700">Beitrag Nr. {{ $model.content.MusenalmID }}</span>
|
{{- if $model.is_new -}}
|
||||||
|
<span class="text-base font-semibold text-slate-700">Neuer Beitrag</span>
|
||||||
|
{{- else -}}
|
||||||
|
<span class="text-base font-semibold text-slate-700">Beitrag Nr. {{ $model.content.MusenalmID }}</span>
|
||||||
|
{{- end -}}
|
||||||
{{- else -}}
|
{{- else -}}
|
||||||
Beiträge bearbeiten
|
Beiträge bearbeiten
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
</h1>
|
</h1>
|
||||||
{{- if $model.result -}}
|
{{- if $model.result -}}
|
||||||
<div class="flex flex-row gap-x-3">
|
<div class="flex flex-row gap-x-3">
|
||||||
|
{{- if not $model.is_new -}}
|
||||||
<div>
|
<div>
|
||||||
<a
|
<a
|
||||||
href="/beitrag/{{ $model.content.MusenalmID }}"
|
href="/beitrag/{{ $model.content.MusenalmID }}"
|
||||||
@@ -51,6 +56,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
·
|
·
|
||||||
|
{{- end -}}
|
||||||
<div>
|
<div>
|
||||||
<a href="/almanach/{{- $model.result.Entry.MusenalmID -}}/contents/edit" class="text-gray-700 no-underline hover:text-slate-950 block">
|
<a href="/almanach/{{- $model.result.Entry.MusenalmID -}}/contents/edit" class="text-gray-700 no-underline hover:text-slate-950 block">
|
||||||
<i class="ri-arrow-left-line"></i> Zurück zur Liste
|
<i class="ri-arrow-left-line"></i> Zurück zur Liste
|
||||||
@@ -73,6 +79,7 @@
|
|||||||
<i class="ri-navigation-line"></i> Navigation
|
<i class="ri-navigation-line"></i> Navigation
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
|
{{- if not $model.is_new -}}
|
||||||
{{- if $model.prev_content -}}
|
{{- if $model.prev_content -}}
|
||||||
<tool-tip position="top" class="!inline">
|
<tool-tip position="top" class="!inline">
|
||||||
<div class="data-tip">
|
<div class="data-tip">
|
||||||
@@ -112,6 +119,9 @@
|
|||||||
</a>
|
</a>
|
||||||
</tool-tip>
|
</tool-tip>
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
|
{{- else -}}
|
||||||
|
<span class="text-gray-800 font-bold no-underline">Neu</span>
|
||||||
|
{{- end -}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -154,6 +164,7 @@
|
|||||||
hx-boost="false"
|
hx-boost="false"
|
||||||
action="/almanach/{{ $model.result.Entry.MusenalmID }}/contents/edit">
|
action="/almanach/{{ $model.result.Entry.MusenalmID }}/contents/edit">
|
||||||
<input type="hidden" name="csrf_token" value="{{ $model.csrf_token }}" />
|
<input type="hidden" name="csrf_token" value="{{ $model.csrf_token }}" />
|
||||||
|
<input type="hidden" name="content_id" value="{{ $model.content_id }}" />
|
||||||
|
|
||||||
<div class="flex gap-8">
|
<div class="flex gap-8">
|
||||||
<div class="flex-1 flex flex-col gap-4">
|
<div class="flex-1 flex flex-col gap-4">
|
||||||
@@ -227,10 +238,12 @@
|
|||||||
<i class="ri-arrow-left-line"></i>
|
<i class="ri-arrow-left-line"></i>
|
||||||
<span>Zurück</span>
|
<span>Zurück</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="content-delete">
|
{{- if not $model.is_new -}}
|
||||||
<i class="ri-delete-bin-line"></i>
|
<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="content-delete">
|
||||||
<span>Eintrag loeschen</span>
|
<i class="ri-delete-bin-line"></i>
|
||||||
</button>
|
<span>Eintrag loeschen</span>
|
||||||
|
</button>
|
||||||
|
{{- end -}}
|
||||||
<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>
|
||||||
@@ -304,59 +317,58 @@
|
|||||||
initMultiSelects();
|
initMultiSelects();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!deleteButton || !deleteDialog || !deleteConfirm || !deleteCancel) {
|
if (deleteButton && deleteDialog && deleteConfirm && deleteCancel) {
|
||||||
return;
|
deleteButton.addEventListener("click", () => {
|
||||||
}
|
if (deleteDialog.showModal) {
|
||||||
|
deleteDialog.showModal();
|
||||||
deleteButton.addEventListener("click", () => {
|
} else {
|
||||||
if (deleteDialog.showModal) {
|
deleteDialog.setAttribute("open", "true");
|
||||||
deleteDialog.showModal();
|
|
||||||
} else {
|
|
||||||
deleteDialog.setAttribute("open", "true");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
deleteCancel.addEventListener("click", () => {
|
|
||||||
deleteDialog.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
deleteDialog.addEventListener("cancel", (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
deleteDialog.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
deleteConfirm.addEventListener("click", () => {
|
|
||||||
const payload = new URLSearchParams();
|
|
||||||
payload.set("csrf_token", csrfToken);
|
|
||||||
payload.set("content_id", contentId);
|
|
||||||
fetch(deleteEndpoint, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
|
||||||
},
|
|
||||||
body: payload.toString(),
|
|
||||||
}).then((response) => {
|
|
||||||
if (response.ok) {
|
|
||||||
window.location.assign("/almanach/{{ $model.result.Entry.MusenalmID }}/contents/edit");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
deleteDialog.close();
|
});
|
||||||
}).catch(() => {
|
|
||||||
|
deleteCancel.addEventListener("click", () => {
|
||||||
deleteDialog.close();
|
deleteDialog.close();
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
deleteDialog.addEventListener("cancel", (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
deleteDialog.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
deleteConfirm.addEventListener("click", () => {
|
||||||
|
const payload = new URLSearchParams();
|
||||||
|
payload.set("csrf_token", csrfToken);
|
||||||
|
payload.set("content_id", contentId);
|
||||||
|
fetch(deleteEndpoint, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
},
|
||||||
|
body: payload.toString(),
|
||||||
|
}).then((response) => {
|
||||||
|
if (response.ok) {
|
||||||
|
window.location.assign("/almanach/{{ $model.result.Entry.MusenalmID }}/contents/edit");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
deleteDialog.close();
|
||||||
|
}).catch(() => {
|
||||||
|
deleteDialog.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const form = document.querySelector("form.dbform");
|
const form = document.querySelector("form.dbform");
|
||||||
const uploadInput = document.querySelector("[data-role='content-images-upload-input']");
|
const uploadInput = document.querySelector("[data-role='content-images-upload-input']");
|
||||||
const userMessage = document.getElementById("user-message");
|
const userMessage = document.getElementById("user-message");
|
||||||
if (form && uploadInput && userMessage) {
|
if (form && uploadInput && userMessage) {
|
||||||
form.addEventListener("submit", async (event) => {
|
form.addEventListener("submit", async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
event.stopImmediatePropagation();
|
event.stopImmediatePropagation();
|
||||||
const files = Array.from(uploadInput.files || []);
|
const files = Array.from(uploadInput.files || []);
|
||||||
if (files.length > 0) {
|
if (files.length > 0) {
|
||||||
const hasInvalid = files.some((file) => !file.type || !file.type.startsWith("image/"));
|
const hasInvalid = files.some((file) => !file.type || !file.type.startsWith("image/"));
|
||||||
if (hasInvalid) {
|
if (hasInvalid) {
|
||||||
event.preventDefault();
|
|
||||||
userMessage.innerHTML = `
|
userMessage.innerHTML = `
|
||||||
<div class="text-red-800 text-sm mt-2 rounded-xs bg-red-200 p-2 font-bold border-red-700 shadow border mb-3">
|
<div class="text-red-800 text-sm mt-2 rounded-xs bg-red-200 p-2 font-bold border-red-700 shadow border mb-3">
|
||||||
<i class="ri-error-warning-fill"></i> Bitte nur Bilddateien auswählen.
|
<i class="ri-error-warning-fill"></i> Bitte nur Bilddateien auswählen.
|
||||||
@@ -366,7 +378,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
const payload = new FormData(form);
|
const payload = new FormData(form);
|
||||||
if (payload.has("scans")) {
|
if (payload.has("scans")) {
|
||||||
payload.delete("scans");
|
payload.delete("scans");
|
||||||
@@ -402,9 +413,6 @@
|
|||||||
window.location.assign(response.url);
|
window.location.assign(response.url);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!response.ok) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const html = await response.text();
|
const html = await response.text();
|
||||||
if (!html) {
|
if (!html) {
|
||||||
return;
|
return;
|
||||||
@@ -413,8 +421,14 @@
|
|||||||
const nextMessage = doc.getElementById("user-message");
|
const nextMessage = doc.getElementById("user-message");
|
||||||
if (nextMessage) {
|
if (nextMessage) {
|
||||||
userMessage.innerHTML = nextMessage.innerHTML;
|
userMessage.innerHTML = nextMessage.innerHTML;
|
||||||
|
} else if (!response.ok) {
|
||||||
|
userMessage.innerHTML = `
|
||||||
|
<div class="text-red-800 text-sm mt-2 rounded-xs bg-red-200 p-2 font-bold border-red-700 shadow border mb-3">
|
||||||
|
<i class="ri-error-warning-fill"></i> Speichern fehlgeschlagen.
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
});
|
}, true);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -39,23 +39,32 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="inputwrapper">
|
||||||
|
<div class="inputlabelrow">
|
||||||
|
<label for="{{ $baseID }}-title" class="inputlabel">Titel</label>
|
||||||
|
</div>
|
||||||
|
<textarea name="{{ $prefix }}title_statement" id="{{ $baseID }}-title" class="inputinput no-enter whitespace-normal" autocomplete="off" rows="1">{{- $content.TitleStmt -}}</textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="inputwrapper">
|
||||||
|
<div class="inputlabelrow">
|
||||||
|
<label for="{{ $baseID }}-incipit" class="inputlabel">Incipit</label>
|
||||||
|
</div>
|
||||||
|
<textarea name="{{ $prefix }}incipit_statement" id="{{ $baseID }}-incipit" class="inputinput no-enter whitespace-normal" autocomplete="off" rows="1">{{- $content.IncipitStmt -}}</textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="inputwrapper">
|
||||||
|
<div class="inputlabelrow">
|
||||||
|
<label for="{{ $baseID }}-responsibility" class="inputlabel">Autorangabe</label>
|
||||||
|
</div>
|
||||||
|
<textarea name="{{ $prefix }}responsibility_statement" id="{{ $baseID }}-responsibility" class="inputinput no-enter whitespace-normal" autocomplete="off" rows="1">{{- $content.ResponsibilityStmt -}}</textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="{{ $baseID }}-edit-fields" class="mt-2 flex flex-col gap-2"></div>
|
<div id="{{ $baseID }}-edit-fields" class="mt-2 flex flex-col gap-2"></div>
|
||||||
<div-manager dm-target="{{ $baseID }}-edit-fields" class="flex items-center justify-end mt-1">
|
<div-manager dm-target="{{ $baseID }}-edit-fields" class="flex items-center justify-end mt-1">
|
||||||
<button class="dm-menu-button text-right cursor-pointer whitespace-nowrap"><i class="ri-add-line"></i>
|
<button class="dm-menu-button text-right cursor-pointer whitespace-nowrap"><i class="ri-add-line"></i>
|
||||||
Felder hinzufügen</button>
|
Felder hinzufügen</button>
|
||||||
|
|
||||||
<div class="inputwrapper {{ if eq $content.TitleStmt "" }}hidden{{ end }}">
|
|
||||||
<div class="inputlabelrow">
|
|
||||||
<label for="{{ $baseID }}-title" class="inputlabel menu-label">Titel</label>
|
|
||||||
<div class="pr-2">
|
|
||||||
<button class="dm-close-button font-bold input-label">
|
|
||||||
<i class="ri-close-line"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<textarea name="{{ $prefix }}title_statement" id="{{ $baseID }}-title" class="inputinput no-enter whitespace-normal" autocomplete="off" rows="1">{{- $content.TitleStmt -}}</textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="inputwrapper {{ if eq $content.SubtitleStmt "" }}hidden{{ end }}">
|
<div class="inputwrapper {{ if eq $content.SubtitleStmt "" }}hidden{{ end }}">
|
||||||
<div class="inputlabelrow">
|
<div class="inputlabelrow">
|
||||||
<label for="{{ $baseID }}-subtitle" class="inputlabel menu-label">Untertitel</label>
|
<label for="{{ $baseID }}-subtitle" class="inputlabel menu-label">Untertitel</label>
|
||||||
@@ -68,29 +77,7 @@
|
|||||||
<textarea name="{{ $prefix }}subtitle_statement" id="{{ $baseID }}-subtitle" class="inputinput no-enter whitespace-normal" autocomplete="off" rows="1">{{- $content.SubtitleStmt -}}</textarea>
|
<textarea name="{{ $prefix }}subtitle_statement" id="{{ $baseID }}-subtitle" class="inputinput no-enter whitespace-normal" autocomplete="off" rows="1">{{- $content.SubtitleStmt -}}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="inputwrapper {{ if eq $content.IncipitStmt "" }}hidden{{ end }}">
|
|
||||||
<div class="inputlabelrow">
|
|
||||||
<label for="{{ $baseID }}-incipit" class="inputlabel menu-label">Incipit</label>
|
|
||||||
<div class="pr-2">
|
|
||||||
<button class="dm-close-button font-bold input-label">
|
|
||||||
<i class="ri-close-line"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<textarea name="{{ $prefix }}incipit_statement" id="{{ $baseID }}-incipit" class="inputinput no-enter whitespace-normal" autocomplete="off" rows="1">{{- $content.IncipitStmt -}}</textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="inputwrapper {{ if eq $content.ResponsibilityStmt "" }}hidden{{ end }}">
|
|
||||||
<div class="inputlabelrow">
|
|
||||||
<label for="{{ $baseID }}-responsibility" class="inputlabel menu-label">Autorangabe</label>
|
|
||||||
<div class="pr-2">
|
|
||||||
<button class="dm-close-button font-bold input-label">
|
|
||||||
<i class="ri-close-line"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<textarea name="{{ $prefix }}responsibility_statement" id="{{ $baseID }}-responsibility" class="inputinput no-enter whitespace-normal" autocomplete="off" rows="1">{{- $content.ResponsibilityStmt -}}</textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="inputwrapper {{ if eq $content.ParallelTitle "" }}hidden{{ end }}">
|
<div class="inputwrapper {{ if eq $content.ParallelTitle "" }}hidden{{ end }}">
|
||||||
<div class="inputlabelrow">
|
<div class="inputlabelrow">
|
||||||
|
|||||||
@@ -23,18 +23,12 @@
|
|||||||
aria-label="Beitrag verschieben">
|
aria-label="Beitrag verschieben">
|
||||||
<i class="ri-draggable"></i>
|
<i class="ri-draggable"></i>
|
||||||
</button>
|
</button>
|
||||||
<tool-tip position="top" class="!inline">
|
<button type="button" class="text-slate-600 rounded-sm px-2 py-1 text-sm transition-colors hover:bg-stone-300" data-role="content-move-up" aria-label="Beitrag nach oben">
|
||||||
<div class="data-tip">Nach oben verschieben</div>
|
<i class="ri-arrow-up-line"></i>
|
||||||
<button type="button" class="text-slate-600 rounded-sm px-2 py-1 text-sm transition-colors hover:bg-stone-300" data-role="content-move-up" aria-label="Beitrag nach oben">
|
</button>
|
||||||
<i class="ri-arrow-up-line"></i>
|
<button type="button" class="text-slate-600 rounded-sm px-2 py-1 text-sm transition-colors hover:bg-stone-300" data-role="content-move-down" aria-label="Beitrag nach unten">
|
||||||
</button>
|
<i class="ri-arrow-down-line"></i>
|
||||||
</tool-tip>
|
</button>
|
||||||
<tool-tip position="top" class="!inline">
|
|
||||||
<div class="data-tip">Nach unten verschieben</div>
|
|
||||||
<button type="button" class="text-slate-600 rounded-sm px-2 py-1 text-sm transition-colors hover:bg-stone-300" data-role="content-move-down" aria-label="Beitrag nach unten">
|
|
||||||
<i class="ri-arrow-down-line"></i>
|
|
||||||
</button>
|
|
||||||
</tool-tip>
|
|
||||||
</div>
|
</div>
|
||||||
{{- if $content.Extent -}}
|
{{- if $content.Extent -}}
|
||||||
<span class="content-search-text bg-slate-200 text-slate-900 px-1.5 py-0.5 rounded text-sm font-semibold shadow-sm shrink-0" data-role="content-page-pill">S. {{- $content.Extent -}}</span>
|
<span class="content-search-text bg-slate-200 text-slate-900 px-1.5 py-0.5 rounded text-sm font-semibold shadow-sm shrink-0" data-role="content-page-pill">S. {{- $content.Extent -}}</span>
|
||||||
|
|||||||
@@ -134,6 +134,10 @@
|
|||||||
@apply w-full inline-flex justify-center py-2 px-4 border border-transparent rounded-md text-sm font-medium text-gray-800 bg-stone-200 hover:bg-stone-300 cursor-pointer focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-slate-500 no-underline;
|
@apply w-full inline-flex justify-center py-2 px-4 border border-transparent rounded-md text-sm font-medium text-gray-800 bg-stone-200 hover:bg-stone-300 cursor-pointer focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-slate-500 no-underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.content-action-button {
|
||||||
|
@apply inline-flex items-center justify-center gap-2 rounded-xs border border-slate-300 bg-white px-3 py-1.5 text-sm font-medium text-gray-800 shadow-sm transition-all duration-75 hover:bg-stone-50 focus:outline-none focus:ring-2 focus:ring-slate-400/30;
|
||||||
|
}
|
||||||
|
|
||||||
.dbform div-menu {
|
.dbform div-menu {
|
||||||
@apply relative inline-block;
|
@apply relative inline-block;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,6 +102,14 @@ export class ToolTip extends HTMLElement {
|
|||||||
|
|
||||||
this.addEventListener("mouseenter", () => this._showTooltip());
|
this.addEventListener("mouseenter", () => this._showTooltip());
|
||||||
this.addEventListener("mouseleave", () => this._hideTooltip());
|
this.addEventListener("mouseleave", () => this._hideTooltip());
|
||||||
|
this.addEventListener("pointerdown", () => this._forceHide());
|
||||||
|
this.addEventListener("mousedown", () => this._forceHide());
|
||||||
|
this.addEventListener("click", () => this._forceHide());
|
||||||
|
this.addEventListener("keydown", (event) => {
|
||||||
|
if (event.key === "Enter" || event.key === " ") {
|
||||||
|
this._forceHide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (this._dataTipElem) {
|
if (this._dataTipElem) {
|
||||||
this._observer = new MutationObserver(() => {
|
this._observer = new MutationObserver(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user