+Bilder- u. Download-Manager

This commit is contained in:
Simon Martens
2026-01-27 13:44:46 +01:00
parent 826c08add2
commit 8cf466851a
8 changed files with 275 additions and 34 deletions

View File

@@ -204,6 +204,11 @@ func seitenEditorData(e *core.RequestEvent, app core.App) (map[string]any, error
return nil, err
}
files, err := dbmodels.Files_All(app)
if err != nil {
return nil, err
}
selectedKey := strings.TrimSpace(e.Request.URL.Query().Get("key"))
if selectedKey == "" && len(pages) > 0 {
selectedKey = pages[0].Key
@@ -221,6 +226,24 @@ func seitenEditorData(e *core.RequestEvent, app core.App) (map[string]any, error
"pages": pages,
"selected": selected,
"selected_key": selectedKey,
"files": files,
}
if selectedKey == pagemodels.P_INDEX_NAME {
images, err := dbmodels.Images_KeyPrefix(app, "page.index.image.")
if err != nil {
return nil, err
}
data["images_index"] = images
data["images_index_prefix"] = "page.index"
}
if selectedKey == pagemodels.P_REIHEN_NAME {
image, err := dbmodels.Images_Key(app, "page.reihen.image")
if err != nil {
return nil, err
}
data["image_reihen"] = image
data["image_reihen_key"] = "page.reihen.image"
}
req := templating.NewRequest(e)

View File

@@ -1,6 +1,9 @@
package dbmodels
import "github.com/pocketbase/pocketbase/core"
import (
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/types"
)
type File struct {
core.BaseRecordProxy
@@ -14,6 +17,14 @@ func (f *File) SetKey(key string) {
f.Set(KEY_FIELD, key)
}
func (f *File) Title() string {
return f.GetString(TITLE_FIELD)
}
func (f *File) SetTitle(title string) {
f.Set(TITLE_FIELD, title)
}
func (f *File) Description() string {
return f.GetString(DESCRIPTION_FIELD)
}
@@ -29,3 +40,23 @@ func (f *File) FileField() string {
func (f *File) SetFileField(file string) {
f.Set(FILE_FIELD, file)
}
func (f *File) PublicURL() string {
filename := f.FileField()
if filename == "" {
return ""
}
return "/api/files/" + FILES_TABLE + "/" + f.Id + "/" + filename
}
func (f *File) Created() types.DateTime {
return f.GetDateTime(CREATED_FIELD)
}
func (f *File) CreatedUnix() int64 {
t := f.GetDateTime(CREATED_FIELD)
if t.IsZero() {
return 0
}
return t.Time().Unix()
}

View File

@@ -1,6 +1,9 @@
package dbmodels
import "github.com/pocketbase/pocketbase/core"
import (
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/types"
)
type Image struct {
core.BaseRecordProxy
@@ -57,3 +60,15 @@ func (i *Image) VorschauPath() string {
func (i *Image) Beschreibung() string {
return i.Description()
}
func (i *Image) Created() types.DateTime {
return i.GetDateTime(CREATED_FIELD)
}
func (i *Image) CreatedUnix() int64 {
t := i.GetDateTime(CREATED_FIELD)
if t.IsZero() {
return 0
}
return t.Time().Unix()
}

View File

@@ -190,6 +190,14 @@ func Images_KeyPrefix(app core.App, prefix string) ([]*Image, error) {
return images, err
}
func Files_All(app core.App) ([]*File, error) {
files := make([]*File, 0)
err := app.RecordQuery(FILES_TABLE).
OrderBy(CREATED_FIELD + " DESC").
All(&files)
return files, err
}
func AccessTokens_Token(app core.App, token string) (*AccessToken, error) {
ret, err := TableByField[AccessToken](
app,

View File

@@ -532,7 +532,7 @@ func filesTable() *core.Collection {
func filesTableFields() core.FieldsList {
fields := core.NewFieldsList(
&core.TextField{Name: dbmodels.KEY_FIELD, Required: true, Presentable: true},
&core.TextField{Name: dbmodels.TITLE_FIELD, Required: true, Presentable: true},
&core.TextField{Name: dbmodels.DESCRIPTION_FIELD, Required: false},
&core.FileField{
Name: dbmodels.FILE_FIELD,
@@ -547,7 +547,7 @@ func filesTableFields() core.FieldsList {
}
func filesTableIndexes(collection *core.Collection) {
dbmodels.AddIndex(collection, dbmodels.KEY_FIELD, false)
dbmodels.AddIndex(collection, dbmodels.TITLE_FIELD, false)
}
func htmlTable() *core.Collection {

View File

@@ -11,6 +11,7 @@ import (
"github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels"
"github.com/Theodor-Springmann-Stiftung/musenalm/pagemodels"
"github.com/Theodor-Springmann-Stiftung/musenalm/xmlmodels"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
"github.com/pocketbase/pocketbase/tools/filesystem"
@@ -654,26 +655,90 @@ const (
KABINETT_TITLE = "Lesekabinett"
KABINETT_DESCRIPTION = "Musenalm: Verzeichnis deutschsprachiger Almanache des 18. und 19. Jahrhunderts. Historische Texte zum Almanachwesen."
KABINETT_TEXT = `<h1>Texte zum Almanachwesen</h1>
<p><em>Joseph Franz von Ratschky:</em> Vorbericht. in: Wiener Musenalmanach. 1779, S. 3-6. [&darr;<a href="/assets/Lesekabinett/ratschky_in_wiener_1779.pdf" target="_blank" rel="noopener">Download</a>]</p>
<p><em>Gottfried August B&uuml;rger:</em> Nothgedrungene Nachrede. in: G&ouml;ttinger Musenalmanach. 1782, S. 184-192. [&darr;<a href="/assets/Lesekabinett/buerger_in_goettinger_1782.pdf" target="_blank" rel="noopener">Download</a>]</p>
<p><em>Christian Cay Lorenz Hirschfeld: </em>An die Leser. in: Gartenkalender. 1783, S. 272. [&darr;<a href="/assets/Lesekabinett/hirschfeld_in_gartenkalender_1783.pdf" target="_blank" rel="noopener">Download</a>]</p>
<p><em>Johann Heinrich Vo&szlig;:</em> Ank&uuml;ndigung. in: Hamburger Musenalmanach. 1784, S. 222ff. [&darr;<a href="/assets/Lesekabinett/voss_in_hamburger_1784.pdf" target="_blank" rel="noopener">Download</a>]</p>
<p><em>Gotthold Friedrich St&auml;udlin: </em>Nachrede. in: Schw&auml;bischer Musenalmanach. 1786 [o. S.]. [&darr;<a href="/assets/Lesekabinett/staeudlin_in_schwaebischer_1786.pdf" target="_blank" rel="noopener">Download</a>]</p>
<p><em>Gottfried August B&uuml;rger:</em> F&uuml;rbitte eines ans peinliche Kreuz der Verlegenheit genagelten Herausgebers eines Musenalmanachs. in: G&ouml;ttinger Musenalmanach. 1789, S. 104. [&darr;<a href="/assets/Lesekabinett/buerger_in_goettinger_1789.pdf" target="_blank" rel="noopener">Download</a>]</p>
<p><em>Anonymus: </em>Die deutschen Almanache. in: Bibliothek der redenden und bildenden K&uuml;nste. Zweyten Bandes erstes St&uuml;ck. Leipzig, in der Dyckischen Buchhandlung, 1806, S. 207-217. [&darr;<a href="/assets/Lesekabinett/anonymus.pdf" target="_blank" rel="noopener">Download</a>]</p>
<p><em>Stephan Sch&uuml;tze:</em> Die Neujahrsversammlung. Ein dramatischer Prolog. in: Taschenbuch der Liebe und Freundschaft gewidmet. 1813, S. 1-20. [&darr;<a href="/assets/Lesekabinett/schuetze_in_taschenbuch_1813.pdf" target="_blank" rel="noopener">Download</a>]</p>
<p><em>N. B. E.: </em>Die deutschen Taschenb&uuml;cher f&uuml;r 1820. in: Hermes oder kritisches Jahrbuch der Literatur. Zweites St&uuml;ck f&uuml;r das Jahr 1820. Amsterdam, in der Verlags-Expedition des Hermes, S. 191-235. [&darr;<em><a href="/assets/Lesekabinett/nbe_in_hermes_1820.pdf" target="_blank" rel="noopener">Download</a>]</em></p>
<p><em>Ferdinand Johannes Wit:</em> Die Almanachomanie. in: Politisches Taschenbuch. 1831, S. 102-111. [&darr;<a href="/assets/Lesekabinett/wit_in_politaschenbuch_1831.pdf" target="_blank" rel="noopener">Download</a>]</p>
<p><em>August Wilhelm Schlegel:</em> Recept. in: Deutscher Musenalmanach (Chamisso, Schwab, Gaudy). 1836, S. 18. [&darr;<a href="/assets/Lesekabinett/schlegel_in_deutscher_1836.pdf" target="_blank" rel="noopener">Download</a>]</p>
<p><em>Robert Eduard Prutz:</em> Die Musenalmanache und Taschenb&uuml;cher in Deutschland. in: Neue Schriften. Zur deutschen Literatur- und Kulturgeschichte. Erster Band, Halle, G. Schwetschke'scher Verlag, 1854, S. 105-165. [&darr;<a href="/assets/Lesekabinett/prutz_in_musenalmanache_1854.pdf" target="_blank" rel="noopener">Download</a>]</p>
<p data-olk-copy-source="MessageBody"><em>Friedrich Arnold Brockhaus:</em> Taschenb&uuml;cher &ndash; und Almanachsliteratur in Deutschland. In: &nbsp;Allgemeine deutsche Real-Encyclop&auml;die f&uuml;r die gebildeten St&auml;nde (Conversations-Lexicon) Leipzig 1820 Bd. 10. S. 973-978. [&darr;<a href="/assets/Lesekabinett/brockhaus.pdf" target="_blank" rel="noopener">Download</a>]</p>
<h1>Allotria und Kuriosa</h1>
<p><em>Anonymus:</em> Woher das Wort Almanach komme. in: Neues Wochenblatt zum Nuzzen und zur Unterhaltung f&uuml;r Kinder und junge Leute. Erstes B&auml;ndchen, erstes St&uuml;ck, Leipzig, in der Sommerschen Buchhandlung 1794, S. 8f. [&darr;<a href="/assets/Lesekabinett/allatroia_anonymus_wochenblatt_1794.pdf" target="_blank" rel="noopener">Download</a>]</p>`
LESEKABINETT_FILES_PATH = "./views/public/Lesekabinett"
ABKUERZUNGEN_PATH = "./import/data/abkuerzungen.txt"
)
type lesekabinettFileSeed struct {
HTML string
FileName string
Title string
}
var lesekabinettSeed = []lesekabinettFileSeed{
{
HTML: `<p><em>Joseph Franz von Ratschky:</em> Vorbericht. in: Wiener Musenalmanach. 1779, S. 3-6. [&darr;<a href="%s" target="_blank" rel="noopener">Download</a>]</p>`,
FileName: "ratschky_in_wiener_1779.pdf",
Title: "Joseph Franz von Ratschky: Vorbericht",
},
{
HTML: `<p><em>Gottfried August B&uuml;rger:</em> Nothgedrungene Nachrede. in: G&ouml;ttinger Musenalmanach. 1782, S. 184-192. [&darr;<a href="%s" target="_blank" rel="noopener">Download</a>]</p>`,
FileName: "buerger_in_goettinger_1782.pdf",
Title: "Gottfried August Bürger: Nothgedrungene Nachrede",
},
{
HTML: `<p><em>Christian Cay Lorenz Hirschfeld: </em>An die Leser. in: Gartenkalender. 1783, S. 272. [&darr;<a href="%s" target="_blank" rel="noopener">Download</a>]</p>`,
FileName: "hirschfeld_in_gartenkalender_1783.pdf",
Title: "Christian Cay Lorenz Hirschfeld: An die Leser",
},
{
HTML: `<p><em>Johann Heinrich Vo&szlig;:</em> Ank&uuml;ndigung. in: Hamburger Musenalmanach. 1784, S. 222ff. [&darr;<a href="%s" target="_blank" rel="noopener">Download</a>]</p>`,
FileName: "voss_in_hamburger_1784.pdf",
Title: "Johann Heinrich Voß: Ankündigung",
},
{
HTML: `<p><em>Gotthold Friedrich St&auml;udlin: </em>Nachrede. in: Schw&auml;bischer Musenalmanach. 1786 [o. S.]. [&darr;<a href="%s" target="_blank" rel="noopener">Download</a>]</p>`,
FileName: "staeudlin_in_schwaebischer_1786.pdf",
Title: "Gotthold Friedrich Stäudlin: Nachrede",
},
{
HTML: `<p><em>Gottfried August B&uuml;rger:</em> F&uuml;rbitte eines ans peinliche Kreuz der Verlegenheit genagelten Herausgebers eines Musenalmanachs. in: G&ouml;ttinger Musenalmanach. 1789, S. 104. [&darr;<a href="%s" target="_blank" rel="noopener">Download</a>]</p>`,
FileName: "buerger_in_goettinger_1789.pdf",
Title: "Gottfried August Bürger: Fürbitte eines Herausgebers",
},
{
HTML: `<p><em>Anonymus: </em>Die deutschen Almanache. in: Bibliothek der redenden und bildenden K&uuml;nste. Zweyten Bandes erstes St&uuml;ck. Leipzig, in der Dyckischen Buchhandlung, 1806, S. 207-217. [&darr;<a href="%s" target="_blank" rel="noopener">Download</a>]</p>`,
FileName: "anonymus.pdf",
Title: "Anonymus: Die deutschen Almanache",
},
{
HTML: `<p><em>Stephan Sch&uuml;tze:</em> Die Neujahrsversammlung. Ein dramatischer Prolog. in: Taschenbuch der Liebe und Freundschaft gewidmet. 1813, S. 1-20. [&darr;<a href="%s" target="_blank" rel="noopener">Download</a>]</p>`,
FileName: "schuetze_in_taschenbuch_1813.pdf",
Title: "Stephan Schütze: Die Neujahrsversammlung",
},
{
HTML: `<p><em>N. B. E.: </em>Die deutschen Taschenb&uuml;cher f&uuml;r 1820. in: Hermes oder kritisches Jahrbuch der Literatur. Zweites St&uuml;ck f&uuml;r das Jahr 1820. Amsterdam, in der Verlags-Expedition des Hermes, S. 191-235. [&darr;<em><a href="%s" target="_blank" rel="noopener">Download</a>]</em></p>`,
FileName: "nbe_in_hermes_1820.pdf",
Title: "N. B. E.: Die deutschen Taschenbücher für 1820",
},
{
HTML: `<p><em>Ferdinand Johannes Wit:</em> Die Almanachomanie. in: Politisches Taschenbuch. 1831, S. 102-111. [&darr;<a href="%s" target="_blank" rel="noopener">Download</a>]</p>`,
FileName: "wit_in_politaschenbuch_1831.pdf",
Title: "Ferdinand Johannes Wit: Die Almanachomanie",
},
{
HTML: `<p><em>August Wilhelm Schlegel:</em> Recept. in: Deutscher Musenalmanach (Chamisso, Schwab, Gaudy). 1836, S. 18. [&darr;<a href="%s" target="_blank" rel="noopener">Download</a>]</p>`,
FileName: "schlegel_in_deutscher_1836.pdf",
Title: "August Wilhelm Schlegel: Recept",
},
{
HTML: `<p><em>Robert Eduard Prutz:</em> Die Musenalmanache und Taschenb&uuml;cher in Deutschland. in: Neue Schriften. Zur deutschen Literatur- und Kulturgeschichte. Erster Band, Halle, G. Schwetschke'scher Verlag, 1854, S. 105-165. [&darr;<a href="%s" target="_blank" rel="noopener">Download</a>]</p>`,
FileName: "prutz_in_musenalmanache_1854.pdf",
Title: "Robert Eduard Prutz: Die Musenalmanache und Taschenbücher",
},
{
HTML: `<p data-olk-copy-source="MessageBody"><em>Friedrich Arnold Brockhaus:</em> Taschenb&uuml;cher &ndash; und Almanachsliteratur in Deutschland. In: &nbsp;Allgemeine deutsche Real-Encyclop&auml;die f&uuml;r die gebildeten St&auml;nde (Conversations-Lexicon) Leipzig 1820 Bd. 10. S. 973-978. [&darr;<a href="%s" target="_blank" rel="noopener">Download</a>]</p>`,
FileName: "brockhaus.pdf",
Title: "Friedrich Arnold Brockhaus: Taschenbücher und Almanachsliteratur",
},
{
HTML: `<p><em>Anonymus:</em> Woher das Wort Almanach komme. in: Neues Wochenblatt zum Nuzzen und zur Unterhaltung f&uuml;r Kinder und junge Leute. Erstes B&auml;ndchen, erstes St&uuml;ck, Leipzig, in der Sommerschen Buchhandlung 1794, S. 8f. [&darr;<a href="%s" target="_blank" rel="noopener">Download</a>]</p>`,
FileName: "allatroia_anonymus_wochenblatt_1794.pdf",
Title: "Anonymus: Woher das Wort Almanach komme",
},
}
var pageMetaSeed = map[string]PageMeta{
pagemodels.P_INDEX_NAME: {
Title: INDEX_TITLE,
@@ -725,27 +790,38 @@ var pageMetaSeed = map[string]PageMeta{
},
}
var pageHTMLSeed = map[string]string{
pageHTMLKey(pagemodels.P_INDEX_NAME, "abs1"): INDEX_ABS1,
pageHTMLKey(pagemodels.P_INDEX_NAME, "abs2"): INDEX_ABS2,
pageHTMLKey(pagemodels.P_REIHEN_NAME, "text"): REIHEN_TEXT,
pageHTMLKey(pagemodels.P_DANK_NAME, "text"): DANKSAGUNGEN_TEXT,
pageHTMLKey(pagemodels.P_EINFUEHRUNG_NAME, "text"): EINLEITUNG_TEXT,
pageHTMLKey(pagemodels.P_KONTAKT_NAME, "text"): KONTAKT_TEXT,
pageHTMLKey(pagemodels.P_LIT_NAME, "text"): LITERATUR_TEXT,
pageHTMLKey(pagemodels.P_DOK_NAME, "text"): DOKUMENTATION_TEXT,
pageHTMLKey(pagemodels.P_KABINETT_NAME, "text"): KABINETT_TEXT,
func pageHTMLSeed(kabinetText string) map[string]string {
return map[string]string{
pageHTMLKey(pagemodels.P_INDEX_NAME, "abs1"): INDEX_ABS1,
pageHTMLKey(pagemodels.P_INDEX_NAME, "abs2"): INDEX_ABS2,
pageHTMLKey(pagemodels.P_REIHEN_NAME, "text"): REIHEN_TEXT,
pageHTMLKey(pagemodels.P_DANK_NAME, "text"): DANKSAGUNGEN_TEXT,
pageHTMLKey(pagemodels.P_EINFUEHRUNG_NAME, "text"): EINLEITUNG_TEXT,
pageHTMLKey(pagemodels.P_KONTAKT_NAME, "text"): KONTAKT_TEXT,
pageHTMLKey(pagemodels.P_LIT_NAME, "text"): LITERATUR_TEXT,
pageHTMLKey(pagemodels.P_DOK_NAME, "text"): DOKUMENTATION_TEXT,
pageHTMLKey(pagemodels.P_KABINETT_NAME, "text"): kabinetText,
}
}
func init() {
m.Register(func(app core.App) error {
kabinetUrls, err := seedLesekabinettFiles(app)
if err != nil {
return err
}
kabinetText, err := buildLesekabinettHTML(kabinetUrls)
if err != nil {
return err
}
for key, meta := range pageMetaSeed {
if err := upsertPageMeta(app, key, meta); err != nil {
return err
}
}
for key, html := range pageHTMLSeed {
for key, html := range pageHTMLSeed(kabinetText) {
if err := upsertHTML(app, key, html); err != nil {
return err
}
@@ -764,7 +840,7 @@ func init() {
return seedAbkuerzungen(app)
}, func(app core.App) error {
for key := range pageHTMLSeed {
for key := range pageHTMLSeed("") {
if err := deleteByKey(app, dbmodels.HTML_TABLE, key); err != nil {
return err
}
@@ -787,10 +863,90 @@ func init() {
_, err = app.DB().
NewQuery("DELETE FROM " + imagesCollection.TableName() + " WHERE " + dbmodels.KEY_FIELD + " LIKE 'page.index.image.%'").
Execute()
return err
if err != nil {
return err
}
return deleteLesekabinettFiles(app)
})
}
func seedLesekabinettFiles(app core.App) (map[string]string, error) {
collection, err := app.FindCollectionByNameOrId(dbmodels.FILES_TABLE)
if err != nil {
return nil, err
}
urls := map[string]string{}
for _, entry := range lesekabinettSeed {
path := filepath.Join(LESEKABINETT_FILES_PATH, entry.FileName)
file, err := filesystem.NewFileFromPath(path)
if err != nil {
app.Logger().Error("Failed to read lesekabinett file", "error", err, "path", path)
return nil, err
}
record, _ := app.FindFirstRecordByData(collection.Id, dbmodels.TITLE_FIELD, entry.Title)
if record == nil {
record = core.NewRecord(collection)
}
record.Set(dbmodels.TITLE_FIELD, entry.Title)
record.Set(dbmodels.DESCRIPTION_FIELD, entry.Title)
record.Set(dbmodels.FILE_FIELD, file)
if err := app.Save(record); err != nil {
return nil, err
}
fileName := record.GetString(dbmodels.FILE_FIELD)
if fileName == "" {
fileName = entry.FileName
}
urls[entry.FileName] = "/api/files/" + dbmodels.FILES_TABLE + "/" + record.Id + "/" + fileName
}
return urls, nil
}
func buildLesekabinettHTML(urls map[string]string) (string, error) {
var builder strings.Builder
builder.WriteString("<h1>Texte zum Almanachwesen</h1>\n")
for _, entry := range lesekabinettSeed {
if entry.FileName == "allatroia_anonymus_wochenblatt_1794.pdf" {
builder.WriteString("<h1>Allotria und Kuriosa</h1>\n")
}
url, ok := urls[entry.FileName]
if !ok {
return "", fmt.Errorf("missing file url for %s", entry.FileName)
}
builder.WriteString(fmt.Sprintf(entry.HTML, url))
}
return builder.String(), nil
}
func deleteLesekabinettFiles(app core.App) error {
collection, err := app.FindCollectionByNameOrId(dbmodels.FILES_TABLE)
if err != nil {
return err
}
if len(lesekabinettSeed) == 0 {
return nil
}
params := dbx.Params{}
placeholders := make([]string, 0, len(lesekabinettSeed))
for i, entry := range lesekabinettSeed {
key := fmt.Sprintf("p%d", i)
params[key] = entry.FileName
placeholders = append(placeholders, "{:"+key+"}")
}
query := "DELETE FROM " + collection.TableName() + " WHERE " + dbmodels.FILE_FIELD + " IN (" + strings.Join(placeholders, ", ") + ")"
_, err = app.DB().NewQuery(query).Bind(params).Execute()
return err
}
func seedReihenImage(app core.App) error {
img, err := filesystem.NewFileFromPath(REIHEN_IMAGE_PATH)
if err != nil {

File diff suppressed because one or more lines are too long

View File

@@ -43,6 +43,14 @@
<span>Seiteninhalte</span>
</div>
{{ template "_file_uploader" $model }}
{{- if and $model.images_index $model.images_index_prefix -}}
{{ template "_image_uploader" (Dict "images" $model.images_index "prefix" $model.images_index_prefix "csrf_token" $model.csrf_token) }}
{{- end -}}
{{- if and $model.image_reihen $model.image_reihen_key -}}
{{ template "_image_uploader_single" (Dict "image" $model.image_reihen "key" $model.image_reihen_key "csrf_token" $model.csrf_token) }}
{{- end -}}
{{- if not $model.selected.Sections -}}
<div class="text-gray-700 bg-slate-100 border border-slate-200 rounded-xs p-4">
Keine HTML-Bereiche gefunden.