resetbutton & almanach edit start

This commit is contained in:
Simon Martens
2025-05-30 19:30:50 +02:00
parent d8e50b27b0
commit 8ea36da40f
28 changed files with 1789 additions and 763 deletions

View File

@@ -68,6 +68,7 @@ type AlmanachResult struct {
EntriesSeries map[string]*dbmodels.REntriesSeries // <- Key is series id
EntriesAgents []*dbmodels.REntriesAgents
ContentsAgents map[string][]*dbmodels.RContentsAgents // <- Key is content id
User *dbmodels.User
Types []string
HasScans bool
@@ -166,6 +167,16 @@ func NewAlmanachResult(app core.App, id string, params BeitraegeFilterParameters
agentsMap[a.Id] = a
}
var user *dbmodels.User
if entry.Editor() != "" {
u, err := dbmodels.Users_ID(app, entry.Editor())
if err == nil {
user = u
} else {
app.Logger().Error("Failed to load user for entry editor", "entry", entry.Id, "error", err)
}
}
ret := &AlmanachResult{
Entry: entry,
Places: places,
@@ -177,6 +188,7 @@ func NewAlmanachResult(app core.App, id string, params BeitraegeFilterParameters
ContentsAgents: caMap,
Types: types,
HasScans: hs,
User: user,
}
ret.Collections()

View File

@@ -125,12 +125,11 @@ func (p *LoginPage) POST(engine *templating.Engine, app core.App) HandleFunc {
return Unauthorized(engine, e, fmt.Errorf("Benuztername oder Passwort falsch. Bitte versuchen Sie es erneut."), data)
}
record, err := app.FindFirstRecordByData(dbmodels.USERS_TABLE, dbmodels.USERS_EMAIL_FIELD, formdata.Username)
if err != nil || !record.ValidatePassword(formdata.Password) {
user, err := dbmodels.Users_Email(app, formdata.Username)
if err != nil || !user.ValidatePassword(formdata.Password) {
return Unauthorized(engine, e, fmt.Errorf("Benuztername oder Passwort falsch. Bitte versuchen Sie es erneut."), data)
}
user := dbmodels.NewUser(record)
if user.Deactivated() {
return Unauthorized(engine, e, fmt.Errorf("Ihr Benutzerkonto ist deaktiviert. Bitte kontaktieren Sie den Administrator."), data)
}
@@ -140,7 +139,7 @@ func (p *LoginPage) POST(engine *templating.Engine, app core.App) HandleFunc {
duration = time.Hour * 24 * 90
}
token, err := dbmodels.CreateSessionToken(app, record.Id, e.RealIP(), e.Request.UserAgent(), formdata.Persistent == "on", duration)
token, err := dbmodels.CreateSessionToken(app, user.Id, e.RealIP(), e.Request.UserAgent(), formdata.Persistent == "on", duration)
if err != nil || token == nil || token.SessionTokenClear == "" {
return engine.Response500(e, err, data)
}

View File

@@ -53,14 +53,13 @@ func Logout(e *core.RequestEvent, app *core.App) {
if err == nil && app != nil {
go func() {
app := *app
record, err := app.FindFirstRecordByData(dbmodels.SESSIONS_TABLE, dbmodels.SESSIONS_TOKEN_FIELD, dbmodels.HashStringSHA256(cookie.Value))
if err == nil && record != nil {
session := dbmodels.NewSession(record)
session, err := dbmodels.Sessions_Token(app, cookie.Value)
if err == nil {
session.SetStatus(dbmodels.MUSENALM_STATUS_VALUES[1])
if err := app.Save(session); err != nil {
app.Logger().Error("Failed to update session status.", "error", err.Error())
}
} else if err != nil {
} else {
app.Logger().Error("Failed to find session record.", "error", err.Error())
}

View File

@@ -64,12 +64,11 @@ func (p *UserEditPage) GET(engine *templating.Engine, app core.App) HandleFunc {
func (p *UserEditPage) getData(app core.App, data map[string]any, e *core.RequestEvent) error {
uid := e.Request.PathValue(UID_PATH_VALUE)
u, err := app.FindRecordById(dbmodels.USERS_TABLE, uid)
user, err := dbmodels.Users_ID(app, uid)
if err != nil {
return fmt.Errorf("Konnte Nutzer nicht finden: %w", err)
}
user := dbmodels.NewUser(u)
fu := user.Fixed()
data["user"] = &fu
@@ -220,11 +219,10 @@ func (p *UserEditPage) POST(engine *templating.Engine, app core.App) HandleFunc
req := templating.NewRequest(e)
user := req.User()
u, err := app.FindRecordById(dbmodels.USERS_TABLE, uid)
user_proxy, err := dbmodels.Users_ID(app, uid)
if err != nil {
return engine.Response404(e, err, nil)
}
user_proxy := dbmodels.NewUser(u)
fu := user_proxy.Fixed()
formdata := struct {

View File

@@ -142,13 +142,11 @@ func (p *UserManagementPage) POSTDeactivate(engine *templating.Engine, app core.
return p.ErrorResponse(engine, e, err)
}
user, err := app.FindRecordById(dbmodels.USERS_TABLE, formdata.User)
u, err := dbmodels.Users_ID(app, formdata.User)
if err != nil {
return p.ErrorResponse(engine, e, fmt.Errorf("Konnte Nutzer nicht finden."))
}
u := dbmodels.NewUser(user)
u.SetDeactivated(true)
if err := app.Save(u); err != nil {
@@ -187,13 +185,11 @@ func (p *UserManagementPage) POSTActivate(engine *templating.Engine, app core.Ap
return p.ErrorResponse(engine, e, err)
}
user, err := app.FindRecordById(dbmodels.USERS_TABLE, formdata.User)
u, err := dbmodels.Users_ID(app, formdata.User)
if err != nil {
return p.ErrorResponse(engine, e, fmt.Errorf("Konnte Nutzer nicht finden."))
}
u := dbmodels.NewUser(user)
u.SetDeactivated(false)
if err := app.Save(u); err != nil {
@@ -232,12 +228,11 @@ func (p *UserManagementPage) POSTLogout(engine *templating.Engine, app core.App)
return p.ErrorResponse(engine, e, err)
}
user, err := app.FindRecordById(dbmodels.USERS_TABLE, formdata.User)
u, err := dbmodels.Users_ID(app, formdata.User)
if err != nil {
return p.ErrorResponse(engine, e, fmt.Errorf("Konnte Nutzer nicht finden."))
}
u := dbmodels.NewUser(user)
DeleteSessionsForUser(app, u.Id)
data := make(map[string]any)

View File

@@ -4,6 +4,7 @@ import (
"log/slog"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/types"
)
var _ core.RecordProxy = (*Entry)(nil)
@@ -243,3 +244,19 @@ func (e *Entry) Editor() string {
func (e *Entry) SetEditor(editor string) {
e.Set(EDITOR_FIELD, editor)
}
func (e *Entry) Updated() types.DateTime {
return e.GetDateTime(UPDATED_FIELD)
}
func (e *Entry) SetUpdated(updated types.DateTime) {
e.Set(UPDATED_FIELD, updated)
}
func (e *Entry) Created() types.DateTime {
return e.GetDateTime(CREATED_FIELD)
}
func (e *Entry) SetCreated(created types.DateTime) {
e.Set(CREATED_FIELD, created)
}

View File

@@ -133,6 +133,42 @@ func Series_ID(app core.App, id string) (*Series, error) {
return &ret, err
}
func Users_ID(app core.App, id string) (*User, error) {
ret, err := TableByID[User](app, USERS_TABLE, id)
return &ret, err
}
func Sessions_ID(app core.App, id string) (*Session, error) {
ret, err := TableByID[Session](app, SESSIONS_TABLE, id)
return &ret, err
}
func AccessTokens_Token(app core.App, token string) (*AccessToken, error) {
t := HashStringSHA256(token)
return TableByField[*AccessToken](
app,
ACCESS_TOKENS_TABLE,
ACCESS_TOKENS_TOKEN_FIELD,
t,
)
}
func Users_Email(app core.App, email string) (*User, error) {
ret, err := TableByField[User](app, USERS_TABLE, USERS_EMAIL_FIELD, email)
return &ret, err
}
func Sessions_Token(app core.App, token string) (*Session, error) {
t := HashStringSHA256(token)
ret, err := TableByField[Session](
app,
SESSIONS_TABLE,
SESSIONS_TOKEN_FIELD,
t,
)
return &ret, err
}
func Places_IDs(app core.App, ids []any) ([]*Place, error) {
return TableByIDs[*Place](app, PLACES_TABLE, ids)
}

View File

@@ -3,6 +3,8 @@ package functions
import (
"fmt"
"time"
"github.com/pocketbase/pocketbase/tools/types"
)
type Weekday struct {
@@ -18,6 +20,7 @@ type Month struct {
}
var Months = []Month{
{"N/A", "N/A", 0},
{"Januar", "Jan", 1},
{"Februar", "Feb", 2},
{"März", "Mär", 3},
@@ -30,7 +33,6 @@ var Months = []Month{
{"Oktober", "Okt", 10},
{"November", "Nov", 11},
{"Dezember", "Dez", 12},
{"N/A", "N/A", 0},
}
var Weekdays = []Weekday{
@@ -65,3 +67,27 @@ func GetMonth(month any) Month {
fmt.Println("Invalid month value", month)
return Months[12]
}
func GermanDate(t types.DateTime) string {
if t.IsZero() {
return "N/A"
}
location, _ := time.LoadLocation("Europe/Berlin")
time := t.Time().In(location)
month := Months[time.Month()]
weekday := Weekdays[time.Weekday()]
return fmt.Sprintf("%s, %d. %s %d", weekday.ShortName, time.Day(), month.ShortName, time.Year())
}
func GermanTime(t types.DateTime) string {
if t.IsZero() {
return "N/A"
}
location, _ := time.LoadLocation("Europe/Berlin")
time := t.Time().In(location)
return fmt.Sprintf("%02d:%02d", time.Hour(), time.Minute())
}

View File

@@ -21,7 +21,8 @@ var deact_cookie = &http.Cookie{
func Authenticated(app core.App) func(*core.RequestEvent) error {
return func(e *core.RequestEvent) error {
if strings.HasPrefix(e.Request.URL.Path, "/assets") ||
strings.HasPrefix(e.Request.URL.Path, "/api") {
strings.HasPrefix(e.Request.URL.Path, "/api") ||
strings.HasPrefix(e.Request.URL.Path, "/_") {
return e.Next()
}
@@ -32,22 +33,20 @@ func Authenticated(app core.App) func(*core.RequestEvent) error {
user, session, loaded := SESSION_CACHE.Get(cookie.Value)
if !loaded {
hashedsession := dbmodels.HashStringSHA256(cookie.Value)
record, err := app.FindFirstRecordByData(dbmodels.SESSIONS_TABLE, dbmodels.SESSIONS_TOKEN_FIELD, hashedsession)
s, err := dbmodels.Sessions_Token(app, cookie.Value)
if err != nil {
e.SetCookie(deact_cookie)
e.Response.Header().Set("Clear-Site-Data", "\"cookies\"")
return e.Next()
}
s := dbmodels.NewSession(record)
r, err := app.FindRecordById(dbmodels.USERS_TABLE, s.User())
slog.Debug("Session loaded from database", "session", s.Id, "user", s.User())
u, err := dbmodels.Users_ID(app, s.User())
if err != nil {
e.SetCookie(deact_cookie)
e.Response.Header().Set("Clear-Site-Data", "\"cookies\"")
return e.Next()
}
u := dbmodels.NewUser(r)
user, session = SESSION_CACHE.Set(u, s)
}
@@ -59,12 +58,15 @@ func Authenticated(app core.App) func(*core.RequestEvent) error {
slog.Warn("Session expired", "user", user.Id, "name", user.Name, "session", session.ID)
SESSION_CACHE.Delete(cookie.Value)
go func() {
r, err := app.FindRecordById(dbmodels.SESSIONS_TABLE, session.ID)
r, err := dbmodels.Sessions_ID(app, session.ID)
if err == nil {
r.SetStatus(dbmodels.TOKEN_STATUS_VALUES[1])
if err := app.Save(r); err != nil {
app.Logger().Error("Failed to save session status", "session", session.ID, "error", err)
}
}
e.SetCookie(deact_cookie)
e.Response.Header().Set("Clear-Site-Data", "\"cookies\"")
if err == nil {
app.Delete(r)
}
}()
return e.Next()
}
@@ -74,21 +76,19 @@ func Authenticated(app core.App) func(*core.RequestEvent) error {
token := e.Request.URL.Query().Get("token")
if token != "" {
record, err := app.FindFirstRecordByData(dbmodels.ACCESS_TOKENS_TABLE, dbmodels.ACCESS_TOKENS_TOKEN_FIELD, token)
a, err := dbmodels.AccessTokens_Token(app, token)
if err != nil {
slog.Error("Failed to find access token", "token", token, "error", err)
return e.Next()
}
a := dbmodels.NewAccessToken(record)
if a.User() != "" {
r, err := app.FindRecordById(dbmodels.USERS_TABLE, a.User())
u, err := dbmodels.Users_ID(app, a.User())
if err != nil {
slog.Error("Failed to find access token user", "user", a.User(), "error", err)
return e.Next()
}
u := dbmodels.NewUser(r)
e.Set("access_token_user", u.Fixed())
}

View File

@@ -12,3 +12,11 @@ Ideen:
- Logout auf einer geschützen Seite: weiterleitung (evtl. referer nutzen?) -> evtl. footer-logout link ändern auf AdminPage
- Tooltips
06221 56 34 553
hr. konuk
9-15.30 Uhr
von beratung sprechen

View File

@@ -124,6 +124,8 @@ func (e *Engine) funcs() error {
e.AddFunc("Contains", functions.Contains)
e.AddFunc("Add", functions.Add)
e.AddFunc("Len", functions.Length)
e.AddFunc("GermanDate", functions.GermanDate)
e.AddFunc("GermanTime", functions.GermanTime)
// String Functions
e.AddFunc("Lower", functions.Lower)

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

325
views/bun.lock Normal file
View File

@@ -0,0 +1,325 @@
{
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "caveman_views",
"devDependencies": {
"@tailwindcss/postcss": "^4.0.0",
"daisyui": "^5.0.0-beta.8",
"postcss": "^8.4.47",
"postcss-cli": "^11.0.0",
"prettier": "^3.3.3",
"prettier-plugin-go-template": "^0.0.15",
"tailwindcss": "^4.0.0",
"vite": "^6.0.0",
},
},
},
"packages": {
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
"@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.5", "", { "os": "android", "cpu": "arm" }, "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA=="],
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.5", "", { "os": "android", "cpu": "arm64" }, "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg=="],
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.5", "", { "os": "android", "cpu": "x64" }, "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw=="],
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ=="],
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ=="],
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw=="],
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw=="],
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.5", "", { "os": "linux", "cpu": "arm" }, "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw=="],
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg=="],
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.5", "", { "os": "linux", "cpu": "ia32" }, "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA=="],
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.5", "", { "os": "linux", "cpu": "none" }, "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg=="],
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.5", "", { "os": "linux", "cpu": "none" }, "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg=="],
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ=="],
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.5", "", { "os": "linux", "cpu": "none" }, "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA=="],
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ=="],
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.5", "", { "os": "linux", "cpu": "x64" }, "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw=="],
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.5", "", { "os": "none", "cpu": "arm64" }, "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw=="],
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.5", "", { "os": "none", "cpu": "x64" }, "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ=="],
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.5", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw=="],
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.5", "", { "os": "openbsd", "cpu": "x64" }, "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg=="],
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.5", "", { "os": "sunos", "cpu": "x64" }, "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA=="],
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw=="],
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ=="],
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.5", "", { "os": "win32", "cpu": "x64" }, "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g=="],
"@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="],
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="],
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
"@jridgewell/set-array": ["@jridgewell/set-array@1.2.1", "", {}, "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A=="],
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="],
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.41.1", "", { "os": "android", "cpu": "arm" }, "sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.41.1", "", { "os": "android", "cpu": "arm64" }, "sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA=="],
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.41.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5afxvwszzdulsU2w8JKWwY8/sJOLPzf0e1bFuvcW5h9zsEg+RQAojdW0ux2zyYAz7R8HvvzKCjLNJhVq965U7w=="],
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.41.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-egpJACny8QOdHNNMZKf8xY0Is6gIMz+tuqXlusxquWu3F833DcMwmGM7WlvCO9sB3OsPjdC4U0wHw5FabzCGZg=="],
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.41.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-DBVMZH5vbjgRk3r0OzgjS38z+atlupJ7xfKIDJdZZL6sM6wjfDNo64aowcLPKIx7LMQi8vybB56uh1Ftck/Atg=="],
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.41.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-3FkydeohozEskBxNWEIbPfOE0aqQgB6ttTkJ159uWOFn42VLyfAiyD9UK5mhu+ItWzft60DycIN1Xdgiy8o/SA=="],
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.41.1", "", { "os": "linux", "cpu": "arm" }, "sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg=="],
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.41.1", "", { "os": "linux", "cpu": "arm" }, "sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA=="],
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.41.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA=="],
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.41.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg=="],
"@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.41.1", "", { "os": "linux", "cpu": "none" }, "sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw=="],
"@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.41.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A=="],
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.41.1", "", { "os": "linux", "cpu": "none" }, "sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw=="],
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.41.1", "", { "os": "linux", "cpu": "none" }, "sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw=="],
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.41.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g=="],
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.41.1", "", { "os": "linux", "cpu": "x64" }, "sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A=="],
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.41.1", "", { "os": "linux", "cpu": "x64" }, "sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ=="],
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.41.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ=="],
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.41.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-+psFT9+pIh2iuGsxFYYa/LhS5MFKmuivRsx9iPJWNSGbh2XVEjk90fmpUEjCnILPEPJnikAU6SFDiEUyOv90Pg=="],
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.41.1", "", { "os": "win32", "cpu": "x64" }, "sha512-Wq2zpapRYLfi4aKxf2Xff0tN+7slj2d4R87WEzqw7ZLsVvO5zwYCIuEGSZYiK41+GlwUo1HiR+GdkLEJnCKTCw=="],
"@tailwindcss/node": ["@tailwindcss/node@4.1.8", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", "tailwindcss": "4.1.8" } }, "sha512-OWwBsbC9BFAJelmnNcrKuf+bka2ZxCE2A4Ft53Tkg4uoiE67r/PMEYwCsourC26E+kmxfwE0hVzMdxqeW+xu7Q=="],
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.8", "", { "dependencies": { "detect-libc": "^2.0.4", "tar": "^7.4.3" }, "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.8", "@tailwindcss/oxide-darwin-arm64": "4.1.8", "@tailwindcss/oxide-darwin-x64": "4.1.8", "@tailwindcss/oxide-freebsd-x64": "4.1.8", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.8", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.8", "@tailwindcss/oxide-linux-arm64-musl": "4.1.8", "@tailwindcss/oxide-linux-x64-gnu": "4.1.8", "@tailwindcss/oxide-linux-x64-musl": "4.1.8", "@tailwindcss/oxide-wasm32-wasi": "4.1.8", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.8", "@tailwindcss/oxide-win32-x64-msvc": "4.1.8" } }, "sha512-d7qvv9PsM5N3VNKhwVUhpK6r4h9wtLkJ6lz9ZY9aeZgrUWk1Z8VPyqyDT9MZlem7GTGseRQHkeB1j3tC7W1P+A=="],
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.8", "", { "os": "android", "cpu": "arm64" }, "sha512-Fbz7qni62uKYceWYvUjRqhGfZKwhZDQhlrJKGtnZfuNtHFqa8wmr+Wn74CTWERiW2hn3mN5gTpOoxWKk0jRxjg=="],
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RdRvedGsT0vwVVDztvyXhKpsU2ark/BjgG0huo4+2BluxdXo8NDgzl77qh0T1nUxmM11eXwR8jA39ibvSTbi7A=="],
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-t6PgxjEMLp5Ovf7uMb2OFmb3kqzVTPPakWpBIFzppk4JE4ix0yEtbtSjPbU8+PZETpaYMtXvss2Sdkx8Vs4XRw=="],
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.8", "", { "os": "freebsd", "cpu": "x64" }, "sha512-g8C8eGEyhHTqwPStSwZNSrOlyx0bhK/V/+zX0Y+n7DoRUzyS8eMbVshVOLJTDDC+Qn9IJnilYbIKzpB9n4aBsg=="],
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.8", "", { "os": "linux", "cpu": "arm" }, "sha512-Jmzr3FA4S2tHhaC6yCjac3rGf7hG9R6Gf2z9i9JFcuyy0u79HfQsh/thifbYTF2ic82KJovKKkIB6Z9TdNhCXQ=="],
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-qq7jXtO1+UEtCmCeBBIRDrPFIVI4ilEQ97qgBGdwXAARrUqSn/L9fUrkb1XP/mvVtoVeR2bt/0L77xx53bPZ/Q=="],
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-O6b8QesPbJCRshsNApsOIpzKt3ztG35gfX9tEf4arD7mwNinsoCKxkj8TgEE0YRjmjtO3r9FlJnT/ENd9EVefQ=="],
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.8", "", { "os": "linux", "cpu": "x64" }, "sha512-32iEXX/pXwikshNOGnERAFwFSfiltmijMIAbUhnNyjFr3tmWmMJWQKU2vNcFX0DACSXJ3ZWcSkzNbaKTdngH6g=="],
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.8", "", { "os": "linux", "cpu": "x64" }, "sha512-s+VSSD+TfZeMEsCaFaHTaY5YNj3Dri8rST09gMvYQKwPphacRG7wbuQ5ZJMIJXN/puxPcg/nU+ucvWguPpvBDg=="],
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.8", "", { "cpu": "none" }, "sha512-CXBPVFkpDjM67sS1psWohZ6g/2/cd+cq56vPxK4JeawelxwK4YECgl9Y9TjkE2qfF+9/s1tHHJqrC4SS6cVvSg=="],
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-7GmYk1n28teDHUjPlIx4Z6Z4hHEgvP5ZW2QS9ygnDAdI/myh3HTHjDqtSqgu1BpRoI4OiLx+fThAyA1JePoENA=="],
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.8", "", { "os": "win32", "cpu": "x64" }, "sha512-fou+U20j+Jl0EHwK92spoWISON2OBnCazIc038Xj2TdweYV33ZRkS9nwqiUi2d/Wba5xg5UoHfvynnb/UB49cQ=="],
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.8", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.8", "@tailwindcss/oxide": "4.1.8", "postcss": "^8.4.41", "tailwindcss": "4.1.8" } }, "sha512-vB/vlf7rIky+w94aWMw34bWW1ka6g6C3xIOdICKX2GC0VcLtL6fhlLiafF0DVIwa9V6EHz8kbWMkS2s2QvvNlw=="],
"@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="],
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
"binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
"chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
"chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
"cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
"daisyui": ["daisyui@5.0.40", "", {}, "sha512-5GpRcRsArYjkeIhUrlMMIGGZzbuPCpCkEMokiO3PtkVr1/IFZezJO6lLR+lLxxMGm46EwRG/fSJw4VLeNMBZnw=="],
"dependency-graph": ["dependency-graph@1.0.0", "", {}, "sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg=="],
"detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="],
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"enhanced-resolve": ["enhanced-resolve@5.18.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg=="],
"esbuild": ["esbuild@0.25.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.5", "@esbuild/android-arm": "0.25.5", "@esbuild/android-arm64": "0.25.5", "@esbuild/android-x64": "0.25.5", "@esbuild/darwin-arm64": "0.25.5", "@esbuild/darwin-x64": "0.25.5", "@esbuild/freebsd-arm64": "0.25.5", "@esbuild/freebsd-x64": "0.25.5", "@esbuild/linux-arm": "0.25.5", "@esbuild/linux-arm64": "0.25.5", "@esbuild/linux-ia32": "0.25.5", "@esbuild/linux-loong64": "0.25.5", "@esbuild/linux-mips64el": "0.25.5", "@esbuild/linux-ppc64": "0.25.5", "@esbuild/linux-riscv64": "0.25.5", "@esbuild/linux-s390x": "0.25.5", "@esbuild/linux-x64": "0.25.5", "@esbuild/netbsd-arm64": "0.25.5", "@esbuild/netbsd-x64": "0.25.5", "@esbuild/openbsd-arm64": "0.25.5", "@esbuild/openbsd-x64": "0.25.5", "@esbuild/sunos-x64": "0.25.5", "@esbuild/win32-arm64": "0.25.5", "@esbuild/win32-ia32": "0.25.5", "@esbuild/win32-x64": "0.25.5" }, "bin": "bin/esbuild" }, "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ=="],
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
"fdir": ["fdir@6.4.5", "", { "peerDependencies": { "picomatch": "^3 || ^4" } }, "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw=="],
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
"fs-extra": ["fs-extra@11.3.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
"glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="],
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
"jiti": ["jiti@2.4.2", "", { "bin": "lib/jiti-cli.mjs" }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="],
"jsonfile": ["jsonfile@6.1.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ=="],
"lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="],
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ=="],
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA=="],
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig=="],
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.1", "", { "os": "linux", "cpu": "arm" }, "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q=="],
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw=="],
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ=="],
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw=="],
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ=="],
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA=="],
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.1", "", { "os": "win32", "cpu": "x64" }, "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg=="],
"lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="],
"magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],
"minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
"minizlib": ["minizlib@3.0.2", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA=="],
"mkdirp": ["mkdirp@3.0.1", "", { "bin": "dist/cjs/src/bin.js" }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="],
"nanoid": ["nanoid@3.3.11", "", { "bin": "bin/nanoid.cjs" }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="],
"pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="],
"postcss": ["postcss@8.5.3", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A=="],
"postcss-cli": ["postcss-cli@11.0.1", "", { "dependencies": { "chokidar": "^3.3.0", "dependency-graph": "^1.0.0", "fs-extra": "^11.0.0", "picocolors": "^1.0.0", "postcss-load-config": "^5.0.0", "postcss-reporter": "^7.0.0", "pretty-hrtime": "^1.0.3", "read-cache": "^1.0.0", "slash": "^5.0.0", "tinyglobby": "^0.2.12", "yargs": "^17.0.0" }, "peerDependencies": { "postcss": "^8.0.0" }, "bin": { "postcss": "index.js" } }, "sha512-0UnkNPSayHKRe/tc2YGW6XnSqqOA9eqpiRMgRlV1S6HdGi16vwJBx7lviARzbV1HpQHqLLRH3o8vTcB0cLc+5g=="],
"postcss-load-config": ["postcss-load-config@5.1.0", "", { "dependencies": { "lilconfig": "^3.1.1", "yaml": "^2.4.2" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1" }, "optionalPeers": ["tsx"] }, "sha512-G5AJ+IX0aD0dygOE0yFZQ/huFFMSNneyfp0e3/bT05a8OfPC5FUoZRPfGijUdGOJNMewJiwzcHJXFafFzeKFVA=="],
"postcss-reporter": ["postcss-reporter@7.1.0", "", { "dependencies": { "picocolors": "^1.0.0", "thenby": "^1.3.4" }, "peerDependencies": { "postcss": "^8.1.0" } }, "sha512-/eoEylGWyy6/DOiMP5lmFRdmDKThqgn7D6hP2dXKJI/0rJSO1ADFNngZfDzxL0YAxFvws+Rtpuji1YIHj4mySA=="],
"prettier": ["prettier@3.5.3", "", { "bin": "bin/prettier.cjs" }, "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw=="],
"prettier-plugin-go-template": ["prettier-plugin-go-template@0.0.15", "", { "dependencies": { "ulid": "^2.3.0" }, "peerDependencies": { "prettier": "^3.0.0" } }, "sha512-WqU92E1NokWYNZ9mLE6ijoRg6LtIGdLMePt2C7UBDjXeDH9okcRI3zRqtnWR4s5AloiqyvZ66jNBAa9tmRY5EQ=="],
"pretty-hrtime": ["pretty-hrtime@1.0.3", "", {}, "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A=="],
"read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="],
"readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
"rollup": ["rollup@4.41.1", "", { "dependencies": { "@types/estree": "1.0.7" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.41.1", "@rollup/rollup-android-arm64": "4.41.1", "@rollup/rollup-darwin-arm64": "4.41.1", "@rollup/rollup-darwin-x64": "4.41.1", "@rollup/rollup-freebsd-arm64": "4.41.1", "@rollup/rollup-freebsd-x64": "4.41.1", "@rollup/rollup-linux-arm-gnueabihf": "4.41.1", "@rollup/rollup-linux-arm-musleabihf": "4.41.1", "@rollup/rollup-linux-arm64-gnu": "4.41.1", "@rollup/rollup-linux-arm64-musl": "4.41.1", "@rollup/rollup-linux-loongarch64-gnu": "4.41.1", "@rollup/rollup-linux-powerpc64le-gnu": "4.41.1", "@rollup/rollup-linux-riscv64-gnu": "4.41.1", "@rollup/rollup-linux-riscv64-musl": "4.41.1", "@rollup/rollup-linux-s390x-gnu": "4.41.1", "@rollup/rollup-linux-x64-gnu": "4.41.1", "@rollup/rollup-linux-x64-musl": "4.41.1", "@rollup/rollup-win32-arm64-msvc": "4.41.1", "@rollup/rollup-win32-ia32-msvc": "4.41.1", "@rollup/rollup-win32-x64-msvc": "4.41.1", "fsevents": "~2.3.2" }, "bin": "dist/bin/rollup" }, "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw=="],
"slash": ["slash@5.1.0", "", {}, "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"tailwindcss": ["tailwindcss@4.1.8", "", {}, "sha512-kjeW8gjdxasbmFKpVGrGd5T4i40mV5J2Rasw48QARfYeQ8YS9x02ON9SFWax3Qf616rt4Cp3nVNIj6Hd1mP3og=="],
"tapable": ["tapable@2.2.2", "", {}, "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg=="],
"tar": ["tar@7.4.3", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.0.1", "mkdirp": "^3.0.1", "yallist": "^5.0.0" } }, "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw=="],
"thenby": ["thenby@1.3.4", "", {}, "sha512-89Gi5raiWA3QZ4b2ePcEwswC3me9JIg+ToSgtE0JWeCynLnLxNr/f9G+xfo9K+Oj4AFdom8YNJjibIARTJmapQ=="],
"tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="],
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
"ulid": ["ulid@2.4.0", "", { "bin": "bin/cli.js" }, "sha512-fIRiVTJNcSRmXKPZtGzFQv9WRrZ3M9eoptl/teFJvjOzmpU+/K/JH6HZ8deBfb5vMEpicJcLn7JmvdknlMq7Zg=="],
"universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
"vite": ["vite@6.3.5", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx"], "bin": "bin/vite.js" }, "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ=="],
"wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
"yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
"yaml": ["yaml@2.8.0", "", { "bin": "bin.mjs" }, "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ=="],
"yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
"yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
}
}

View File

@@ -1,5 +1,6 @@
{{- $date := Today -}}
<footer id="footer" class="container-normal pb-1.5 text-base text-gray-800 relative" x-data="{ openusermenu: false }">
<!-- INFO: User menu -->
{{- if .request.user -}}
<div class="" x-show="openusermenu">
<div
@@ -8,7 +9,7 @@
[&>a]:block [&>a]:px-3 [&>a]:py-2 [&>a]:text-sm [&>a]:w-full [&>a]:text-left
[&>a]:whitespace-nowrap [&>a]:transition-all [&>a]:duration-200 [&>a]:border-b
[&>a]:last:border-b-0">
<a href="/user/{{ .request.user.Id }}/edit?redirectTo={{ .request.fullpath }}" class="">
<a :href="'/user/{{ .request.user.Id }}/edit?redirectTo=' + window.location" class="">
<i class="ri-user-3-line"></i>
Profil bearbeiten
</a>
@@ -29,8 +30,9 @@
</div>
</div>
{{- end -}}
<!-- END: User menu -->
<!-- INFO: Actual Footer -->
<div class="mt-12 pt-3 flex flex-row justify-between">
<div>
<i class="ri-creative-commons-line"></i>

View File

@@ -27,12 +27,5 @@
<link rel="stylesheet" type="text/css" href="/assets/style.css" />
<script type="module">
document.body.addEventListener("htmx:responseError", function (event) {
const config = event.detail.requestConfig;
if (config.boosted) {
document.body.innerHTML = event.detail.xhr.responseText;
const newUrl = event.detail.xhr.responseURL || config.url;
window.history.pushState(null, "", newUrl);
}
});
ShowBoostedErrors();
</script>

View File

@@ -1,26 +1,11 @@
{
"name": "caveman_views",
"version": "1.0.0",
"description": "default views for caveman",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"tailwind": "tailwindcss -i transform/site.css -o assets/style.css",
"css": "postcss transform/site.css -o assets/style.css",
"preview": "vite preview"
},
"author": "Simon Martens",
"repository": {
"type": "git",
"url": "github.com/Simon-Martens/pocketcatalog"
},
"keywords": [
"DB",
"htmx",
"frontend"
],
"author": "Simon Martens",
"license": "MIT",
"devDependencies": {
"@tailwindcss/postcss": "^4.0.0",
"daisyui": "^5.0.0-beta.8",
@@ -30,5 +15,20 @@
"prettier-plugin-go-template": "^0.0.15",
"tailwindcss": "^4.0.0",
"vite": "^6.0.0"
}
},
"description": "default views for caveman",
"keywords": [
"DB",
"htmx",
"frontend"
],
"license": "MIT",
"scripts": {
"dev": "vite",
"build": "vite build",
"tailwind": "tailwindcss -i transform/site.css -o assets/style.css",
"css": "postcss transform/site.css -o assets/style.css",
"preview": "vite preview"
},
"type": "module"
}

View File

@@ -1,5 +1,5 @@
export default {
plugins: {
'@tailwindcss/postcss': {},
"@tailwindcss/postcss": {},
},
};

View File

@@ -9,23 +9,70 @@ type AlmanachResult struct {
EntriesSeries map[string]*dbmodels.REntriesSeries // <- Key is series id
EntriesAgents []*dbmodels.REntriesAgents
ContentsAgents map[string][]*dbmodels.RContentsAgents // <- Key is content id
User *dbmodels.User
Types []string
HasScans bool
}
-->
<div class="flex container-normal bg-slate-100 mx-auto !pt-36 px-8">
<div class="flex-col w-full">
{{ if $model.redirect_url }}
<a href="{{ $model.redirect_url }}" class="text-gray-700 hover:text-slate-950">
<i class="ri-arrow-left-s-line"></i> Zurück
</a>
{{ else }}
<a href="/" class="text-gray-700 hover:text-slate-950">
<i class="ri-arrow-left-s-line"></i> Startseite
</a>
{{ end }}
<h1 class="text-2xl self-baseline w-full my-6 font-bold text-slate-900">Almanach bearbeiten</h1>
<div class="flex container-normal bg-slate-100 mx-auto px-8">
<div class="flex flex-row w-full justify-between pb-6">
<div class="flex flex-col justify-end-safe pt-36 flex-2/5">
<a href="/almanach/{{ $model.result.Entry.MusenalmID }}" class="text-gray-700 hover:text-slate-950 block mb-2"><i class="ri-arrow-left-s-line"></i>Anschauen</a>
<h1 class="text-2xl w-full font-bold text-slate-900">Almanach bearbeiten</h1>
<!--
<div class="mt-1">
{{ $model.result.Entry.PreferredTitle }}
</div>
-->
</div>
<div class="flex flex-row" id="almanach-header-data">
<div class="flex flex-col justify-end gap-y-6 pr-20">
<div class="">
<div class="font-bold text-sm">
<i class="ri-database-2-line"></i> Datenbank-ID
<tool-tip position="right" class="!inline">
<div class="data-tip">Die Datenbank-ID kann zur Fehlerdiagnose hilfreich sein.</div>
<i class="ri-information-line"></i>
</tool-tip>
</div>
<div class="">{{ $model.result.Entry.Id }}</div>
</div>
</div>
<div class="flex flex-col justify-end gap-y-6 pr-4">
<div class="">
<div class="font-bold text-sm mb-1">
<i class="ri-hashtag"></i> Alm-Nummer
<tool-tip position="right" class="!inline">
<div class="data-tip">Die Alm-Nr ist Teil der URL und wird automatisch vergeben.</div>
<i class="ri-information-line"></i>
</tool-tip>
</div>
<div class="px-1.5 py-0.5 rounded-xs bg-gray-200 w-fit font-bold">{{ $model.result.Entry.MusenalmID }}</div>
</div>
<div class="">
<div class="font-bold text-sm mb-1"><i class="ri-calendar-line"></i> Zuletzt bearbeitet</div>
<div>
<div class="px-1.5 py-0.5 rounded-xs bg-gray-200 w-fit">
{{ GermanDate $model.result.Entry.Updated }};
{{ GermanTime
$model.result.Entry.Updated
}}
</div>
{{- if $model.result.User -}}
<div class="px-1.5 py-0.5 rounded-xs mt-1.5 bg-gray-200 w-fit">
<i class="ri-user-line mr-1"></i> {{- $model.result.User.Name -}}
</div>
{{- end -}}
</div>
</div>
</div>
</div>
</div>
</div>
<div class="container-normal mx-auto px-8 mt-4">
{{ template "_usermessage" $model }}
<form class="w-full grid grid-cols-12 gap-4" id="changealmanachform" x-target="changealmanachform user-message almanach-header-data" hx-boost="false" method="POST"></form>
</div>

View File

@@ -20,15 +20,11 @@
<div id="breadcrumbs">
<div>
<div>
<a href="/personen?letter={{- First $model.result.Agent.Name -}}"
>Personen &amp; Körperschaften</a
>
<a href="/personen?letter={{- First $model.result.Agent.Name -}}">Personen &amp; Körperschaften</a>
<i class="ri-arrow-right-wide-line"></i> <b>{{ $model.result.Agent.Name }}</b>
</div>
<div class="backbutton">
<a href="/personen/" class="no-underline">
<i class="ri-arrow-left-long-line"></i> Alle Personen &amp; Körperschaften
</a>
<a href="/personen/" class="no-underline"> <i class="ri-arrow-left-long-line"></i> Alle Personen &amp; Körperschaften </a>
</div>
</div>
</div>
@@ -155,32 +151,17 @@
<input type="checkbox" id="showall" autocomplete="off" />
<label for="showall" class="cursor-pointer select-none ml-1">Alle anzeigen</label>
<script type="module">
const tablist = document.getElementById("entries-tabs");
const checkbox = document.getElementById("showall");
entriestabs?.hookupShowAll(showall);
{{- if eq (len $model.result.CResult) 1 -}}
if (checkbox) {
if (tablist) {
tablist.showAll();
}
checkbox.checked = true;
checkbox.disabled = true;
}
{{- else -}}
if (tablist && checkbox) {
checkbox.addEventListener("change", () => {
if (checkbox && checkbox.checked) {
tablist.showAll();
} else {
tablist.default();
}
});
}
entriestabs?.showAll();
showall?.checked = true;
showall?.disabled = true;
{{- end -}}
</script>
</div>
</div>
<div class="mt-8">
<tab-list id="entries-tabs">
<tab-list id="entriestabs">
{{- range $_, $e := $model.result.CResult -}}
{{- $contents := index $model.result.Contents $e.Id -}}
<div
@@ -191,8 +172,7 @@
<i class="ri-arrow-right-s-fill show-closed"></i>
<i class="ri-arrow-down-s-fill show-opened"></i>
</div>
<div
class="inline-block font-sans bg-slate-800 text-white h-max text-sm px-1.5 rounded">
<div class="inline-block font-sans bg-slate-800 text-white h-max text-sm px-1.5 rounded">
{{- len $contents -}}
</div>
</div>

View File

@@ -14,42 +14,54 @@
<div class="flex container-normal mx-auto px-8 mt-4">
<div class="flex-col max-w-2xl w-full">
<form class="w-full grid grid-cols-3 gap-4" id="changeuserform" x-target="changeuserform footer" hx-boost="false" method="POST" x-data="{ openpw: false }">
<form
class="w-full flex flex-col gap-4 dbform"
id="changeuserform"
x-target="changeuserform
footer"
hx-boost="false"
method="POST"
x-data="{ openpw: false }"
@rbichange="FormHasChanged($el) ? resetb.classList.add('hidden') : resetb.classList.remove('hidden')">
<!-- INFO: MESSAGES -->
<div class="col-span-3">
{{ template "_usermessage" $model }}
</div>
<div
class="rounded-xs col-span-3 border-2 border-transparent px-3
py-1 pb-1.5 border-l-2 focus-within:border-l-slate-600
bg-slate-200 focus-within:bg-slate-100 transition-all duration-100">
<label for="username" class="text-sm text-gray-700 font-bold"> Name <i class="ri-text"></i> </label>
<input type="text" name="name" id="name" class="mt-1 block w-full focus:border-none focus:outline-none" placeholder="" required autocomplete="off" value="{{ $model.user.Name }}" autofocus />
<!-- INFO: BASIC FELDER -->
<div class="inputwrapper">
<label for="username" class="inputlabel"> Name <i class="ri-text"></i> </label>
<div class="flex flex-row">
<input type="text" name="name" id="name" class="inputinput" placeholder="" required autocomplete="off" value="{{ $model.user.Name }}" autofocus />
<reset-button controls="name" wrapper-class="inputwrapper" modified-class-suffix="modified"></reset-button>
</div>
</div>
<div
class="rounded-xs col-span-3 border-2 border-transparent px-3
py-1 pb-1.5 border-l-2 focus-within:border-l-slate-600
bg-slate-200 focus-within:bg-slate-100 transition-all duration-100">
<label for="username" class="text-sm text-gray-700 font-bold"> E-Mail <i class="ri-at-line"></i> </label>
<input type="email" name="username" id="username" autocomplete="off" class="mt-1 block w-full rounded-md focus:border-none focus:outline-none" placeholder="" required value="{{ $model.user.Email }}" />
<div class="inputwrapper">
<label for="username" class="inputlabel"> E-Mail <i class="ri-at-line"></i> </label>
<div class="flex flex-row">
<input type="email" name="username" id="username" autocomplete="off" class="inputinput" placeholder="" required value="{{ $model.user.Email }}" />
<reset-button controls="username" wrapper-class="inputwrapper" modified-class-suffix="modified"></reset-button>
</div>
</div>
<div
class="rounded-xs col-span-3 border-2 border-transparent px-3
py-1 pb-1.5 border-l-2 focus-within:border-l-slate-600
bg-slate-200 focus-within:bg-slate-100 transition-all duration-100">
<label for="role" class="text-sm text-gray-700 font-bold"> Rolle <i class="ri-user-3-line"></i> </label>
<select
{{ if not (eq $model.request.user.Role "Admin") -}}
disabled
{{- end }}
name="role"
id="role"
autocomplete="off"
class="mt-1 block w-full rounded-md focus:border-none focus:outline-none
disabled:opacity-50">
<option value="User" {{ if eq $model.user.Role "User" }}selected{{ end }}>Benutzer</option>
<option value="Editor" {{ if eq $model.user.Role "Editor" }}selected{{ end }}>Redakteur</option>
<option value="Admin" {{ if eq $model.user.Role "Admin" }}selected{{ end }}>Administrator</option>
</select>
<!-- INFO: ROLLE -->
<div class="inputwrapper">
<label for="role" class="inputlabel"> Rolle <i class="ri-user-3-line"></i> </label>
<div class="flex flex-row">
<select
{{ if not (eq $model.request.user.Role "Admin") -}}
disabled
{{- end }}
name="role"
id="role"
autocomplete="off"
class="inputselect">
<option value="User" {{ if eq $model.user.Role "User" }}selected{{ end }}>Benutzer</option>
<option value="Editor" {{ if eq $model.user.Role "Editor" }}selected{{ end }}>Redakteur</option>
<option value="Admin" {{ if eq $model.user.Role "Admin" }}selected{{ end }}>Administrator</option>
</select>
<reset-button controls="role" wrapper-class="inputwrapper" modified-class-suffix="modified"></reset-button>
</div>
</div>
{{- if and
(eq $model.request.user.Role "Admin")
@@ -70,63 +82,49 @@
<p class="text-sm text-gray-700 max-w-[80ch]">Achtung! Wenn Sie die Rolle eines Benutzers ändern, wird dieser unter Umständen von laufenden Sitzungen abgemeldet und muss sich erneut anmelden.</p>
</div>
{{- end -}}
<!-- INFO: PW ÄNDERN AUSKLAPPEN -->
<div class="col-span-3">
<div class="flex items-center">
<input type="checkbox" name="openpw" id="openpw" x-model="openpw" class="mr-2" />
<label for="openpw" class="text-sm text-gray-700 font-bold"> Passwort ändern <i class="ri-key-2-line"></i> </label>
<label for="openpw" class="inputlabeltext"> Passwort ändern <i class="ri-key-2-line"></i> </label>
</div>
</div>
<!-- INFO: PASSWORT -->
{{- if not (eq $model.request.user.Role "Admin") -}}
<div
x-bind:style="!openpw ? 'display:none' : ''"
class="rounded-xs col-span-3 border-2 border-transparent px-3
py-1 pb-1.5 border-l-2 focus-within:border-l-slate-600
bg-slate-200 focus-within:bg-slate-100 transition-all duration-100">
<label for="password_old" class="text-sm text-gray-700 font-bold"> Altes Passwort </label>
<input x-bind:type="openpw ? 'password' : 'hidden'" name="password_old" id="password_old" class="mt-1 block w-full rounded-md focus:border-none focus:outline-none" placeholder="" required />
<div x-bind:style="!openpw ? 'display:none' : ''" class="inputwrapper">
<label for="password_old" class="inputlabel"> Altes Passwort </label>
<input x-bind:type="openpw ? 'password' : 'hidden'" name="password_old" id="password_old" class="inputinput" placeholder="" required />
</div>
{{- end -}}
<div
x-bind:style="!openpw ? 'display:none' : ''"
class="rounded-xs col-span-3 border-2 border-transparent px-3
py-1 pb-1.5 border-l-2 focus-within:border-l-slate-600
bg-slate-200 focus-within:bg-slate-100 transition-all duration-100">
<label for="password" class="text-sm text-gray-700 font-bold"> Neues Passwort </label>
<input x-bind:type="openpw ? 'password' : 'hidden'" minlength="10" name="password" id="password" class="mt-1 block w-full rounded-md focus:border-none focus:outline-none" placeholder="" required />
<div x-bind:style="!openpw ? 'display:none' : ''" class="inputwrapper">
<label for="password" class="inputlabel"> Neues Passwort </label>
<input x-bind:type="openpw ? 'password' : 'hidden'" minlength="10" name="password" id="password" class="inputinput" placeholder="" required />
</div>
<div
x-bind:style="!openpw ? 'display:none' : ''"
class="rounded-xs col-span-3 border-2 border-transparent px-3
py-1 pb-1.5 border-l-2 focus-within:border-l-slate-600
bg-slate-200 focus-within:bg-slate-100 transition-all duration-100">
<label for="password_repeat" class="text-sm text-gray-700 font-bold"> Passwort wiederholen </label>
<input x-bind:type="openpw ? 'password' : 'hidden'" minlength="10" name="password_repeat" id="password_repeat" class="mt-1 block w-full rounded-md focus:border-none focus:outline-none" placeholder="" required />
<div x-bind:style="!openpw ? 'display:none' : ''" class="inputwrapper">
<label for="password_repeat" class="inputlabel"> Passwort wiederholen </label>
<input x-bind:type="openpw ? 'password' : 'hidden'" minlength="10" name="password_repeat" id="password_repeat" class="inputinput" placeholder="" required />
</div>
<div class="col-span-3 flex justify-end" x-bind:style="!openpw ? 'display:none' : ''">
<input type="checkbox" name="logout" id="logout" class="mr-2" x-bind:style="!openpw ? 'display:none' : ''" />
<label for="logout" class="text-sm text-gray-700 font-bold"> überall ausloggen <i class="ri-logout-box-line"></i> </label>
<label for="logout" class="inputlabeltext"><i class="ri-logout-box-line"></i> überall ausloggen</label>
</div>
<div class="col-span-1 col-start-2">
<a
href="/user/{{ $model.user.Id }}/edit?redirectTo={{ $model.redirect_url }}"
type="cancel"
class="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">
Zurücksetzen
</a>
</div>
<div class="col-span-1 col-start-3">
<input type="hidden" name="csrf_token" id="csrf_token" required value="{{ $model.csrf_token }}" />
<button
type="submit"
class="w-full inline-flex justify-center py-2 px-4 border border-transparent rounded-md
shadow-sm text-sm font-medium text-white bg-slate-700 hover:bg-slate-800 cursor-pointer focus:outline-none
focus:ring-2 focus:ring-offset-2 focus:ring-slate-500">
Speichern
</button>
<!-- INFO: Buttons -->
<div class="grid grid-cols-3 gap-4 col-span-3 mt-6">
<div id="resetb" class="col-span-1 col-start-2 hidden">
<a href="/user/{{ $model.user.Id }}/edit?redirectTo={{ $model.redirect_url }}" type="cancel" class="resetbutton"> Zurücksetzen </a>
</div>
<div class="col-span-1 col-start-3">
<input type="hidden" name="csrf_token" id="csrf_token" required value="{{ $model.csrf_token }}" />
<button type="submit" class="submitbutton">Speichern</button>
</div>
</div>
</form>
<!-- INFO: Aktivieren/Deaktivieren -->
<div class="col-span-1 mt-12 justify-self-end self-end items-end flex flex-row justify-end">
{{ if not $model.user.Deactivated }}
<form id="actbtn" x-init @ajax:before="confirm('Der Benutzer {{ $model.user.Name }} wird deaktiviert und kann sich nicht mehr einloggen. Sicher?') || $event.preventDefault()" action="/user/{{ $model.user.Id }}/deactivate/" method="POST" hx-boost="false" x-target="user-message footer actbtn" x-target.away="_top">

View File

@@ -4,73 +4,8 @@
<script src="/assets/js/qrcode.min.js"></script>
<script type="module">
/**
* @param {number} timeout - Maximum time to wait in milliseconds.
* @param {number} interval - How often to check in milliseconds.
* @returns {Promise<Function>} Resolves with the QRCode constructor when available.
*/
function getQRCodeWhenAvailable(timeout = 5000, interval = 100) {
return new Promise((resolve, reject) => {
let elapsedTime = 0;
const checkInterval = setInterval(() => {
if (typeof window.QRCode === "function") {
clearInterval(checkInterval);
resolve(window.QRCode); // Resolve with the QRCode object/function
} else {
elapsedTime += interval;
if (elapsedTime >= timeout) {
clearInterval(checkInterval);
console.error("Timed out waiting for QRCode to become available.");
reject(new Error("QRCode not available after " + timeout + "ms. Check if qrcode.min.js is loaded correctly and sets window.QRCode."));
}
}
}, interval);
});
}
// INFO: We have to wait for the QRCode object to be available. It's messy.
async function genQRCode() {
console.debug("Generating QR Code...");
const QRCode = await getQRCodeWhenAvailable();
const tokenElement = document.getElementById("token");
const qrElement = document.getElementById("qr");
if (qrElement) {
// INFO: Clear previous QR code if any
// Also hide it initially to prevent flickering
qrElement.innerHTML = "";
qrElement.classList.add("hidden");
new QRCode(qrElement, {
text: tokenElement.value,
width: 1280,
height: 1280,
colorDark: "#000000",
colorLight: "#ffffff",
correctLevel: QRCode.CorrectLevel.H,
});
setTimeout(() => {
qrElement.classList.remove("hidden");
}, 20);
}
// Add event listeners to the token input field to select its content on focus or click
if (tokenElement) {
tokenElement.addEventListener("focus", (ev) => {
ev.preventDefault();
tokenElement.select();
});
tokenElement.addEventListener("mousedown", (ev) => {
ev.preventDefault();
tokenElement.select();
});
tokenElement.addEventListener("mouseup", (ev) => {
ev.preventDefault();
tokenElement.select();
});
}
}
genQRCode();
window.genQRCode = genQRCode;
GenQRCode(token.value);
SelectableInput(token);
</script>
<div class="flex container-normal bg-slate-100 mx-auto !pt-36 px-8">
@@ -135,7 +70,7 @@
<i class="ri-external-link-line"></i>
</a>
</tool-tip>
<form class="" method="POST" hx-boost="false" x-target="token access-link qrinfo" @ajax:after="genQRCode()">
<form class="" method="POST" hx-boost="false" x-target="token access-link qrinfo" @ajax:after="GenQRCode(token.value); SelectableInput(token)">
<input type="hidden" name="csrf_token" id="csrf_token" required value="{{ $model.csrf_token }}" />
<div class="col-span-9 flex flex-row items-center justify-center">
<tool-tip position="top">

276
views/transform/form.css Normal file
View File

@@ -0,0 +1,276 @@
@layer components {
.dbform .inputwrapper {
@apply rounded-xs border-2 border-transparent px-3
py-1 pb-1.5 border-l-2 focus-within:border-l-slate-600
bg-slate-200 focus-within:bg-slate-100 transition-all duration-100;
}
.dbform .inputwrapper .inputlabel {
@apply text-sm text-gray-700 font-bold;
}
.inputlabeltext {
@apply text-sm text-gray-700 font-bold;
}
.dbform .inputwrapper .inputselect {
@apply mt-1 block w-full rounded-md focus:border-none focus:outline-none
disabled:opacity-50;
}
.dbform .inputwrapper .inputinput {
@apply mt-1 block w-full focus:border-none focus:outline-none;
}
.dbform .submitbutton {
@apply w-full inline-flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-slate-700 hover:bg-slate-800 cursor-pointer focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-slate-500 active:bg-slate-900 transition-all duration-75;
}
.dbform .resetbutton {
@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;
}
/* Multi-Select-Role example styles */
.msr-selected-items-container {
@apply rounded-md;
}
.msr-placeholder-no-selection-text {
@apply text-sm text-gray-500 italic px-2 py-1;
}
.msr-input-area-wrapper {
@apply p-2 rounded-md;
}
.msr-input-area-wrapper.msr-input-area-default-border {
@apply border border-gray-300;
}
.msr-input-area-wrapper.msr-input-area-default-border:focus-within {
@apply focus-within:border-gray-500 focus-within:ring-1 focus-within:ring-gray-400;
}
.msr-input-area-wrapper.msr-input-area-staged {
@apply border border-transparent;
}
.msr-text-input {
@apply bg-transparent text-sm placeholder-gray-400;
}
.msr-selected-item-pill {
@apply bg-gray-200 text-gray-700 px-3 py-[0.3rem] rounded-md text-sm inline-flex items-center m-0.5;
}
.msr-item-name {
@apply font-medium;
}
.msr-item-additional-data {
@apply text-xs ml-1 text-gray-600;
}
.msr-selected-item-role {
@apply font-semibold text-xs ml-1 text-gray-800;
}
.msr-selected-item-delete-btn {
@apply bg-transparent border-none text-gray-500 text-lg leading-none px-1 cursor-pointer opacity-60 transition-opacity duration-200;
}
.msr-selected-item-delete-btn:hover {
@apply hover:opacity-100 hover:text-gray-900;
}
.msr-staged-item-pill {
@apply bg-gray-100 text-gray-800 px-2 py-1 rounded-md text-sm font-medium;
}
.msr-staged-item-text {
@apply mr-2;
}
.msr-staged-role-select {
@apply px-2 py-1 text-sm rounded-md border border-gray-300 bg-white outline-none text-gray-700;
}
.msr-staged-role-select:focus {
@apply focus:border-gray-500 focus:ring-1 focus:ring-gray-400;
}
.msr-staged-cancel-btn {
@apply w-5 h-5 bg-gray-200 text-gray-600 rounded-full text-sm leading-none cursor-pointer;
}
.msr-staged-cancel-btn:hover {
@apply hover:bg-gray-300 hover:text-gray-800;
}
.msr-pre-add-button {
@apply w-10 h-[42px] text-xl rounded-md bg-gray-50 text-gray-700 border border-gray-300 font-semibold outline-none;
}
.msr-pre-add-button:focus {
@apply focus:border-gray-500 focus:ring-1 focus:ring-gray-400;
}
.msr-pre-add-button:hover {
@apply hover:bg-gray-100;
}
.msr-pre-add-button:disabled {
@apply disabled:bg-gray-200 disabled:text-gray-400 disabled:cursor-not-allowed disabled:border-gray-200;
}
.msr-pre-add-button.hidden {
@apply hidden;
}
.msr-add-button {
@apply px-4 py-2 text-sm rounded-md bg-gray-600 text-white font-medium;
}
.msr-add-button:hover {
@apply hover:bg-gray-700;
}
.msr-add-button:disabled {
@apply disabled:bg-gray-300 disabled:cursor-not-allowed;
}
.msr-add-button.hidden {
@apply hidden;
}
.msr-options-list {
@apply bg-white border border-gray-300 rounded-md shadow-md;
}
.msr-options-list.hidden {
@apply hidden;
}
.msr-option-item {
@apply px-3 py-2 text-sm cursor-pointer transition-colors duration-75;
}
.msr-option-item:hover {
@apply bg-gray-100 text-gray-800;
}
.msr-option-item-highlighted {
@apply bg-gray-100 text-gray-800;
}
.msr-option-item-name {
@apply font-medium;
}
.msr-option-item-detail {
@apply text-xs ml-2 text-gray-500;
}
.msr-option-item-highlighted .msr-option-item-detail,
.msr-option-item:hover .msr-option-item-detail {
/* Ensure detail text color changes on hover too */
@apply text-gray-600;
}
multi-select-role[disabled] {
/* This remains standard CSS as Tailwind's disabled: variant is for native elements */
opacity: 0.6;
cursor: not-allowed;
}
.msr-hidden-select {
/* No specific styling needed as it's visually hidden by JS/inline style */
}
/* --- MultiSelectSimple Component Base Styles (using @apply) --- */
.mss-component-wrapper {
/* 'relative' is set inline for positioning dropdown */
}
.mss-selected-items-container {
@apply border border-gray-300 p-1.5 rounded;
/* Tailwind classes from component: flex flex-wrap gap-1 mb-1 min-h-[38px] */
}
.mss-no-items-text {
@apply italic text-xs text-gray-500 p-1 w-full; /* Adjusted font size slightly to match 'xs' */
}
.mss-selected-item-pill {
@apply bg-gray-200 text-gray-800 py-0.5 px-2 rounded text-xs leading-5; /* Adjusted font size and padding */
/* Tailwind classes from component: flex items-center */
}
.mss-selected-item-text {
/* Base styles for text part of the pill */
}
.mss-selected-item-pill-detail {
@apply ml-1 opacity-75 text-xs text-gray-600;
}
.mss-selected-item-pill-detail.hidden {
@apply hidden;
}
.mss-selected-item-delete-btn {
@apply bg-transparent border-none text-gray-600 opacity-70 cursor-pointer ml-1 text-base leading-none align-middle hover:opacity-100 hover:text-gray-900 disabled:opacity-40 disabled:cursor-not-allowed;
}
.mss-input-controls-container {
/* Tailwind classes from component: flex items-center space-x-2 */
}
.mss-input-wrapper {
@apply border border-gray-300 rounded;
/* Tailwind classes from component: relative flex items-center flex-grow */
}
.mss-input-wrapper-focused {
@apply border-indigo-600 ring-1 ring-indigo-600; /* Using ring for focus shadow */
}
.mss-text-input {
@apply py-1.5 px-2 text-sm;
/* Tailwind classes from component: w-full outline-none bg-transparent */
}
.mss-text-input::placeholder {
@apply text-gray-400 italic;
}
.mss-create-new-button {
@apply bg-gray-100 text-gray-700 border border-gray-300 py-1 px-1.5 text-sm rounded hover:bg-gray-200 hover:border-gray-400 disabled:bg-gray-50 disabled:text-gray-400 disabled:border-gray-200 disabled:opacity-70 disabled:cursor-not-allowed;
}
.mss-create-new-button.hidden {
@apply !hidden; /* Ensure it hides */
}
.mss-options-list {
@apply bg-white border border-gray-300 rounded shadow-md; /* Using shadow-md as a softer default */
/* Tailwind classes from component: absolute z-20 w-full max-h-60 overflow-y-auto mt-1 hidden */
}
.mss-option-item {
@apply text-gray-700 py-1.5 px-2.5 text-sm cursor-pointer transition-colors duration-75 hover:bg-gray-100;
}
.mss-option-item-name {
@apply font-medium;
}
.mss-option-item-detail {
@apply text-gray-500 text-xs ml-1.5;
}
.mss-option-item-highlighted {
@apply bg-indigo-100 text-indigo-800;
}
.mss-option-item-highlighted .mss-option-item-name {
/* @apply font-medium; */ /* Already set by .mss-option-item-name, inherit color from parent */
}
.mss-option-item-highlighted .mss-option-item-detail {
@apply text-indigo-700;
}
.mss-hidden-select {
/* Styles are inline in _render for !important, no change needed here */
}
multi-select-simple[disabled] {
@apply opacity-60; /* Adjusted opacity */
}
multi-select-simple[disabled] .mss-selected-items-container {
@apply bg-gray-100;
}
multi-select-simple[disabled] .mss-selected-item-pill {
@apply bg-gray-300 text-gray-500;
}
multi-select-simple[disabled] .mss-selected-item-delete-btn {
@apply text-gray-400;
}
.rbi-button {
@apply disabled:hidden;
}
select + reset-button .rbi-button {
@apply ml-3;
}
}

View File

@@ -12,6 +12,7 @@ import { IntLink } from "./int-link.js";
import { ImageReel } from "./image-reel.js";
import { MultiSelectRole } from "./multi-select-role.js";
import { MultiSelectSimple } from "./multi-select-simple.js";
import { ResetButton } from "./reset-button.js";
const FILTER_LIST_ELEMENT = "filter-list";
const SCROLL_BUTTON_ELEMENT = "scroll-button";
@@ -24,6 +25,7 @@ const FILTER_PILL_ELEMENT = "filter-pill";
const IMAGE_REEL_ELEMENT = "image-reel";
const MULTI_SELECT_ROLE_ELEMENT = "multi-select-places";
const MULTI_SELECT_SIMPLE_ELEMENT = "multi-select-simple";
const RESET_BUTTON_ELEMENT = "reset-button";
customElements.define(INT_LINK_ELEMENT, IntLink);
customElements.define(ABBREV_TOOLTIPS_ELEMENT, AbbreviationTooltips);
@@ -36,5 +38,118 @@ customElements.define(FILTER_PILL_ELEMENT, FilterPill);
customElements.define(IMAGE_REEL_ELEMENT, ImageReel);
customElements.define(MULTI_SELECT_ROLE_ELEMENT, MultiSelectRole);
customElements.define(MULTI_SELECT_SIMPLE_ELEMENT, MultiSelectSimple);
customElements.define(RESET_BUTTON_ELEMENT, ResetButton);
export { FilterList, ScrollButton, AbbreviationTooltips };
function PathPlusQuery() {
const path = window.location.pathname;
const query = window.location.search;
const fullPath = path + query;
return encodeURIComponent(fullPath);
}
/**
* @param {number} timeout - Maximum time to wait in milliseconds.
* @param {number} interval - How often to check in milliseconds.
* @returns {Promise<Function>} Resolves with the QRCode constructor when available.
*/
function getQRCodeWhenAvailable(timeout = 5000, interval = 100) {
return new Promise((resolve, reject) => {
let elapsedTime = 0;
const checkInterval = setInterval(() => {
if (typeof window.QRCode === "function") {
clearInterval(checkInterval);
resolve(window.QRCode); // Resolve with the QRCode object/function
} else {
elapsedTime += interval;
if (elapsedTime >= timeout) {
clearInterval(checkInterval);
console.error("Timed out waiting for QRCode to become available.");
reject(new Error("QRCode not available after " + timeout + "ms. Check if qrcode.min.js is loaded correctly and sets window.QRCode."));
}
}
}, interval);
});
}
// INFO: We have to wait for the QRCode object to be available. It's messy.
async function GenQRCode(value) {
const QRCode = await getQRCodeWhenAvailable();
const qrElement = document.getElementById("qr");
if (qrElement) {
// INFO: Clear previous QR code if any
// Also hide it initially to prevent flickering
qrElement.innerHTML = "";
qrElement.classList.add("hidden");
new QRCode(qrElement, {
text: value,
width: 1280,
height: 1280,
colorDark: "#000000",
colorLight: "#ffffff",
correctLevel: QRCode.CorrectLevel.H,
});
setTimeout(() => {
qrElement.classList.remove("hidden");
}, 20);
}
// Add event listeners to the token input field to select its content on focus or click
}
function SelectableInput(tokenElement) {
if (tokenElement) {
tokenElement.addEventListener("focus", (ev) => {
ev.preventDefault();
tokenElement.select();
});
tokenElement.addEventListener("mousedown", (ev) => {
ev.preventDefault();
tokenElement.select();
});
tokenElement.addEventListener("mouseup", (ev) => {
ev.preventDefault();
tokenElement.select();
});
}
if (tokenElement) {
tokenElement.addEventListener("focus", () => {
tokenElement.select();
});
tokenElement.addEventListener("click", () => {
tokenElement.select();
});
}
}
// TODO: Doesn't work properly.
// Intended to make sure errors from boosted links are shown.
function ShowBoostedErrors() {
document.body.addEventListener("htmx:responseError", function (event) {
const config = event.detail.requestConfig;
if (config.boosted) {
document.body.innerHTML = event.detail.xhr.responseText;
const newUrl = event.detail.xhr.responseURL || config.url;
window.history.pushState(null, "", newUrl);
}
});
}
function FormHasChanged(form) {
if (!form || !(form instanceof HTMLFormElement)) {
return false;
}
const resetButton = form.querySelector("reset-button");
if (resetButton && resetButton.changed) {
return true;
}
return false;
}
window.ShowBoostedErrors = ShowBoostedErrors;
window.GenQRCode = GenQRCode;
window.SelectableInput = SelectableInput;
window.PathPlusQuery = PathPlusQuery;
window.FormHasChanged = FormHasChanged;
export { FilterList, ScrollButton, AbbreviationTooltips, MultiSelectSimple, MultiSelectRole, ToolTip, PopupImage, TabList, FilterPill, ImageReel, IntLink };

View File

@@ -0,0 +1,263 @@
const RBI_BUTTON_BASE_CLASS = "rbi-button";
const RBI_ICON_CLASS = "rbi-icon";
export class ResetButton extends HTMLElement {
constructor() {
super();
this.initialStates = new Map();
this._controlledElements = [];
this.button = null;
this.changed = false;
this.handleInputChange = this.handleInputChange.bind(this);
this.handleReset = this.handleReset.bind(this);
}
static get observedAttributes() {
return ["controls", "wrapper-class", "modified-class-suffix", "button-aria-label"];
}
connectedCallback() {
// Use an HTML template literal string to define the button's structure
const buttonHTML = `
<button type="button" class="${RBI_BUTTON_BASE_CLASS} cursor-pointer disabled:cursor-default" aria-label="Reset field">
<tool-tip position="right">
<div class="data-tip">Feld zurücksetzen</div>
<span class="${RBI_ICON_CLASS} ri-arrow-go-back-fill"></span>
</tool-tip>
</button>
`;
this.innerHTML = buttonHTML; // Set the inner HTML of the custom element
this.button = this.querySelector("button"); // Get the button element
if (this.button) {
this.button.addEventListener("click", this.handleReset);
} else {
// This case should ideally not be reached if the HTML string is correct
console.error("ResetButtonIndividual: Button element not found after setting innerHTML.");
}
this.updateControlledElements();
this.updateButtonAriaLabel();
}
disconnectedCallback() {
if (this.button) {
this.button.removeEventListener("click", this.handleReset);
}
this._controlledElements.forEach((el) => {
el.removeEventListener("input", this.handleInputChange);
el.removeEventListener("change", this.handleInputChange);
});
}
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue === newValue) return;
if (name === "controls") {
this.updateControlledElements();
}
if (name === "controls" || name === "button-aria-label") {
this.updateButtonAriaLabel();
}
}
updateControlledElements() {
this._controlledElements.forEach((el) => {
el.removeEventListener("input", this.handleInputChange);
el.removeEventListener("change", this.handleInputChange);
});
this._controlledElements = [];
this.initialStates.clear();
const controlIds = (this.getAttribute("controls") || "")
.split(",")
.map((id) => id.trim())
.filter((id) => id);
if (!controlIds.length && this.button) {
this.button.disabled = true;
this.button.setAttribute("aria-disabled", "true");
return;
}
const foundElements = [];
controlIds.forEach((id) => {
const element = document.getElementById(id);
if (element) {
foundElements.push(element);
this.storeInitialState(element);
element.addEventListener("input", this.handleInputChange);
element.addEventListener("change", this.handleInputChange);
} else {
console.warn(`ResetButtonIndividual: Element with ID "${id}" not found.`);
}
});
this._controlledElements = foundElements;
if (this.button) {
this.button.disabled = this._controlledElements.length === 0;
this.button.setAttribute("aria-controls", this._controlledElements.map((el) => el.id).join(" "));
if (this.button.disabled) {
this.button.setAttribute("aria-disabled", "true");
} else {
this.button.removeAttribute("aria-disabled");
}
}
this.checkIfModified();
}
storeInitialState(element) {
let state;
switch (element.type) {
case "checkbox":
case "radio":
state = { checked: element.checked };
break;
case "select-multiple":
state = {
selectedOptions: Array.from(element.options)
.filter((o) => o.selected)
.map((o) => o.value),
};
break;
case "select-one":
default:
state = { value: element.value };
break;
}
this.initialStates.set(element.id, state);
}
resetElement(element) {
const initialState = this.initialStates.get(element.id);
if (!initialState) return;
switch (element.type) {
case "checkbox":
case "radio":
element.checked = initialState.checked;
break;
case "select-multiple":
Array.from(element.options).forEach((option) => {
option.selected = initialState.selectedOptions.includes(option.value);
});
break;
case "select-one":
default:
element.value = initialState.value;
break;
}
element.dispatchEvent(new Event("input", { bubbles: true, cancelable: true }));
element.dispatchEvent(new Event("change", { bubbles: true, cancelable: true }));
}
handleReset() {
this._controlledElements.forEach((el) => {
this.resetElement(el);
});
this.checkIfModified();
}
handleInputChange(event) {
if (this._controlledElements.includes(event.target)) {
this.checkIfModified();
}
}
isElementModified(element) {
const initialState = this.initialStates.get(element.id);
if (!initialState) return false;
switch (element.type) {
case "checkbox":
case "radio":
return element.checked !== initialState.checked;
case "select-multiple":
const currentSelected = Array.from(element.options)
.filter((o) => o.selected)
.map((o) => o.value);
const initialSelected = initialState.selectedOptions;
return currentSelected.length !== initialSelected.length || currentSelected.some((val) => !initialSelected.includes(val)) || initialSelected.some((val) => !currentSelected.includes(val));
case "select-one":
default:
return element.value !== initialState.value;
}
}
checkIfModified() {
let overallModified = false;
this._controlledElements.forEach((el) => {
const modified = this.isElementModified(el);
if (modified) {
overallModified = true;
el.classList.add("modified-element");
} else {
el.classList.remove("modified-element");
}
});
const wrapperClass = this.getAttribute("wrapper-class");
if (wrapperClass) {
const wrapperElement = this.closest(`.${wrapperClass}`);
if (wrapperElement) {
const modifiedSuffix = this.getAttribute("modified-class-suffix") || "modified";
const modifiedWrapperClassName = `${wrapperClass}-${modifiedSuffix}`;
if (overallModified) {
wrapperElement.classList.add(modifiedWrapperClassName);
} else {
wrapperElement.classList.remove(modifiedWrapperClassName);
}
}
}
if (this.button) {
this.button.disabled = !overallModified || this._controlledElements.length === 0;
if (this.button.disabled) {
this.button.setAttribute("aria-disabled", "true");
} else {
this.button.removeAttribute("aria-disabled");
}
}
if (this.changed !== overallModified) {
const event = new CustomEvent("rbichange", {
bubbles: true,
composed: true,
detail: {
modified: overallModified,
controlledElementIds: this._controlledElements.map((el) => el.id),
instance: this,
},
});
this.dispatchEvent(event);
this.changed = overallModified;
}
}
updateButtonAriaLabel() {
if (!this.button) return;
let labelText = this.getAttribute("button-aria-label");
if (!labelText) {
const controlIds = this._controlledElements.map((el) => el.id);
if (controlIds.length === 1 && this._controlledElements[0]) {
const controlledEl = this._controlledElements[0];
const labelEl = document.querySelector(`label[for="${controlledEl.id}"]`);
let fieldName = controlledEl.name || controlledEl.id;
if (labelEl && labelEl.textContent) {
fieldName = labelEl.textContent.trim().replace(/[:*]$/, "").trim();
} else if (controlledEl.getAttribute("aria-label")) {
fieldName = controlledEl.getAttribute("aria-label");
}
labelText = `Reset ${fieldName}`;
} else if (controlIds.length > 1) {
labelText = "Reset selected fields";
} else {
labelText = "Reset field";
}
}
this.button.setAttribute("aria-label", labelText);
}
}

View File

@@ -1,4 +1,7 @@
@import "tailwindcss";
@import "./form.css";
@import "./usermgmt.css";
@theme {
--font-script: Rancho, ui-serif;
--font-sans: "Source Sans 3", "Merriweather Sans", ui-sans-serif;
@@ -516,55 +519,6 @@
@apply !text-slate-900 bg-stone-50;
}
.user-chooser a {
@apply px-4 py-2 no-underline text-gray-500 hover:text-slate-900 font-serif font-bold border-l-4 border-transparent hover:bg-slate-100 transition-all duration-75 rounded-xs;
}
.user-chooser a[aria-current="page"] {
@apply text-slate-900 font-bold bg-slate-100 border-slate-900 shadow-sm;
}
.user-chooser a[aria-current="page"]:nth-child(1) {
@apply border-blue-500;
}
.user-chooser a[aria-current="page"]:nth-child(2) {
@apply border-orange-600;
}
.user-chooser a[aria-current="page"]:nth-child(3) {
@apply border-red-600;
}
.user-mgmt thead th {
@apply text-left font-bold border-b-2 border-slate-800 py-2 px-3;
}
.user-mgmt tbody tr td {
@apply px-3 py-1.5;
}
.user-mgmt tbody tr:nth-child(odd) td {
@apply bg-slate-100;
}
.user-mgmt tbody tr:nth-child(even) td {
@apply bg-slate-50;
}
.user-mgmt tbody tr td:last-of-type {
@apply align-middle text-right pr-4;
}
.user-mgmt tbody tr.deactivated td:not(:last-of-type) {
@apply text-gray-400 line-through;
}
.user-mgmt form button,
.user-mgmt .edit-button {
@apply bg-slate-700 text-gray-200 text-base rounded-xs font-sans transition-all duration-75 px-3 py-1.5 hover:bg-slate-800 hover:text-white cursor-pointer;
}
@keyframes spin {
0% {
transform: rotate(0deg);
@@ -576,240 +530,4 @@
transform: rotate(360deg);
} /* Pause at the final position */
}
/* Multi-Select-Role example styles */
.msr-selected-items-container {
@apply rounded-md;
}
.msr-placeholder-no-selection-text {
@apply text-sm text-gray-500 italic px-2 py-1;
}
.msr-input-area-wrapper {
@apply p-2 rounded-md;
}
.msr-input-area-wrapper.msr-input-area-default-border {
@apply border border-gray-300;
}
.msr-input-area-wrapper.msr-input-area-default-border:focus-within {
@apply focus-within:border-gray-500 focus-within:ring-1 focus-within:ring-gray-400;
}
.msr-input-area-wrapper.msr-input-area-staged {
@apply border border-transparent;
}
.msr-text-input {
@apply bg-transparent text-sm placeholder-gray-400;
}
.msr-selected-item-pill {
@apply bg-gray-200 text-gray-700 px-3 py-[0.3rem] rounded-md text-sm inline-flex items-center m-0.5;
}
.msr-item-name {
@apply font-medium;
}
.msr-item-additional-data {
@apply text-xs ml-1 text-gray-600;
}
.msr-selected-item-role {
@apply font-semibold text-xs ml-1 text-gray-800;
}
.msr-selected-item-delete-btn {
@apply bg-transparent border-none text-gray-500 text-lg leading-none px-1 cursor-pointer opacity-60 transition-opacity duration-200;
}
.msr-selected-item-delete-btn:hover {
@apply hover:opacity-100 hover:text-gray-900;
}
.msr-staged-item-pill {
@apply bg-gray-100 text-gray-800 px-2 py-1 rounded-md text-sm font-medium;
}
.msr-staged-item-text {
@apply mr-2;
}
.msr-staged-role-select {
@apply px-2 py-1 text-sm rounded-md border border-gray-300 bg-white outline-none text-gray-700;
}
.msr-staged-role-select:focus {
@apply focus:border-gray-500 focus:ring-1 focus:ring-gray-400;
}
.msr-staged-cancel-btn {
@apply w-5 h-5 bg-gray-200 text-gray-600 rounded-full text-sm leading-none cursor-pointer;
}
.msr-staged-cancel-btn:hover {
@apply hover:bg-gray-300 hover:text-gray-800;
}
.msr-pre-add-button {
@apply w-10 h-[42px] text-xl rounded-md bg-gray-50 text-gray-700 border border-gray-300 font-semibold outline-none;
}
.msr-pre-add-button:focus {
@apply focus:border-gray-500 focus:ring-1 focus:ring-gray-400;
}
.msr-pre-add-button:hover {
@apply hover:bg-gray-100;
}
.msr-pre-add-button:disabled {
@apply disabled:bg-gray-200 disabled:text-gray-400 disabled:cursor-not-allowed disabled:border-gray-200;
}
.msr-pre-add-button.hidden {
@apply hidden;
}
.msr-add-button {
@apply px-4 py-2 text-sm rounded-md bg-gray-600 text-white font-medium;
}
.msr-add-button:hover {
@apply hover:bg-gray-700;
}
.msr-add-button:disabled {
@apply disabled:bg-gray-300 disabled:cursor-not-allowed;
}
.msr-add-button.hidden {
@apply hidden;
}
.msr-options-list {
@apply bg-white border border-gray-300 rounded-md shadow-md;
}
.msr-options-list.hidden {
@apply hidden;
}
.msr-option-item {
@apply px-3 py-2 text-sm cursor-pointer transition-colors duration-75;
}
.msr-option-item:hover {
@apply bg-gray-100 text-gray-800;
}
.msr-option-item-highlighted {
@apply bg-gray-100 text-gray-800;
}
.msr-option-item-name {
@apply font-medium;
}
.msr-option-item-detail {
@apply text-xs ml-2 text-gray-500;
}
.msr-option-item-highlighted .msr-option-item-detail,
.msr-option-item:hover .msr-option-item-detail {
/* Ensure detail text color changes on hover too */
@apply text-gray-600;
}
multi-select-role[disabled] {
/* This remains standard CSS as Tailwind's disabled: variant is for native elements */
opacity: 0.6;
cursor: not-allowed;
}
.msr-hidden-select {
/* No specific styling needed as it's visually hidden by JS/inline style */
}
/* --- MultiSelectSimple Component Base Styles (using @apply) --- */
.mss-component-wrapper {
/* 'relative' is set inline for positioning dropdown */
}
.mss-selected-items-container {
@apply border border-gray-300 p-1.5 rounded;
/* Tailwind classes from component: flex flex-wrap gap-1 mb-1 min-h-[38px] */
}
.mss-no-items-text {
@apply italic text-xs text-gray-500 p-1 w-full; /* Adjusted font size slightly to match 'xs' */
}
.mss-selected-item-pill {
@apply bg-gray-200 text-gray-800 py-0.5 px-2 rounded text-xs leading-5; /* Adjusted font size and padding */
/* Tailwind classes from component: flex items-center */
}
.mss-selected-item-text {
/* Base styles for text part of the pill */
}
.mss-selected-item-pill-detail {
@apply ml-1 opacity-75 text-xs text-gray-600;
}
.mss-selected-item-pill-detail.hidden {
@apply hidden;
}
.mss-selected-item-delete-btn {
@apply bg-transparent border-none text-gray-600 opacity-70 cursor-pointer ml-1 text-base leading-none align-middle hover:opacity-100 hover:text-gray-900 disabled:opacity-40 disabled:cursor-not-allowed;
}
.mss-input-controls-container {
/* Tailwind classes from component: flex items-center space-x-2 */
}
.mss-input-wrapper {
@apply border border-gray-300 rounded;
/* Tailwind classes from component: relative flex items-center flex-grow */
}
.mss-input-wrapper-focused {
@apply border-indigo-600 ring-1 ring-indigo-600; /* Using ring for focus shadow */
}
.mss-text-input {
@apply py-1.5 px-2 text-sm;
/* Tailwind classes from component: w-full outline-none bg-transparent */
}
.mss-text-input::placeholder {
@apply text-gray-400 italic;
}
.mss-create-new-button {
@apply bg-gray-100 text-gray-700 border border-gray-300 py-1 px-1.5 text-sm rounded hover:bg-gray-200 hover:border-gray-400 disabled:bg-gray-50 disabled:text-gray-400 disabled:border-gray-200 disabled:opacity-70 disabled:cursor-not-allowed;
}
.mss-create-new-button.hidden {
@apply !hidden; /* Ensure it hides */
}
.mss-options-list {
@apply bg-white border border-gray-300 rounded shadow-md; /* Using shadow-md as a softer default */
/* Tailwind classes from component: absolute z-20 w-full max-h-60 overflow-y-auto mt-1 hidden */
}
.mss-option-item {
@apply text-gray-700 py-1.5 px-2.5 text-sm cursor-pointer transition-colors duration-75 hover:bg-gray-100;
}
.mss-option-item-name {
@apply font-medium;
}
.mss-option-item-detail {
@apply text-gray-500 text-xs ml-1.5;
}
.mss-option-item-highlighted {
@apply bg-indigo-100 text-indigo-800;
}
.mss-option-item-highlighted .mss-option-item-name {
/* @apply font-medium; */ /* Already set by .mss-option-item-name, inherit color from parent */
}
.mss-option-item-highlighted .mss-option-item-detail {
@apply text-indigo-700;
}
.mss-hidden-select {
/* Styles are inline in _render for !important, no change needed here */
}
multi-select-simple[disabled] {
@apply opacity-60; /* Adjusted opacity */
}
multi-select-simple[disabled] .mss-selected-items-container {
@apply bg-gray-100;
}
multi-select-simple[disabled] .mss-selected-item-pill {
@apply bg-gray-300 text-gray-500;
}
multi-select-simple[disabled] .mss-selected-item-delete-btn {
@apply text-gray-400;
}
}

View File

@@ -7,6 +7,7 @@ export class TabList extends HTMLElement {
this.shown = -1;
this._headings = [];
this._contents = [];
this._checkbox = null;
}
connectedCallback() {
@@ -38,6 +39,19 @@ export class TabList extends HTMLElement {
});
}
hookupShowAll(checkbox) {
if (checkbox) {
this._checkbox = checkbox;
checkbox.addEventListener("change", (event) => {
if (event.target.checked) {
this.showAll();
} else {
this.default();
}
});
}
}
hookupEvtHandlers() {
for (let heading of this._headings) {
heading.addEventListener("click", this.handleTabClick.bind(this));

View File

@@ -0,0 +1,50 @@
@layer components {
.user-chooser a {
@apply px-4 py-2 no-underline text-gray-500 hover:text-slate-900 font-serif font-bold border-l-4 border-transparent hover:bg-slate-100 transition-all duration-75 rounded-xs;
}
.user-chooser a[aria-current="page"] {
@apply text-slate-900 font-bold bg-slate-100 border-slate-900 shadow-sm;
}
.user-chooser a[aria-current="page"]:nth-child(1) {
@apply border-blue-500;
}
.user-chooser a[aria-current="page"]:nth-child(2) {
@apply border-orange-600;
}
.user-chooser a[aria-current="page"]:nth-child(3) {
@apply border-red-600;
}
.user-mgmt thead th {
@apply text-left font-bold border-b-2 border-slate-800 py-2 px-3;
}
.user-mgmt tbody tr td {
@apply px-3 py-1.5;
}
.user-mgmt tbody tr:nth-child(odd) td {
@apply bg-slate-100;
}
.user-mgmt tbody tr:nth-child(even) td {
@apply bg-slate-50;
}
.user-mgmt tbody tr td:last-of-type {
@apply align-middle text-right pr-4;
}
.user-mgmt tbody tr.deactivated td:not(:last-of-type) {
@apply text-gray-400 line-through;
}
.user-mgmt form button,
.user-mgmt .edit-button {
@apply bg-slate-700 text-gray-200 text-base rounded-xs font-sans transition-all duration-75 px-3 py-1.5 hover:bg-slate-800 hover:text-white cursor-pointer;
}
}