mirror of
https://github.com/Theodor-Springmann-Stiftung/musenalm.git
synced 2026-02-04 02:25:30 +00:00
+Abkürzungen
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels"
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/middleware"
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/pagemodels"
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/templating"
|
||||
@@ -147,6 +148,14 @@ func (app *App) createEngine() (*templating.Engine, error) {
|
||||
"desc": "Bibliographie deutscher Almanache des 18. und 19. Jahrhunderts",
|
||||
}})
|
||||
|
||||
engine.AddFunc("data", func(key string) any {
|
||||
res, err := dbmodels.Data_Key(app.PB.App, key)
|
||||
if err != nil {
|
||||
return "{}"
|
||||
}
|
||||
return res.Value()
|
||||
})
|
||||
|
||||
return engine, nil
|
||||
}
|
||||
|
||||
|
||||
210
controllers/abkuerzungen.go
Normal file
210
controllers/abkuerzungen.go
Normal file
@@ -0,0 +1,210 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/app"
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels"
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/middleware"
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/pagemodels"
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/templating"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"github.com/pocketbase/pocketbase/tools/router"
|
||||
"golang.org/x/text/collate"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
const (
|
||||
URL_ABKUERZUNGEN = "/abkuerzungen/"
|
||||
TEMPLATE_ABKUERZUNGEN = "/abkuerzungen/"
|
||||
)
|
||||
|
||||
func init() {
|
||||
ap := &AbkuerzungenPage{
|
||||
StaticPage: pagemodels.StaticPage{
|
||||
Name: pagemodels.P_ABKUERZUNGEN_NAME,
|
||||
URL: URL_ABKUERZUNGEN,
|
||||
Template: TEMPLATE_ABKUERZUNGEN,
|
||||
Layout: templating.DEFAULT_LAYOUT_NAME,
|
||||
},
|
||||
}
|
||||
app.Register(ap)
|
||||
}
|
||||
|
||||
type AbkuerzungenPage struct {
|
||||
pagemodels.StaticPage
|
||||
}
|
||||
|
||||
type AbkEntry struct {
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
|
||||
type AbkuerzungenResult struct {
|
||||
Entries []AbkEntry
|
||||
}
|
||||
|
||||
func (p *AbkuerzungenPage) Setup(router *router.Router[*core.RequestEvent], app core.App, engine *templating.Engine) error {
|
||||
router.GET(URL_ABKUERZUNGEN, p.GET(engine, app))
|
||||
|
||||
rg := router.Group(URL_ABKUERZUNGEN)
|
||||
rg.BindFunc(middleware.IsAdminOrEditor())
|
||||
rg.POST("", p.POST(engine, app))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *AbkuerzungenPage) GET(engine *templating.Engine, app core.App) HandleFunc {
|
||||
return func(e *core.RequestEvent) error {
|
||||
// Read abbreviations from data table
|
||||
dataRecord, err := dbmodels.Data_Key(app, "abkuerzungen")
|
||||
if err != nil {
|
||||
return engine.Response500(e, err, nil)
|
||||
}
|
||||
|
||||
// Parse JSON value into map
|
||||
abkMap := make(map[string]string)
|
||||
rawValue := dataRecord.Value()
|
||||
|
||||
// Convert via JSON marshal/unmarshal to handle types.JSONRaw
|
||||
jsonBytes, err := json.Marshal(rawValue)
|
||||
if err != nil {
|
||||
app.Logger().Error("Failed to marshal abkürzungen", "error", err)
|
||||
return engine.Response500(e, err, nil)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(jsonBytes, &abkMap); err != nil {
|
||||
app.Logger().Error("Failed to unmarshal abkürzungen", "error", err)
|
||||
return engine.Response500(e, err, nil)
|
||||
}
|
||||
|
||||
// Convert to sorted array
|
||||
entries := []AbkEntry{}
|
||||
for k, v := range abkMap {
|
||||
entries = append(entries, AbkEntry{Key: k, Value: v})
|
||||
}
|
||||
|
||||
// Sort by key (German collation)
|
||||
collator := collate.New(language.German)
|
||||
slices.SortFunc(entries, func(a, b AbkEntry) int {
|
||||
return collator.CompareString(a.Key, b.Key)
|
||||
})
|
||||
|
||||
data := map[string]any{
|
||||
"result": &AbkuerzungenResult{Entries: entries},
|
||||
}
|
||||
|
||||
req := templating.NewRequest(e)
|
||||
if req.Session() != nil {
|
||||
data["csrf_token"] = req.Session().Token
|
||||
} else {
|
||||
data["csrf_token"] = ""
|
||||
}
|
||||
|
||||
if msg := e.Request.URL.Query().Get("success"); msg != "" {
|
||||
data["success"] = msg
|
||||
}
|
||||
if msg := e.Request.URL.Query().Get("error"); msg != "" {
|
||||
data["error"] = msg
|
||||
}
|
||||
|
||||
return engine.Response200(e, p.Template, data, p.Layout)
|
||||
}
|
||||
}
|
||||
|
||||
type abkFormEntry struct {
|
||||
Key string `form:"key"`
|
||||
Value string `form:"value"`
|
||||
Delete string `form:"_delete"`
|
||||
}
|
||||
|
||||
func (p *AbkuerzungenPage) POST(engine *templating.Engine, app core.App) HandleFunc {
|
||||
return func(e *core.RequestEvent) error {
|
||||
req := templating.NewRequest(e)
|
||||
|
||||
// Parse form manually to handle array syntax
|
||||
if err := e.Request.ParseForm(); err != nil {
|
||||
return p.redirectError(e, "Formulardaten ungültig.")
|
||||
}
|
||||
|
||||
csrfToken := e.Request.FormValue("csrf_token")
|
||||
|
||||
// Validate CSRF
|
||||
if err := req.CheckCSRF(csrfToken); err != nil {
|
||||
return p.redirectError(e, err.Error())
|
||||
}
|
||||
|
||||
// Build new map from form data
|
||||
newMap := make(map[string]string)
|
||||
|
||||
// Parse array fields manually
|
||||
form := e.Request.Form
|
||||
i := 0
|
||||
for {
|
||||
keyField := fmt.Sprintf("abkuerzungen[%d][key]", i)
|
||||
valueField := fmt.Sprintf("abkuerzungen[%d][value]", i)
|
||||
deleteField := fmt.Sprintf("abkuerzungen[%d][_delete]", i)
|
||||
|
||||
key := form.Get(keyField)
|
||||
value := form.Get(valueField)
|
||||
delete := form.Get(deleteField)
|
||||
|
||||
// No more entries
|
||||
if key == "" && value == "" {
|
||||
break
|
||||
}
|
||||
|
||||
app.Logger().Info("Form entry", "i", i, "key", key, "value", value, "delete", delete)
|
||||
i++
|
||||
|
||||
// Skip deleted or empty entries
|
||||
if delete == "true" || strings.TrimSpace(key) == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
key = strings.TrimSpace(key)
|
||||
value = strings.TrimSpace(value)
|
||||
|
||||
// Validate required value
|
||||
if value == "" {
|
||||
return p.redirectError(e, fmt.Sprintf("Abkürzung '%s' benötigt eine Beschreibung.", key))
|
||||
}
|
||||
|
||||
// Check for duplicates
|
||||
if _, exists := newMap[key]; exists {
|
||||
return p.redirectError(e, fmt.Sprintf("Doppelter Schlüssel: '%s'", key))
|
||||
}
|
||||
|
||||
newMap[key] = value
|
||||
}
|
||||
|
||||
app.Logger().Info("Saving abkürzungen", "count", len(newMap))
|
||||
|
||||
// Update data record
|
||||
dataRecord, err := dbmodels.Data_Key(app, "abkuerzungen")
|
||||
if err != nil {
|
||||
app.Logger().Error("Failed to load abkuerzungen record", "error", err)
|
||||
return p.redirectError(e, "Laden der Daten fehlgeschlagen.")
|
||||
}
|
||||
|
||||
dataRecord.Set(dbmodels.VALUE_FIELD, newMap)
|
||||
if err := app.Save(dataRecord); err != nil {
|
||||
app.Logger().Error("Failed to save abkuerzungen", "error", err)
|
||||
return p.redirectError(e, "Speichern fehlgeschlagen.")
|
||||
}
|
||||
|
||||
// Redirect with success message
|
||||
redirect := fmt.Sprintf("%s?success=%s", URL_ABKUERZUNGEN, url.QueryEscape("Änderungen gespeichert."))
|
||||
return e.Redirect(http.StatusSeeOther, redirect)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *AbkuerzungenPage) redirectError(e *core.RequestEvent, message string) error {
|
||||
redirect := fmt.Sprintf("%s?error=%s", URL_ABKUERZUNGEN, url.QueryEscape(message))
|
||||
return e.Redirect(http.StatusSeeOther, redirect)
|
||||
}
|
||||
@@ -50,11 +50,6 @@ func (p *AlmanachPage) GET(engine *templating.Engine, app core.App) HandleFunc {
|
||||
data["result"] = result
|
||||
data["filters"] = filters
|
||||
|
||||
abbrs, err := pagemodels.GetAbks(app)
|
||||
if err == nil {
|
||||
data["abbrs"] = abbrs
|
||||
}
|
||||
|
||||
return engine.Response200(e, p.Template, data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,11 +66,6 @@ func (p *AlmanachEditPage) GET(engine *templating.Engine, app core.App) HandleFu
|
||||
data["agent_relations"] = dbmodels.AGENT_RELATIONS
|
||||
data["series_relations"] = dbmodels.SERIES_RELATIONS
|
||||
|
||||
abbrs, err := pagemodels.GetAbks(app)
|
||||
if err == nil {
|
||||
data["abbrs"] = abbrs
|
||||
}
|
||||
|
||||
if msg := e.Request.URL.Query().Get("saved_message"); msg != "" {
|
||||
data["success"] = msg
|
||||
}
|
||||
@@ -84,7 +79,7 @@ type AlmanachEditResult struct {
|
||||
PrevByID *dbmodels.Entry
|
||||
NextByTitle *dbmodels.Entry
|
||||
PrevByTitle *dbmodels.Entry
|
||||
User *dbmodels.User
|
||||
User *dbmodels.User
|
||||
AlmanachResult
|
||||
}
|
||||
|
||||
|
||||
@@ -83,11 +83,6 @@ func (p *AlmanachNewPage) GET(engine *templating.Engine, app core.App) HandleFun
|
||||
data["series_relations"] = dbmodels.SERIES_RELATIONS
|
||||
data["is_new"] = true
|
||||
|
||||
abbrs, err := pagemodels.GetAbks(app)
|
||||
if err == nil {
|
||||
data["abbrs"] = abbrs
|
||||
}
|
||||
|
||||
return engine.Response200(e, p.Template, data, p.Layout)
|
||||
}
|
||||
}
|
||||
@@ -165,7 +160,7 @@ func (p *AlmanachNewPage) POSTSave(engine *templating.Engine, app core.App) Hand
|
||||
|
||||
redirect := "/"
|
||||
if entry != nil {
|
||||
redirect = "/almanach/" + strconv.Itoa(entry.MusenalmID()) + "/edit?saved_message=" + url.QueryEscape("Änderungen gespeichert.")
|
||||
redirect = "/almanach/" + strconv.Itoa(entry.MusenalmID()) + "/edit?saved_message=" + url.QueryEscape("Änderungen gespeichert.")
|
||||
}
|
||||
|
||||
return e.JSON(http.StatusOK, map[string]any{
|
||||
|
||||
@@ -34,18 +34,13 @@ type BeitragPage struct {
|
||||
func (p *BeitragPage) Setup(router *router.Router[*core.RequestEvent], app core.App, engine *templating.Engine) error {
|
||||
router.GET(p.URL, func(e *core.RequestEvent) error {
|
||||
id := e.Request.PathValue("id")
|
||||
data := make(map[string]interface{})
|
||||
data := make(map[string]any)
|
||||
result, err := NewBeitragResult(app, id)
|
||||
if err != nil {
|
||||
engine.Response404(e, err, nil)
|
||||
}
|
||||
data["result"] = result
|
||||
|
||||
abbrs, err := pagemodels.GetAbks(app)
|
||||
if err == nil {
|
||||
data["abbrs"] = abbrs
|
||||
}
|
||||
|
||||
return engine.Response200(e, p.Template, data)
|
||||
})
|
||||
|
||||
|
||||
@@ -14,14 +14,10 @@ func (d *Data) SetKey(key string) {
|
||||
d.Set(KEY_FIELD, key)
|
||||
}
|
||||
|
||||
func (d *Data) Value() map[string]interface{} {
|
||||
val := d.Get(VALUE_FIELD)
|
||||
if val == nil {
|
||||
return nil
|
||||
}
|
||||
return val.(map[string]interface{})
|
||||
func (d *Data) Value() any {
|
||||
return d.GetRaw(VALUE_FIELD)
|
||||
}
|
||||
|
||||
func (d *Data) SetValue(value map[string]interface{}) {
|
||||
func (d *Data) SetValue(value string) {
|
||||
d.Set(VALUE_FIELD, value)
|
||||
}
|
||||
|
||||
@@ -501,6 +501,7 @@ const (
|
||||
|
||||
REFERENCES_FIELD = "refs"
|
||||
URI_FIELD = "uri"
|
||||
URL_FIELD ="url"
|
||||
|
||||
MUSENALM_BAENDE_STATUS_FIELD = "musenalm_status"
|
||||
MUSENALM_INHALTE_TYPE_FIELD = "musenalm_type"
|
||||
|
||||
@@ -144,6 +144,11 @@ func Sessions_ID(app core.App, id string) (*Session, error) {
|
||||
return &ret, err
|
||||
}
|
||||
|
||||
func Data_Key(app core.App, key string) (*Data, error) {
|
||||
ret, err := TableByField[Data](app, DATA_TABLE, KEY_FIELD, key)
|
||||
return &ret, err
|
||||
}
|
||||
|
||||
func AccessTokens_Token(app core.App, token string) (*AccessToken, error) {
|
||||
t := HashStringSHA256(token)
|
||||
return TableByField[*AccessToken](
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package functions
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"regexp"
|
||||
@@ -19,6 +20,15 @@ func Safe(s string) template.HTML {
|
||||
return template.HTML(s)
|
||||
}
|
||||
|
||||
func SafeJS(s any) template.JS {
|
||||
b, err := json.Marshal(s)
|
||||
if err != nil {
|
||||
return template.JS("{}")
|
||||
}
|
||||
|
||||
return template.JS(b)
|
||||
}
|
||||
|
||||
func ReplaceSlashParen(s string) string {
|
||||
return strings.ReplaceAll(s, "/)", "<p>")
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels"
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/pagemodels"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
m "github.com/pocketbase/pocketbase/migrations"
|
||||
@@ -21,12 +18,6 @@ func init() {
|
||||
return err
|
||||
}
|
||||
|
||||
abk := abkCollection()
|
||||
if err := app.Save(abk); err != nil {
|
||||
app.Logger().Error("Failed to save collection:", "error", err, "collection", abk)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}, func(app core.App) error {
|
||||
collection, err := app.FindCollectionByNameOrId(
|
||||
@@ -37,14 +28,7 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
collection_abk, err2 := app.FindCollectionByNameOrId(
|
||||
pagemodels.GeneratePageTableName(pagemodels.P_DOK_NAME, pagemodels.T_ABK_NAME))
|
||||
if err == nil && collection_abk != nil {
|
||||
if err := app.Delete(collection_abk); err != nil {
|
||||
app.Logger().Error("Failed to delete collection:", "error", err, "collection", collection_abk)
|
||||
}
|
||||
}
|
||||
return errors.Join(err, err2)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
@@ -53,13 +37,3 @@ func dokCollection() *core.Collection {
|
||||
c.Fields = append(c.Fields, dok_fields...)
|
||||
return c
|
||||
}
|
||||
|
||||
func abkCollection() *core.Collection {
|
||||
c := core.NewBaseCollection(pagemodels.GeneratePageTableName(pagemodels.P_DOK_NAME, pagemodels.T_ABK_NAME))
|
||||
c.Fields = core.NewFieldsList(
|
||||
pagemodels.RequiredTextField(pagemodels.F_ABK),
|
||||
pagemodels.RequiredTextField(pagemodels.F_BEDEUTUNG),
|
||||
)
|
||||
dbmodels.SetBasicPublicRules(c)
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@ package migrations
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels"
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/pagemodels"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
m "github.com/pocketbase/pocketbase/migrations"
|
||||
@@ -505,19 +505,7 @@ func init() {
|
||||
return err
|
||||
}
|
||||
|
||||
abk, err := seed_abkuerzungen(app)
|
||||
if err != nil {
|
||||
app.Logger().Error("Failed to seed abkuerzungen", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
for _, a := range abk {
|
||||
if err := app.Save(a); err != nil {
|
||||
app.Logger().Error("Failed to save abk", "error", err, "abk", a)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return seed_abkuerzungen(app)
|
||||
}, func(app core.App) error {
|
||||
coll, err := app.FindCollectionByNameOrId(
|
||||
pagemodels.GeneratePageTableName(pagemodels.P_DOK_NAME))
|
||||
@@ -526,30 +514,19 @@ func init() {
|
||||
app.DB().NewQuery("DELETE FROM " + coll.TableName()).Execute()
|
||||
}
|
||||
|
||||
coll_abk, err2 := app.FindCollectionByNameOrId(
|
||||
pagemodels.GeneratePageTableName(pagemodels.P_DOK_NAME, pagemodels.T_ABK_NAME))
|
||||
|
||||
if err == nil && coll_abk != nil {
|
||||
app.DB().NewQuery("DELETE FROM " + coll_abk.TableName()).Execute()
|
||||
}
|
||||
|
||||
return errors.Join(err, err2)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func seed_abkuerzungen(app core.App) ([]*pagemodels.Abk, error) {
|
||||
collection, err := app.FindCollectionByNameOrId(pagemodels.GeneratePageTableName(pagemodels.P_DOK_NAME, pagemodels.T_ABK_NAME))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func seed_abkuerzungen(app core.App) error {
|
||||
|
||||
if _, err := os.Stat(ABK_PATH); err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
file, err := os.Open(ABK_PATH)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
@@ -568,13 +545,15 @@ func seed_abkuerzungen(app core.App) ([]*pagemodels.Abk, error) {
|
||||
abk[split[0]] = strings.TrimSpace(besch)
|
||||
}
|
||||
|
||||
ret := make([]*pagemodels.Abk, 0, len(abk))
|
||||
for a, b := range abk {
|
||||
r := pagemodels.NewAbk(core.NewRecord(collection))
|
||||
r.SetAbk(a)
|
||||
r.SetBedeutung(b)
|
||||
ret = append(ret, r)
|
||||
dataColl, err := app.FindCollectionByNameOrId(dbmodels.DATA_TABLE)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
// Create new record in data table
|
||||
record := core.NewRecord(dataColl)
|
||||
record.Set(dbmodels.KEY_FIELD, "abkuerzungen")
|
||||
record.Set(dbmodels.VALUE_FIELD, abk)
|
||||
|
||||
return app.Save(record)
|
||||
}
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
m "github.com/pocketbase/pocketbase/migrations"
|
||||
)
|
||||
|
||||
func init() {
|
||||
m.Register(func(app core.App) error {
|
||||
// Find source collection
|
||||
abkColl, err := app.FindCollectionByNameOrId("page_benutzerhinweise_abkuerzungen")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Query all abbreviations
|
||||
var records []*core.Record
|
||||
err = app.RecordQuery(abkColl.Name).All(&records)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Convert to JSON map
|
||||
abkMap := make(map[string]string)
|
||||
for _, r := range records {
|
||||
abkMap[r.GetString("Abkuerzung")] = r.GetString("Bedeutung")
|
||||
}
|
||||
|
||||
// Find data collection
|
||||
dataColl, err := app.FindCollectionByNameOrId(dbmodels.DATA_TABLE)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create new record in data table
|
||||
record := core.NewRecord(dataColl)
|
||||
record.Set(dbmodels.KEY_FIELD, "abkuerzungen")
|
||||
record.Set(dbmodels.VALUE_FIELD, abkMap)
|
||||
|
||||
return app.Save(record)
|
||||
}, func(app core.App) error {
|
||||
// Rollback: delete from data table
|
||||
dataColl, err := app.FindCollectionByNameOrId(dbmodels.DATA_TABLE)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
record, err := app.FindFirstRecordByFilter(dataColl.Name, dbmodels.KEY_FIELD+" = 'abkuerzungen'")
|
||||
if err != nil {
|
||||
return nil // Already deleted
|
||||
}
|
||||
|
||||
return app.Delete(record)
|
||||
})
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package pagemodels
|
||||
|
||||
import "github.com/pocketbase/pocketbase/core"
|
||||
|
||||
type Abk struct {
|
||||
core.BaseRecordProxy
|
||||
}
|
||||
|
||||
func (a *Abk) TableName() string {
|
||||
return GeneratePageTableName(P_DOK_NAME, T_ABK_NAME)
|
||||
}
|
||||
|
||||
func NewAbk(record *core.Record) *Abk {
|
||||
i := &Abk{}
|
||||
i.SetProxyRecord(record)
|
||||
return i
|
||||
}
|
||||
|
||||
func (a *Abk) Abk() string {
|
||||
return a.GetString(F_ABK)
|
||||
}
|
||||
|
||||
func (a *Abk) SetAbk(abk string) {
|
||||
a.Set(F_ABK, abk)
|
||||
}
|
||||
|
||||
func (a *Abk) Bedeutung() string {
|
||||
return a.GetString(F_BEDEUTUNG)
|
||||
}
|
||||
|
||||
func (a *Abk) SetBedeutung(bedeutung string) {
|
||||
a.Set(F_BEDEUTUNG, bedeutung)
|
||||
}
|
||||
|
||||
func GetAbks(app core.App) (map[string]string, error) {
|
||||
ret := make(map[string]string)
|
||||
abks := []*Abk{}
|
||||
|
||||
err := app.RecordQuery(GeneratePageTableName(P_DOK_NAME, T_ABK_NAME)).All(&abks)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
for _, abk := range abks {
|
||||
ret[abk.Abk()] = abk.Bedeutung()
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
@@ -14,16 +14,13 @@ const (
|
||||
|
||||
P_REIHEN_NAME = "reihen"
|
||||
P_ORTE_NAME = "orte"
|
||||
P_ABKUERZUNGEN_NAME = "abkuerzungen"
|
||||
P_DANK_NAME = "danksagungen"
|
||||
P_KONTAKT_NAME = "kontakt"
|
||||
P_LIT_NAME = "literatur"
|
||||
P_EINFUEHRUNG_NAME = "einleitung"
|
||||
|
||||
P_DOK_NAME = "benutzerhinweise"
|
||||
T_ABK_NAME = "abkuerzungen"
|
||||
|
||||
F_ABK = "Abkuerzung"
|
||||
F_BEDEUTUNG = "Bedeutung"
|
||||
|
||||
F_TITLE = "Titel"
|
||||
F_DESCRIPTION = "Beschreibung"
|
||||
|
||||
@@ -115,6 +115,7 @@ func (e *Engine) funcs() error {
|
||||
|
||||
// Passing HTML
|
||||
e.AddFunc("Safe", functions.Safe)
|
||||
e.AddFunc("SafeJS", functions.SafeJS)
|
||||
// Creating an array or dict (to pass to a template)
|
||||
e.AddFunc("Arr", functions.Arr)
|
||||
e.AddFunc("Dict", functions.Dict)
|
||||
|
||||
@@ -8795,6 +8795,15 @@ class Qc extends HTMLElement {
|
||||
<i class="ri-external-link-line text-base"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="grid grid-cols-[1fr_auto] group">
|
||||
<a href="/abkuerzungen/" class="flex items-center px-4 py-2 group-hover:bg-gray-100 transition-colors no-underline text-sm">
|
||||
<i class="ri-text text-base text-gray-700 mr-2.5"></i>
|
||||
<span class="text-gray-900">Abkürzungen</span>
|
||||
</a>
|
||||
<a href="/abkuerzungen/" target="_blank" class="flex items-center justify-center px-3 py-2 group-hover:bg-gray-100 text-gray-700 hover:text-slate-900 transition-colors no-underline text-sm" title="In neuem Tab öffnen">
|
||||
<i class="ri-external-link-line text-base"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="border-t border-gray-200 my-1"></div>
|
||||
` : "", w = r ? `
|
||||
<div class="px-3 py-1.5 text-xs font-semibold text-gray-500 uppercase tracking-wider">
|
||||
|
||||
File diff suppressed because one or more lines are too long
186
views/routes/abkuerzungen/body.gohtml
Normal file
186
views/routes/abkuerzungen/body.gohtml
Normal file
@@ -0,0 +1,186 @@
|
||||
{{ $model := . }}
|
||||
|
||||
<edit-page>
|
||||
<div class="flex container-normal bg-slate-100 mx-auto px-8">
|
||||
<div class="flex flex-row w-full justify-between">
|
||||
<div class="flex flex-col justify-end-safe flex-2/5">
|
||||
<div class="mb-1">
|
||||
<i class="ri-text"></i> Verwaltung
|
||||
</div>
|
||||
<h1 class="text-2xl w-full font-bold text-slate-900 mb-1">
|
||||
Abkürzungen
|
||||
</h1>
|
||||
<div class="flex flex-row gap-x-3">
|
||||
<div>
|
||||
<a href="/abkuerzungen/" class="text-gray-700 hover:text-slate-950 block no-underline">
|
||||
<i class="ri-loop-left-line"></i> Reset
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container-normal mx-auto mt-4 !px-0">
|
||||
{{ template "_usermessage" $model }}
|
||||
|
||||
{{- if (IsAdminOrEditor $model.request.user) -}}
|
||||
{{/* Editable form for admin/editor */}}
|
||||
<form
|
||||
autocomplete="off"
|
||||
class="w-full dbform"
|
||||
id="abkform"
|
||||
method="POST"
|
||||
action="/abkuerzungen/">
|
||||
<input type="hidden" name="csrf_token" value="{{ $model.csrf_token }}" />
|
||||
|
||||
<div class="mb-4 flex items-center gap-3">
|
||||
<button type="button" id="add-row-btn" class="resetbutton w-auto px-3 py-1.5 flex items-center gap-2">
|
||||
<i class="ri-add-line"></i>
|
||||
<span>Zeile hinzufügen</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto bg-white border border-gray-200 rounded">
|
||||
<table class="w-full">
|
||||
<thead>
|
||||
<tr class="border-b-2 border-gray-300 bg-gray-50">
|
||||
<th class="text-left px-4 py-3 font-bold text-sm text-gray-700 w-48">Kürzel</th>
|
||||
<th class="text-left px-4 py-3 font-bold text-sm text-gray-700">Bedeutung</th>
|
||||
<th class="w-16 px-4 py-3"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="abk-tbody">
|
||||
{{- range $index, $entry := $model.result.Entries -}}
|
||||
<tr class="border-b border-gray-200 hover:bg-gray-50 odd:bg-stone-100" data-row="{{ $index }}">
|
||||
<td class="px-4 py-2.5">
|
||||
<input type="text"
|
||||
name="abkuerzungen[{{ $index }}][key]"
|
||||
value="{{ $entry.Key }}"
|
||||
class="inputinput font-mono text-base w-full border border-gray-300 px-2 py-1 rounded"
|
||||
autocomplete="off"
|
||||
required>
|
||||
</td>
|
||||
<td class="px-4 py-2.5">
|
||||
<input type="text"
|
||||
name="abkuerzungen[{{ $index }}][value]"
|
||||
value="{{ $entry.Value }}"
|
||||
class="inputinput text-base w-full border border-gray-300 px-2 py-1 rounded"
|
||||
autocomplete="off"
|
||||
required>
|
||||
</td>
|
||||
<td class="px-4 py-2.5 text-center">
|
||||
<input type="hidden" name="abkuerzungen[{{ $index }}][_delete]" value="false" class="delete-flag">
|
||||
<button type="button" class="delete-btn text-red-600 hover:text-red-800" title="Löschen">
|
||||
<i class="ri-delete-bin-line text-lg"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{{- end -}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="w-full flex items-end justify-between gap-4 mt-6 flex-wrap">
|
||||
<p id="abk-save-feedback" class="text-sm text-gray-600" aria-live="polite"></p>
|
||||
<div class="flex items-center gap-3 self-end flex-wrap">
|
||||
<a href="/abkuerzungen/" class="resetbutton w-40 flex items-center gap-2 justify-center">
|
||||
<i class="ri-close-line"></i>
|
||||
<span>Abbrechen</span>
|
||||
</a>
|
||||
<a href="/abkuerzungen/" class="resetbutton w-40 flex items-center gap-2 justify-center">
|
||||
<i class="ri-loop-left-line"></i>
|
||||
<span>Reset</span>
|
||||
</a>
|
||||
<button type="submit" class="submitbutton w-48 flex items-center gap-2 justify-center">
|
||||
<i class="ri-save-line"></i>
|
||||
<span>Alle speichern</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
const tbody = document.getElementById('abk-tbody');
|
||||
const addBtn = document.getElementById('add-row-btn');
|
||||
let rowIndex = {{ len $model.result.Entries }};
|
||||
|
||||
// Add new row
|
||||
addBtn.addEventListener('click', function() {
|
||||
const tr = document.createElement('tr');
|
||||
tr.className = 'border-b border-gray-200 hover:bg-gray-50 odd:bg-stone-100';
|
||||
tr.dataset.row = rowIndex;
|
||||
tr.innerHTML = `
|
||||
<td class="px-4 py-2.5">
|
||||
<input type="text"
|
||||
name="abkuerzungen[${rowIndex}][key]"
|
||||
value=""
|
||||
class="inputinput font-mono text-base w-full border border-gray-300 px-2 py-1 rounded"
|
||||
autocomplete="off"
|
||||
required>
|
||||
</td>
|
||||
<td class="px-4 py-2.5">
|
||||
<input type="text"
|
||||
name="abkuerzungen[${rowIndex}][value]"
|
||||
value=""
|
||||
class="inputinput text-base w-full border border-gray-300 px-2 py-1 rounded"
|
||||
autocomplete="off"
|
||||
required>
|
||||
</td>
|
||||
<td class="px-4 py-2.5 text-center">
|
||||
<input type="hidden" name="abkuerzungen[${rowIndex}][_delete]" value="false" class="delete-flag">
|
||||
<button type="button" class="delete-btn text-red-600 hover:text-red-800" title="Löschen">
|
||||
<i class="ri-delete-bin-line text-lg"></i>
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
rowIndex++;
|
||||
|
||||
// Focus the new key input
|
||||
const newKeyInput = tr.querySelector('input[type="text"]');
|
||||
if (newKeyInput) {
|
||||
newKeyInput.focus();
|
||||
}
|
||||
});
|
||||
|
||||
// Delete row handler (using event delegation)
|
||||
tbody.addEventListener('click', function(e) {
|
||||
if (e.target.classList.contains('delete-btn')) {
|
||||
const tr = e.target.closest('tr');
|
||||
const deleteFlag = tr.querySelector('.delete-flag');
|
||||
deleteFlag.value = 'true';
|
||||
tr.style.display = 'none';
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
{{- else -}}
|
||||
{{/* Read-only view for public */}}
|
||||
<div class="bg-white border border-gray-200 rounded overflow-hidden">
|
||||
{{- if $model.result.Entries -}}
|
||||
<table class="w-full">
|
||||
<thead>
|
||||
<tr class="border-b-2 border-gray-300 bg-gray-50">
|
||||
<th class="text-left px-4 py-3 font-bold text-sm text-gray-700 w-48">Kürzel</th>
|
||||
<th class="text-left px-4 py-3 font-bold text-sm text-gray-700">Bedeutung</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{- range $entry := $model.result.Entries -}}
|
||||
<tr class="border-b border-gray-200 hover:bg-gray-50 odd:bg-stone-100">
|
||||
<td class="px-4 py-2.5 font-bold font-mono text-base">{{ $entry.Key }}</td>
|
||||
<td class="px-4 py-2.5 text-base">{{ $entry.Value }}</td>
|
||||
</tr>
|
||||
{{- end -}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{- else -}}
|
||||
<div class="px-4 py-8 text-center text-gray-500">Keine Abkürzungen gefunden.</div>
|
||||
{{- end -}}
|
||||
</div>
|
||||
{{- end -}}
|
||||
</div>
|
||||
</edit-page>
|
||||
@@ -187,7 +187,7 @@
|
||||
</div>
|
||||
|
||||
<script type="module">
|
||||
let abbrevs = {{- $model.abbrs -}};
|
||||
let abbrevs = {{- data "abkuerzungen" | SafeJS -}};
|
||||
let ats = document.querySelectorAll('abbrev-tooltips');
|
||||
if (ats) {
|
||||
ats.forEach((at) => {
|
||||
|
||||
@@ -179,6 +179,15 @@ export class FabMenu extends HTMLElement {
|
||||
<i class="ri-external-link-line text-base"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="grid grid-cols-[1fr_auto] group">
|
||||
<a href="/abkuerzungen/" class="flex items-center px-4 py-2 group-hover:bg-gray-100 transition-colors no-underline text-sm">
|
||||
<i class="ri-text text-base text-gray-700 mr-2.5"></i>
|
||||
<span class="text-gray-900">Abkürzungen</span>
|
||||
</a>
|
||||
<a href="/abkuerzungen/" target="_blank" class="flex items-center justify-center px-3 py-2 group-hover:bg-gray-100 text-gray-700 hover:text-slate-900 transition-colors no-underline text-sm" title="In neuem Tab öffnen">
|
||||
<i class="ri-external-link-line text-base"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="border-t border-gray-200 my-1"></div>
|
||||
`
|
||||
: "";
|
||||
|
||||
Reference in New Issue
Block a user