added view collection type

This commit is contained in:
Gani Georgiev 2023-02-18 19:33:42 +02:00
parent 0052e2ab2a
commit a07f67002f
98 changed files with 3259 additions and 829 deletions

View File

@ -26,6 +26,10 @@
- Enabled `process.env` in js migrations to allow accessing `os.Environ()`. - Enabled `process.env` in js migrations to allow accessing `os.Environ()`.
- Enabled file thumbs when visualizing `relation` display file fields.
- Added new "View" collection type (@todo document)
## v0.12.3 ## v0.12.3

View File

@ -45,7 +45,7 @@ func TestCollectionsList(t *testing.T) {
ExpectedContent: []string{ ExpectedContent: []string{
`"page":1`, `"page":1`,
`"perPage":30`, `"perPage":30`,
`"totalItems":8`, `"totalItems":10`,
`"items":[{`, `"items":[{`,
`"id":"_pb_users_auth_"`, `"id":"_pb_users_auth_"`,
`"id":"v851q4r790rhknl"`, `"id":"v851q4r790rhknl"`,
@ -73,10 +73,10 @@ func TestCollectionsList(t *testing.T) {
ExpectedContent: []string{ ExpectedContent: []string{
`"page":2`, `"page":2`,
`"perPage":2`, `"perPage":2`,
`"totalItems":8`, `"totalItems":10`,
`"items":[{`, `"items":[{`,
`"id":"v851q4r790rhknl"`, `"id":"kpv709sk2lqbqk8"`,
`"id":"4d1blo5cuycfaca"`, `"id":"9n89pl5vkct6330"`,
}, },
ExpectedEvents: map[string]int{ ExpectedEvents: map[string]int{
"OnCollectionsListRequest": 1, "OnCollectionsListRequest": 1,
@ -231,7 +231,7 @@ func TestCollectionDelete(t *testing.T) {
{ {
Name: "authorized as admin + using the collection name", Name: "authorized as admin + using the collection name",
Method: http.MethodDelete, Method: http.MethodDelete,
Url: "/api/collections/demo1", Url: "/api/collections/demo5",
RequestHeaders: map[string]string{ RequestHeaders: map[string]string{
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8", "Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8",
}, },
@ -244,13 +244,13 @@ func TestCollectionDelete(t *testing.T) {
"OnCollectionAfterDeleteRequest": 1, "OnCollectionAfterDeleteRequest": 1,
}, },
AfterTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) { AfterTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
ensureDeletedFiles(app, "wsmn24bux7wo113") ensureDeletedFiles(app, "9n89pl5vkct6330")
}, },
}, },
{ {
Name: "authorized as admin + using the collection id", Name: "authorized as admin + using the collection id",
Method: http.MethodDelete, Method: http.MethodDelete,
Url: "/api/collections/wsmn24bux7wo113", Url: "/api/collections/9n89pl5vkct6330",
RequestHeaders: map[string]string{ RequestHeaders: map[string]string{
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8", "Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8",
}, },
@ -263,7 +263,7 @@ func TestCollectionDelete(t *testing.T) {
"OnCollectionAfterDeleteRequest": 1, "OnCollectionAfterDeleteRequest": 1,
}, },
AfterTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) { AfterTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
ensureDeletedFiles(app, "wsmn24bux7wo113") ensureDeletedFiles(app, "9n89pl5vkct6330")
}, },
}, },
{ {
@ -292,6 +292,22 @@ func TestCollectionDelete(t *testing.T) {
"OnCollectionBeforeDeleteRequest": 1, "OnCollectionBeforeDeleteRequest": 1,
}, },
}, },
{
Name: "authorized as admin + deleting a view",
Method: http.MethodDelete,
Url: "/api/collections/view2",
RequestHeaders: map[string]string{
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8",
},
Delay: 100 * time.Millisecond,
ExpectedStatus: 204,
ExpectedEvents: map[string]int{
"OnModelBeforeDelete": 1,
"OnModelAfterDelete": 1,
"OnCollectionBeforeDeleteRequest": 1,
"OnCollectionAfterDeleteRequest": 1,
},
},
} }
for _, scenario := range scenarios { for _, scenario := range scenarios {
@ -520,6 +536,56 @@ func TestCollectionCreate(t *testing.T) {
`"options":{"minPasswordLength":{"code":"validation_required"`, `"options":{"minPasswordLength":{"code":"validation_required"`,
}, },
}, },
// view
// -----------------------------------------------------------
{
Name: "trying to create view collection with invalid options",
Method: http.MethodPost,
Url: "/api/collections",
Body: strings.NewReader(`{
"name":"new",
"type":"view",
"schema":[{"type":"text","id":"12345789","name":"ignored!@#$"}],
"options":{"query": "invalid"}
}`),
RequestHeaders: map[string]string{
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8",
},
ExpectedStatus: 400,
ExpectedContent: []string{
`"data":{`,
`"options":{"query":{"code":"validation_invalid_view_query`,
},
},
{
Name: "creating view collection",
Method: http.MethodPost,
Url: "/api/collections",
Body: strings.NewReader(`{
"name":"new",
"type":"view",
"schema":[{"type":"text","id":"12345789","name":"ignored!@#$"}],
"options": {
"query": "select 1 as id from _admins"
}
}`),
RequestHeaders: map[string]string{
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8",
},
ExpectedStatus: 200,
ExpectedContent: []string{
`"name":"new"`,
`"type":"view"`,
`"schema":[]`,
},
ExpectedEvents: map[string]int{
"OnModelBeforeCreate": 1,
"OnModelAfterCreate": 1,
"OnCollectionBeforeCreateRequest": 1,
"OnCollectionAfterCreateRequest": 1,
},
},
} }
for _, scenario := range scenarios { for _, scenario := range scenarios {
@ -660,7 +726,7 @@ func TestCollectionUpdate(t *testing.T) {
{ {
Name: "updating base collection with reserved auth fields", Name: "updating base collection with reserved auth fields",
Method: http.MethodPatch, Method: http.MethodPatch,
Url: "/api/collections/demo1", Url: "/api/collections/demo4",
Body: strings.NewReader(`{ Body: strings.NewReader(`{
"schema":[ "schema":[
{"type":"text","name":"email"}, {"type":"text","name":"email"},
@ -681,7 +747,7 @@ func TestCollectionUpdate(t *testing.T) {
}, },
ExpectedStatus: 200, ExpectedStatus: 200,
ExpectedContent: []string{ ExpectedContent: []string{
`"name":"demo1"`, `"name":"demo4"`,
`"type":"base"`, `"type":"base"`,
`"schema":[{`, `"schema":[{`,
`"email"`, `"email"`,
@ -751,6 +817,7 @@ func TestCollectionUpdate(t *testing.T) {
}, },
// rel field change displayFields propagation // rel field change displayFields propagation
// -----------------------------------------------------------
{ {
Name: "renaming a display field should also update the referenced displayFields value", Name: "renaming a display field should also update the referenced displayFields value",
Method: http.MethodPatch, Method: http.MethodPatch,
@ -830,6 +897,60 @@ func TestCollectionUpdate(t *testing.T) {
} }
}, },
}, },
// view
// -----------------------------------------------------------
{
Name: "trying to update view collection with invalid options",
Method: http.MethodPatch,
Url: "/api/collections/view1",
Body: strings.NewReader(`{
"schema":[{"type":"text","id":"12345789","name":"ignored!@#$"}],
"options":{"query": "invalid"}
}`),
RequestHeaders: map[string]string{
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8",
},
ExpectedStatus: 400,
ExpectedContent: []string{
`"data":{`,
`"options":{"query":{"code":"validation_invalid_view_query`,
},
},
{
Name: "updating view collection",
Method: http.MethodPatch,
Url: "/api/collections/view2",
Body: strings.NewReader(`{
"name":"view2_update",
"schema":[{"type":"text","id":"12345789","name":"ignored!@#$"}],
"options": {
"query": "select 2 as id, created, updated, email from _admins"
}
}`),
RequestHeaders: map[string]string{
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8",
},
ExpectedStatus: 200,
ExpectedContent: []string{
`"name":"view2_update"`,
`"type":"view"`,
`"schema":[{`,
`"name":"email"`,
},
NotExpectedContent: []string{
// base model fields are not part of the schema
`"name":"id"`,
`"name":"created"`,
`"name":"updated"`,
},
ExpectedEvents: map[string]int{
"OnModelBeforeUpdate": 1,
"OnModelAfterUpdate": 1,
"OnCollectionBeforeUpdateRequest": 1,
"OnCollectionAfterUpdateRequest": 1,
},
},
} }
for _, scenario := range scenarios { for _, scenario := range scenarios {
@ -838,6 +959,8 @@ func TestCollectionUpdate(t *testing.T) {
} }
func TestCollectionImport(t *testing.T) { func TestCollectionImport(t *testing.T) {
totalCollections := 10
scenarios := []tests.ApiScenario{ scenarios := []tests.ApiScenario{
{ {
Name: "unauthorized", Name: "unauthorized",
@ -874,7 +997,7 @@ func TestCollectionImport(t *testing.T) {
if err := app.Dao().CollectionQuery().All(&collections); err != nil { if err := app.Dao().CollectionQuery().All(&collections); err != nil {
t.Fatal(err) t.Fatal(err)
} }
expected := 8 expected := totalCollections
if len(collections) != expected { if len(collections) != expected {
t.Fatalf("Expected %d collections, got %d", expected, len(collections)) t.Fatalf("Expected %d collections, got %d", expected, len(collections))
} }
@ -902,7 +1025,7 @@ func TestCollectionImport(t *testing.T) {
if err := app.Dao().CollectionQuery().All(&collections); err != nil { if err := app.Dao().CollectionQuery().All(&collections); err != nil {
t.Fatal(err) t.Fatal(err)
} }
expected := 8 expected := totalCollections
if len(collections) != expected { if len(collections) != expected {
t.Fatalf("Expected %d collections, got %d", expected, len(collections)) t.Fatalf("Expected %d collections, got %d", expected, len(collections))
} }
@ -944,7 +1067,7 @@ func TestCollectionImport(t *testing.T) {
if err := app.Dao().CollectionQuery().All(&collections); err != nil { if err := app.Dao().CollectionQuery().All(&collections); err != nil {
t.Fatal(err) t.Fatal(err)
} }
expected := 8 expected := totalCollections
if len(collections) != expected { if len(collections) != expected {
t.Fatalf("Expected %d collections, got %d", expected, len(collections)) t.Fatalf("Expected %d collections, got %d", expected, len(collections))
} }
@ -997,7 +1120,7 @@ func TestCollectionImport(t *testing.T) {
if err := app.Dao().CollectionQuery().All(&collections); err != nil { if err := app.Dao().CollectionQuery().All(&collections); err != nil {
t.Fatal(err) t.Fatal(err)
} }
expected := 11 expected := totalCollections + 3
if len(collections) != expected { if len(collections) != expected {
t.Fatalf("Expected %d collections, got %d", expected, len(collections)) t.Fatalf("Expected %d collections, got %d", expected, len(collections))
} }
@ -1084,8 +1207,8 @@ func TestCollectionImport(t *testing.T) {
ExpectedEvents: map[string]int{ ExpectedEvents: map[string]int{
"OnCollectionsAfterImportRequest": 1, "OnCollectionsAfterImportRequest": 1,
"OnCollectionsBeforeImportRequest": 1, "OnCollectionsBeforeImportRequest": 1,
"OnModelBeforeDelete": 6, "OnModelBeforeDelete": 8,
"OnModelAfterDelete": 6, "OnModelAfterDelete": 8,
"OnModelBeforeUpdate": 2, "OnModelBeforeUpdate": 2,
"OnModelAfterUpdate": 2, "OnModelAfterUpdate": 2,
"OnModelBeforeCreate": 1, "OnModelBeforeCreate": 1,

View File

@ -1,6 +1,8 @@
package apis package apis
import ( import (
"fmt"
"github.com/labstack/echo/v5" "github.com/labstack/echo/v5"
"github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/models" "github.com/pocketbase/pocketbase/models"
@ -45,15 +47,27 @@ func (api *fileApi) download(c echo.Context) error {
if fileField == nil { if fileField == nil {
return NewNotFoundError("", nil) return NewNotFoundError("", nil)
} }
options, _ := fileField.Options.(*schema.FileOptions) options, _ := fileField.Options.(*schema.FileOptions)
baseFilesPath := record.BaseFilesPath()
// fetch the original view file field related record
if collection.IsView() {
fileRecord, err := api.app.Dao().FindRecordByViewFile(collection.Id, fileField.Name, filename)
if err != nil {
return NewNotFoundError("", fmt.Errorf("Failed to fetch view file field record: %w", err))
}
baseFilesPath = fileRecord.BaseFilesPath()
}
fs, err := api.app.NewFilesystem() fs, err := api.app.NewFilesystem()
if err != nil { if err != nil {
return NewBadRequestError("Filesystem initialization failure.", err) return NewBadRequestError("Filesystem initialization failure.", err)
} }
defer fs.Close() defer fs.Close()
originalPath := record.BaseFilesPath() + "/" + filename originalPath := baseFilesPath + "/" + filename
servedPath := originalPath servedPath := originalPath
servedName := filename servedName := filename
@ -70,7 +84,7 @@ func (api *fileApi) download(c echo.Context) error {
if list.ExistInSlice(oAttrs.ContentType, imageContentTypes) { if list.ExistInSlice(oAttrs.ContentType, imageContentTypes) {
// add thumb size as file suffix // add thumb size as file suffix
servedName = thumbSize + "_" + filename servedName = thumbSize + "_" + filename
servedPath = record.BaseFilesPath() + "/thumbs_" + filename + "/" + servedName servedPath = baseFilesPath + "/thumbs_" + filename + "/" + servedName
// check if the thumb exists: // check if the thumb exists:
// - if doesn't exist - create a new thumb with the specified thumb size // - if doesn't exist - create a new thumb with the specified thumb size

View File

@ -59,9 +59,12 @@ func RequireGuestOnly() echo.MiddlewareFunc {
// specifying their names. // specifying their names.
// //
// Example: // Example:
// apis.RequireRecordAuth() //
// apis.RequireRecordAuth()
//
// Or: // Or:
// apis.RequireRecordAuth("users", "supervisors") //
// apis.RequireRecordAuth("users", "supervisors")
// //
// To restrict the auth record only to the loaded context collection, // To restrict the auth record only to the loaded context collection,
// use [apis.RequireSameContextRecordAuth()] instead. // use [apis.RequireSameContextRecordAuth()] instead.
@ -83,7 +86,6 @@ func RequireRecordAuth(optCollectionNames ...string) echo.MiddlewareFunc {
} }
} }
//
// RequireSameContextRecordAuth middleware requires a request to have // RequireSameContextRecordAuth middleware requires a request to have
// a valid record Authorization header. // a valid record Authorization header.
// //
@ -261,7 +263,7 @@ func LoadCollectionContext(app core.App, optCollectionTypes ...string) echo.Midd
} }
if len(optCollectionTypes) > 0 && !list.ExistInSlice(collection.Type, optCollectionTypes) { if len(optCollectionTypes) > 0 && !list.ExistInSlice(collection.Type, optCollectionTypes) {
return NewBadRequestError("Invalid collection type.", nil) return NewBadRequestError("Unsupported collection type.", nil)
} }
c.Set(ContextCollectionKey, collection) c.Set(ContextCollectionKey, collection)

View File

@ -26,14 +26,13 @@ func bindRecordCrudApi(app core.App, rg *echo.Group) {
subGroup := rg.Group( subGroup := rg.Group(
"/collections/:collection", "/collections/:collection",
ActivityLogger(app), ActivityLogger(app),
LoadCollectionContext(app),
) )
subGroup.GET("/records", api.list) subGroup.GET("/records", api.list, LoadCollectionContext(app))
subGroup.POST("/records", api.create) subGroup.GET("/records/:id", api.view, LoadCollectionContext(app))
subGroup.GET("/records/:id", api.view) subGroup.POST("/records", api.create, LoadCollectionContext(app, models.CollectionTypeBase, models.CollectionTypeAuth))
subGroup.PATCH("/records/:id", api.update) subGroup.PATCH("/records/:id", api.update, LoadCollectionContext(app, models.CollectionTypeBase, models.CollectionTypeAuth))
subGroup.DELETE("/records/:id", api.delete) subGroup.DELETE("/records/:id", api.delete, LoadCollectionContext(app, models.CollectionTypeBase, models.CollectionTypeAuth))
} }
type recordApi struct { type recordApi struct {

View File

@ -256,7 +256,7 @@ func TestRecordCrudList(t *testing.T) {
ExpectedEvents: map[string]int{"OnRecordsListRequest": 1}, ExpectedEvents: map[string]int{"OnRecordsListRequest": 1},
}, },
// auth collection checks // auth collection
// ----------------------------------------------------------- // -----------------------------------------------------------
{ {
Name: "check email visibility as guest", Name: "check email visibility as guest",
@ -403,6 +403,63 @@ func TestRecordCrudList(t *testing.T) {
}, },
ExpectedEvents: map[string]int{"OnRecordsListRequest": 1}, ExpectedEvents: map[string]int{"OnRecordsListRequest": 1},
}, },
// view collection
// -----------------------------------------------------------
{
Name: "public view records",
Method: http.MethodGet,
Url: "/api/collections/view2/records?filter=state=false",
ExpectedStatus: 200,
ExpectedContent: []string{
`"page":1`,
`"perPage":30`,
`"totalPages":1`,
`"totalItems":2`,
`"items":[{`,
`"id":"al1h9ijdeojtsjy"`,
`"id":"imy661ixudk5izi"`,
},
NotExpectedContent: []string{
`"created"`,
`"updated"`,
},
ExpectedEvents: map[string]int{"OnRecordsListRequest": 1},
},
{
Name: "guest that doesn't match the view collection list rule",
Method: http.MethodGet,
Url: "/api/collections/view1/records",
ExpectedStatus: 200,
ExpectedContent: []string{
`"page":1`,
`"perPage":30`,
`"totalPages":0`,
`"totalItems":0`,
`"items":[]`,
},
ExpectedEvents: map[string]int{"OnRecordsListRequest": 1},
},
{
Name: "authenticated record that matches the view collection list rule",
Method: http.MethodGet,
Url: "/api/collections/view1/records",
RequestHeaders: map[string]string{
// users, test@example.com
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyMjA4OTg1MjYxfQ.UwD8JvkbQtXpymT09d7J6fdA0aP9g4FJ1GPh_ggEkzc",
},
ExpectedStatus: 200,
ExpectedContent: []string{
`"page":1`,
`"perPage":30`,
`"totalPages":1`,
`"totalItems":1`,
`"items":[{`,
`"id":"84nmscqy84lsi1t"`,
`"bool":true`,
},
ExpectedEvents: map[string]int{"OnRecordsListRequest": 1},
},
} }
for _, scenario := range scenarios { for _, scenario := range scenarios {
@ -531,7 +588,7 @@ func TestRecordCrudView(t *testing.T) {
ExpectedEvents: map[string]int{"OnRecordViewRequest": 1}, ExpectedEvents: map[string]int{"OnRecordViewRequest": 1},
}, },
// auth collection checks // auth collection
// ----------------------------------------------------------- // -----------------------------------------------------------
{ {
Name: "check email visibility as guest", Name: "check email visibility as guest",
@ -629,6 +686,49 @@ func TestRecordCrudView(t *testing.T) {
}, },
ExpectedEvents: map[string]int{"OnRecordViewRequest": 1}, ExpectedEvents: map[string]int{"OnRecordViewRequest": 1},
}, },
// view collection
// -----------------------------------------------------------
{
Name: "public view record",
Method: http.MethodGet,
Url: "/api/collections/view2/records/84nmscqy84lsi1t",
ExpectedStatus: 200,
ExpectedContent: []string{
`"id":"84nmscqy84lsi1t"`,
`"state":true`,
`"file_many":["`,
`"rel_many":["`,
},
NotExpectedContent: []string{
`"created"`,
`"updated"`,
},
ExpectedEvents: map[string]int{"OnRecordViewRequest": 1},
},
{
Name: "guest that doesn't match the view collection view rule",
Method: http.MethodGet,
Url: "/api/collections/view1/records/84nmscqy84lsi1t",
ExpectedStatus: 404,
ExpectedContent: []string{`"data":{}`},
},
{
Name: "authenticated record that matches the view collection view rule",
Method: http.MethodGet,
Url: "/api/collections/view1/records/84nmscqy84lsi1t",
RequestHeaders: map[string]string{
// users, test@example.com
"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyMjA4OTg1MjYxfQ.UwD8JvkbQtXpymT09d7J6fdA0aP9g4FJ1GPh_ggEkzc",
},
ExpectedStatus: 200,
ExpectedContent: []string{
`"id":"84nmscqy84lsi1t"`,
`"bool":true`,
`"text":"`,
},
ExpectedEvents: map[string]int{"OnRecordViewRequest": 1},
},
} }
for _, scenario := range scenarios { for _, scenario := range scenarios {
@ -690,6 +790,13 @@ func TestRecordCrudDelete(t *testing.T) {
ExpectedStatus: 404, ExpectedStatus: 404,
ExpectedContent: []string{`"data":{}`}, ExpectedContent: []string{`"data":{}`},
}, },
{
Name: "trying to delete a view collection record",
Method: http.MethodDelete,
Url: "/api/collections/view1/records/imy661ixudk5izi",
ExpectedStatus: 400,
ExpectedContent: []string{`"data":{}`},
},
{ {
Name: "public collection record delete", Name: "public collection record delete",
Method: http.MethodDelete, Method: http.MethodDelete,
@ -885,6 +992,14 @@ func TestRecordCrudCreate(t *testing.T) {
ExpectedStatus: 403, ExpectedStatus: 403,
ExpectedContent: []string{`"data":{}`}, ExpectedContent: []string{`"data":{}`},
}, },
{
Name: "trying to create a new view collection record",
Method: http.MethodPost,
Url: "/api/collections/view1/records",
Body: strings.NewReader(`{"text":"new"}`),
ExpectedStatus: 400,
ExpectedContent: []string{`"data":{}`},
},
{ {
Name: "submit nil body", Name: "submit nil body",
Method: http.MethodPost, Method: http.MethodPost,
@ -1428,6 +1543,14 @@ func TestRecordCrudUpdate(t *testing.T) {
ExpectedStatus: 400, ExpectedStatus: 400,
ExpectedContent: []string{`"data":{}`}, ExpectedContent: []string{`"data":{}`},
}, },
{
Name: "trying to update a view collection record",
Method: http.MethodPatch,
Url: "/api/collections/view1/records/imy661ixudk5izi",
Body: strings.NewReader(`{"text":"new"}`),
ExpectedStatus: 400,
ExpectedContent: []string{`"data":{}`},
},
{ {
Name: "submit nil body", Name: "submit nil body",
Method: http.MethodPatch, Method: http.MethodPatch,

View File

@ -472,7 +472,7 @@ func (app *BaseApp) NewMailClient() mailer.Mailer {
// NB! Make sure to call `Close()` on the returned result // NB! Make sure to call `Close()` on the returned result
// after you are done working with it. // after you are done working with it.
func (app *BaseApp) NewFilesystem() (*filesystem.System, error) { func (app *BaseApp) NewFilesystem() (*filesystem.System, error) {
if app.settings.S3.Enabled { if app.settings != nil && app.settings.S3.Enabled {
return filesystem.NewS3( return filesystem.NewS3(
app.settings.S3.Bucket, app.settings.S3.Bucket,
app.settings.S3.Region, app.settings.S3.Region,

View File

@ -129,13 +129,23 @@ func (dao *Dao) DeleteCollection(collection *models.Collection) error {
return err return err
} }
if total := len(result); total > 0 { if total := len(result); total > 0 {
return fmt.Errorf("The collection %q has external relation field references (%d).", collection.Name, total) names := make([]string, 0, len(result))
for ref := range result {
names = append(names, ref.Name)
}
return fmt.Errorf("The collection %q has external relation field references (%s).", collection.Name, strings.Join(names, ", "))
} }
return dao.RunInTransaction(func(txDao *Dao) error { return dao.RunInTransaction(func(txDao *Dao) error {
// delete the related records table // delete the related view or records table
if err := txDao.DeleteTable(collection.Name); err != nil { if collection.IsView() {
return err if err := txDao.DeleteView(collection.Name); err != nil {
return err
}
} else {
if err := txDao.DeleteTable(collection.Name); err != nil {
return err
}
} }
return txDao.Delete(collection) return txDao.Delete(collection)
@ -163,13 +173,18 @@ func (dao *Dao) SaveCollection(collection *models.Collection) error {
collection.Type = models.CollectionTypeBase collection.Type = models.CollectionTypeBase
} }
// persist the collection model switch collection.Type {
if err := txDao.Save(collection); err != nil { case models.CollectionTypeView:
return err return txDao.saveViewCollection(collection, oldCollection)
} default:
// persist the collection model
if err := txDao.Save(collection); err != nil {
return err
}
// sync the changes with the related records table // sync the changes with the related records table
return txDao.SyncRecordTableSchema(collection, oldCollection) return txDao.SyncRecordTableSchema(collection, oldCollection)
}
}) })
} }
@ -274,8 +289,14 @@ func (dao *Dao) ImportCollections(
continue // exist continue // exist
} }
if err := txDao.DeleteTable(existing.Name); err != nil { if existing.IsView() {
return err if err := txDao.DeleteView(existing.Name); err != nil {
return err
}
} else {
if err := txDao.DeleteTable(existing.Name); err != nil {
return err
}
} }
} }
} }
@ -283,11 +304,58 @@ func (dao *Dao) ImportCollections(
// sync the upserted collections with the related records table // sync the upserted collections with the related records table
for _, imported := range importedCollections { for _, imported := range importedCollections {
existing := mappedExisting[imported.GetId()] existing := mappedExisting[imported.GetId()]
if err := txDao.SyncRecordTableSchema(imported, existing); err != nil {
return err if imported.IsView() {
if err := txDao.saveViewCollection(imported, existing); err != nil {
return err
}
} else {
if err := txDao.SyncRecordTableSchema(imported, existing); err != nil {
return err
}
} }
} }
return nil return nil
}) })
} }
// saveViewCollection persists the provided View collection changes:
// - deletes the old related SQL view (if any)
// - creates a new SQL view with the latest newCollection.Options.Query
// - generates a new schema based on newCollection.Options.Query
// - updates newCollection.Schema based on the generated view table info and query
// - saves the newCollection
//
// This method returns an error if newCollection is not a "view".
func (dao *Dao) saveViewCollection(newCollection *models.Collection, oldCollection *models.Collection) error {
if newCollection.IsAuth() {
return errors.New("not a view collection")
}
return dao.RunInTransaction(func(txDao *Dao) error {
query := newCollection.ViewOptions().Query
// generate collection schema from the query
schema, err := txDao.CreateViewSchema(query)
if err != nil {
return err
}
// delete old renamed view
if oldCollection != nil && newCollection.Name != oldCollection.Name {
if err := txDao.DeleteView(oldCollection.Name); err != nil {
return err
}
}
// (re)create the view
if err := txDao.SaveView(newCollection.Name, query); err != nil {
return err
}
newCollection.Schema = schema
return txDao.Save(newCollection)
})
}

View File

@ -45,16 +45,16 @@ func TestFindCollectionsByType(t *testing.T) {
hasErr := err != nil hasErr := err != nil
if hasErr != scenario.expectError { if hasErr != scenario.expectError {
t.Errorf("(%d) Expected hasErr to be %v, got %v (%v)", i, scenario.expectError, hasErr, err) t.Errorf("[%d] Expected hasErr to be %v, got %v (%v)", i, scenario.expectError, hasErr, err)
} }
if len(collections) != scenario.expectTotal { if len(collections) != scenario.expectTotal {
t.Errorf("(%d) Expected %d collections, got %d", i, scenario.expectTotal, len(collections)) t.Errorf("[%d] Expected %d collections, got %d", i, scenario.expectTotal, len(collections))
} }
for _, c := range collections { for _, c := range collections {
if c.Type != scenario.collectionType { if c.Type != scenario.collectionType {
t.Errorf("(%d) Expected collection with type %s, got %s: \n%v", i, scenario.collectionType, c.Type, c) t.Errorf("[%d] Expected collection with type %s, got %s: \n%v", i, scenario.collectionType, c.Type, c)
} }
} }
} }
@ -80,11 +80,11 @@ func TestFindCollectionByNameOrId(t *testing.T) {
hasErr := err != nil hasErr := err != nil
if hasErr != scenario.expectError { if hasErr != scenario.expectError {
t.Errorf("(%d) Expected hasErr to be %v, got %v (%v)", i, scenario.expectError, hasErr, err) t.Errorf("[%d] Expected hasErr to be %v, got %v (%v)", i, scenario.expectError, hasErr, err)
} }
if model != nil && model.Id != scenario.nameOrId && !strings.EqualFold(model.Name, scenario.nameOrId) { if model != nil && model.Id != scenario.nameOrId && !strings.EqualFold(model.Name, scenario.nameOrId) {
t.Errorf("(%d) Expected model with identifier %s, got %v", i, scenario.nameOrId, model) t.Errorf("[%d] Expected model with identifier %s, got %v", i, scenario.nameOrId, model)
} }
} }
} }
@ -108,7 +108,7 @@ func TestIsCollectionNameUnique(t *testing.T) {
for i, scenario := range scenarios { for i, scenario := range scenarios {
result := app.Dao().IsCollectionNameUnique(scenario.name, scenario.excludeId) result := app.Dao().IsCollectionNameUnique(scenario.name, scenario.excludeId)
if result != scenario.expected { if result != scenario.expected {
t.Errorf("(%d) Expected %v, got %v", i, scenario.expected, result) t.Errorf("[%d] Expected %v, got %v", i, scenario.expected, result)
} }
} }
} }
@ -155,7 +155,7 @@ func TestFindCollectionReferences(t *testing.T) {
} }
for i, f := range fields { for i, f := range fields {
if !list.ExistInSlice(f.Name, expectedFields) { if !list.ExistInSlice(f.Name, expectedFields) {
t.Fatalf("(%d) Didn't expect field %v", i, f) t.Fatalf("[%d] Didn't expect field %v", i, f)
} }
} }
} }
@ -165,21 +165,29 @@ func TestDeleteCollection(t *testing.T) {
app, _ := tests.NewTestApp() app, _ := tests.NewTestApp()
defer app.Cleanup() defer app.Cleanup()
c0 := &models.Collection{} colEmpty := &models.Collection{}
c1, err := app.Dao().FindCollectionByNameOrId("clients")
colAuth, err := app.Dao().FindCollectionByNameOrId("clients")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
c2, err := app.Dao().FindCollectionByNameOrId("demo2")
colReferenced, err := app.Dao().FindCollectionByNameOrId("demo2")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
c3, err := app.Dao().FindCollectionByNameOrId("demo1")
colSystem, err := app.Dao().FindCollectionByNameOrId("demo3")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
c3.System = true colSystem.System = true
if err := app.Dao().Save(c3); err != nil { if err := app.Dao().Save(colSystem); err != nil {
t.Fatal(err)
}
colView, err := app.Dao().FindCollectionByNameOrId("view1")
if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -187,18 +195,28 @@ func TestDeleteCollection(t *testing.T) {
model *models.Collection model *models.Collection
expectError bool expectError bool
}{ }{
{c0, true}, {colEmpty, true},
{c1, false}, {colAuth, false},
{c2, true}, // is part of a reference {colReferenced, true},
{c3, true}, // system {colSystem, true},
{colView, false},
} }
for i, scenario := range scenarios { for i, s := range scenarios {
err := app.Dao().DeleteCollection(scenario.model) err := app.Dao().DeleteCollection(s.model)
hasErr := err != nil
if hasErr != scenario.expectError { hasErr := err != nil
t.Errorf("(%d) Expected hasErr %v, got %v", i, scenario.expectError, hasErr) if hasErr != s.expectError {
t.Errorf("[%d] Expected hasErr %v, got %v (%v)", i, s.expectError, hasErr, err)
continue
}
if hasErr {
continue
}
if app.Dao().HasTable(s.model.Name) {
t.Errorf("[%d] Expected table/view %s to be deleted", i, s.model.Name)
} }
} }
} }
@ -244,7 +262,7 @@ func TestSaveCollectionCreate(t *testing.T) {
} }
for i, c := range columns { for i, c := range columns {
if !list.ExistInSlice(c, expectedColumns) { if !list.ExistInSlice(c, expectedColumns) {
t.Fatalf("(%d) Didn't expect record column %s", i, c) t.Fatalf("[%d] Didn't expect record column %s", i, c)
} }
} }
} }
@ -282,12 +300,14 @@ func TestSaveCollectionUpdate(t *testing.T) {
} }
for i, c := range columns { for i, c := range columns {
if !list.ExistInSlice(c, expectedColumns) { if !list.ExistInSlice(c, expectedColumns) {
t.Fatalf("(%d) Didn't expect record column %s", i, c) t.Fatalf("[%d] Didn't expect record column %s", i, c)
} }
} }
} }
func TestImportCollections(t *testing.T) { func TestImportCollections(t *testing.T) {
totalCollections := 10
scenarios := []struct { scenarios := []struct {
name string name string
jsonData string jsonData string
@ -302,7 +322,7 @@ func TestImportCollections(t *testing.T) {
name: "empty collections", name: "empty collections",
jsonData: `[]`, jsonData: `[]`,
expectError: true, expectError: true,
expectCollectionsCount: 8, expectCollectionsCount: totalCollections,
}, },
{ {
name: "minimal collection import", name: "minimal collection import",
@ -312,7 +332,7 @@ func TestImportCollections(t *testing.T) {
]`, ]`,
deleteMissing: false, deleteMissing: false,
expectError: false, expectError: false,
expectCollectionsCount: 10, expectCollectionsCount: totalCollections + 2,
}, },
{ {
name: "minimal collection import + failed beforeRecordsSync", name: "minimal collection import + failed beforeRecordsSync",
@ -324,7 +344,7 @@ func TestImportCollections(t *testing.T) {
}, },
deleteMissing: false, deleteMissing: false,
expectError: true, expectError: true,
expectCollectionsCount: 8, expectCollectionsCount: totalCollections,
}, },
{ {
name: "minimal collection import + successful beforeRecordsSync", name: "minimal collection import + successful beforeRecordsSync",
@ -336,7 +356,7 @@ func TestImportCollections(t *testing.T) {
}, },
deleteMissing: false, deleteMissing: false,
expectError: false, expectError: false,
expectCollectionsCount: 9, expectCollectionsCount: totalCollections + 1,
}, },
{ {
name: "new + update + delete system collection", name: "new + update + delete system collection",
@ -372,7 +392,7 @@ func TestImportCollections(t *testing.T) {
]`, ]`,
deleteMissing: true, deleteMissing: true,
expectError: true, expectError: true,
expectCollectionsCount: 8, expectCollectionsCount: totalCollections,
}, },
{ {
name: "new + update + delete non-system collection", name: "new + update + delete non-system collection",
@ -442,11 +462,19 @@ func TestImportCollections(t *testing.T) {
"type":"bool" "type":"bool"
} }
] ]
},
{
"id": "test_new_view",
"name": "new_view",
"type": "view",
"options": {
"query": "select id from demo2"
}
} }
]`, ]`,
deleteMissing: true, deleteMissing: true,
expectError: false, expectError: false,
expectCollectionsCount: 3, expectCollectionsCount: 4,
}, },
{ {
name: "test with deleteMissing: false", name: "test with deleteMissing: false",
@ -501,7 +529,7 @@ func TestImportCollections(t *testing.T) {
]`, ]`,
deleteMissing: false, deleteMissing: false,
expectError: false, expectError: false,
expectCollectionsCount: 9, expectCollectionsCount: totalCollections + 1,
afterTestFunc: func(testApp *tests.TestApp, resultCollections []*models.Collection) { afterTestFunc: func(testApp *tests.TestApp, resultCollections []*models.Collection) {
expectedCollectionFields := map[string]int{ expectedCollectionFields := map[string]int{
"nologin": 1, "nologin": 1,
@ -509,7 +537,7 @@ func TestImportCollections(t *testing.T) {
"demo2": 2, "demo2": 2,
"demo3": 2, "demo3": 2,
"demo4": 11, "demo4": 11,
"demo5": 5, "demo5": 6,
"new_import": 1, "new_import": 1,
} }
for name, expectedCount := range expectedCollectionFields { for name, expectedCount := range expectedCollectionFields {

View File

@ -128,7 +128,11 @@ func (dao *Dao) FindRecordsByExpr(collectionNameOrId string, exprs ...dbx.Expres
// FindFirstRecordByData returns the first found record matching // FindFirstRecordByData returns the first found record matching
// the provided key-value pair. // the provided key-value pair.
func (dao *Dao) FindFirstRecordByData(collectionNameOrId string, key string, value any) (*models.Record, error) { func (dao *Dao) FindFirstRecordByData(
collectionNameOrId string,
key string,
value any,
) (*models.Record, error) {
collection, err := dao.FindCollectionByNameOrId(collectionNameOrId) collection, err := dao.FindCollectionByNameOrId(collectionNameOrId)
if err != nil { if err != nil {
return nil, err return nil, err
@ -396,11 +400,15 @@ func (dao *Dao) cascadeRecordDelete(mainRecord *models.Record, refs map[*models.
uniqueJsonEachAlias := "__je__" + security.PseudorandomString(4) uniqueJsonEachAlias := "__je__" + security.PseudorandomString(4)
for refCollection, fields := range refs { for refCollection, fields := range refs {
if refCollection.IsView() {
continue // skip view collections
}
for _, field := range fields { for _, field := range fields {
recordTableName := inflector.Columnify(refCollection.Name) recordTableName := inflector.Columnify(refCollection.Name)
prefixedFieldName := recordTableName + "." + inflector.Columnify(field.Name) prefixedFieldName := recordTableName + "." + inflector.Columnify(field.Name)
// @todo optimize single relation lookup in v0.12+ // @todo optimize single relation lookup
query := dao.RecordQuery(refCollection). query := dao.RecordQuery(refCollection).
Distinct(true). Distinct(true).
InnerJoin(fmt.Sprintf( InnerJoin(fmt.Sprintf(

View File

@ -1,7 +1,10 @@
package daos package daos
import ( import (
"fmt"
"github.com/pocketbase/dbx" "github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/models"
) )
// HasTable checks if a table with the provided name exists (case insensitive). // HasTable checks if a table with the provided name exists (case insensitive).
@ -29,9 +32,28 @@ func (dao *Dao) GetTableColumns(tableName string) ([]string, error) {
return columns, err return columns, err
} }
// GetTableInfo returns the `table_info` pragma result for the specified table.
func (dao *Dao) GetTableInfo(tableName string) ([]*models.TableInfoRow, error) {
info := []*models.TableInfoRow{}
err := dao.DB().NewQuery("SELECT * FROM PRAGMA_TABLE_INFO({:tableName})").
Bind(dbx.Params{"tableName": tableName}).
All(&info)
return info, err
}
// DeleteTable drops the specified table. // DeleteTable drops the specified table.
//
// This method is a no-op if a table with the provided name doesn't exist.
//
// Be aware that this method is vulnerable to SQL injection and the
// "tableName" argument must come only from trusted input!
func (dao *Dao) DeleteTable(tableName string) error { func (dao *Dao) DeleteTable(tableName string) error {
_, err := dao.DB().DropTable(tableName).Execute() _, err := dao.DB().NewQuery(fmt.Sprintf(
"DROP TABLE IF EXISTS {{%s}}",
tableName,
)).Execute()
return err return err
} }

View File

@ -3,6 +3,7 @@ package daos_test
import ( import (
"context" "context"
"database/sql" "database/sql"
"encoding/json"
"testing" "testing"
"time" "time"
@ -28,7 +29,7 @@ func TestHasTable(t *testing.T) {
for i, scenario := range scenarios { for i, scenario := range scenarios {
result := app.Dao().HasTable(scenario.tableName) result := app.Dao().HasTable(scenario.tableName)
if result != scenario.expected { if result != scenario.expected {
t.Errorf("(%d) Expected %v, got %v", i, scenario.expected, result) t.Errorf("[%d] Expected %v, got %v", i, scenario.expected, result)
} }
} }
} }
@ -45,21 +46,50 @@ func TestGetTableColumns(t *testing.T) {
{"_params", []string{"id", "key", "value", "created", "updated"}}, {"_params", []string{"id", "key", "value", "created", "updated"}},
} }
for i, scenario := range scenarios { for i, s := range scenarios {
columns, _ := app.Dao().GetTableColumns(scenario.tableName) columns, _ := app.Dao().GetTableColumns(s.tableName)
if len(columns) != len(scenario.expected) { if len(columns) != len(s.expected) {
t.Errorf("(%d) Expected columns %v, got %v", i, scenario.expected, columns) t.Errorf("[%d] Expected columns %v, got %v", i, s.expected, columns)
continue
} }
for _, c := range columns { for _, c := range columns {
if !list.ExistInSlice(c, scenario.expected) { if !list.ExistInSlice(c, s.expected) {
t.Errorf("(%d) Didn't expect column %s", i, c) t.Errorf("[%d] Didn't expect column %s", i, c)
} }
} }
} }
} }
func TestGetTableInfo(t *testing.T) {
app, _ := tests.NewTestApp()
defer app.Cleanup()
scenarios := []struct {
tableName string
expected string
}{
{"", "[]"},
{"missing", "[]"},
{
"_admins",
`[{"PK":1,"Index":0,"Name":"id","Type":"TEXT","NotNull":false,"DefaultValue":null},{"PK":0,"Index":1,"Name":"avatar","Type":"INTEGER","NotNull":true,"DefaultValue":0},{"PK":0,"Index":2,"Name":"email","Type":"TEXT","NotNull":true,"DefaultValue":null},{"PK":0,"Index":3,"Name":"tokenKey","Type":"TEXT","NotNull":true,"DefaultValue":null},{"PK":0,"Index":4,"Name":"passwordHash","Type":"TEXT","NotNull":true,"DefaultValue":null},{"PK":0,"Index":5,"Name":"lastResetSentAt","Type":"TEXT","NotNull":true,"DefaultValue":""},{"PK":0,"Index":6,"Name":"created","Type":"TEXT","NotNull":true,"DefaultValue":""},{"PK":0,"Index":7,"Name":"updated","Type":"TEXT","NotNull":true,"DefaultValue":""}]`,
},
}
for i, s := range scenarios {
rows, _ := app.Dao().GetTableInfo(s.tableName)
raw, _ := json.Marshal(rows)
rawStr := string(raw)
if rawStr != s.expected {
t.Errorf("[%d] Expected \n%v, \ngot \n%v", i, s.expected, rawStr)
}
}
}
func TestDeleteTable(t *testing.T) { func TestDeleteTable(t *testing.T) {
app, _ := tests.NewTestApp() app, _ := tests.NewTestApp()
defer app.Cleanup() defer app.Cleanup()
@ -69,16 +99,17 @@ func TestDeleteTable(t *testing.T) {
expectError bool expectError bool
}{ }{
{"", true}, {"", true},
{"test", true}, {"test", false}, // missing tables are ignored
{"_admins", false}, {"_admins", false},
{"demo3", false}, {"demo3", false},
} }
for i, scenario := range scenarios { for i, s := range scenarios {
err := app.Dao().DeleteTable(scenario.tableName) err := app.Dao().DeleteTable(s.tableName)
hasErr := err != nil hasErr := err != nil
if hasErr != scenario.expectError { if hasErr != s.expectError {
t.Errorf("(%d) Expected hasErr %v, got %v", i, scenario.expectError, hasErr) t.Errorf("[%d] Expected hasErr %v, got %v", i, s.expectError, hasErr)
} }
} }
} }

583
daos/view.go Normal file
View File

@ -0,0 +1,583 @@
package daos
import (
"errors"
"fmt"
"io"
"regexp"
"strings"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/models"
"github.com/pocketbase/pocketbase/models/schema"
"github.com/pocketbase/pocketbase/tools/inflector"
"github.com/pocketbase/pocketbase/tools/list"
"github.com/pocketbase/pocketbase/tools/security"
"github.com/pocketbase/pocketbase/tools/tokenizer"
"github.com/pocketbase/pocketbase/tools/types"
)
// DeleteView drops the specified view name.
//
// This method is a no-op if a view with the provided name doesn't exist.
//
// Be aware that this method is vulnerable to SQL injection and the
// "name" argument must come only from trusted input!
func (dao *Dao) DeleteView(name string) error {
_, err := dao.DB().NewQuery(fmt.Sprintf(
"DROP VIEW IF EXISTS {{%s}}",
name,
)).Execute()
return err
}
// SaveView creates (or updates already existing) persistent SQL view.
//
// Be aware that this method is vulnerable to SQL injection and the
// "selectQuery" argument must come only from trusted input!
func (dao *Dao) SaveView(name string, selectQuery string) error {
return dao.RunInTransaction(func(txDao *Dao) error {
// delete old view (if exists)
if err := txDao.DeleteView(name); err != nil {
return err
}
trimmed := strings.Trim(selectQuery, ";")
// try to eagerly detect multiple inline statements
tk := tokenizer.NewFromString(trimmed)
tk.Separators(';')
if queryParts, _ := tk.ScanAll(); len(queryParts) > 1 {
return errors.New("multiple statements are not supported")
}
// (re)create the view
//
// note: the query is wrapped in a secondary SELECT as a rudimentary
// measure to discourage multiple inline sql statements execution.
viewQuery := fmt.Sprintf("CREATE VIEW {{%s}} AS SELECT * FROM (%s)", name, trimmed)
if _, err := txDao.DB().NewQuery(viewQuery).Execute(); err != nil {
return err
}
// fetch the view table info to ensure that the view was created
// because missing tables or columns won't return an error
if _, err := txDao.GetTableInfo(name); err != nil {
return err
}
return nil
})
}
// CreateViewSchema creates a new view schema from the provided select query.
//
// There are some caveats:
// - The select query must have an "id" column.
// - Wildcard ("*") columns are not supported to avoid accidentally leaking sensitive data.
func (dao *Dao) CreateViewSchema(selectQuery string) (schema.Schema, error) {
result := schema.NewSchema()
suggestedFields, err := dao.parseQueryToFields(selectQuery)
if err != nil {
return result, err
}
// note wrap in a transaction in case the selectQuery contains
// multiple statements allowing us to rollback on any error
txErr := dao.RunInTransaction(func(txDao *Dao) error {
tempView := "_temp_" + security.PseudorandomString(5)
// create a temp view with the provided query
if err := txDao.SaveView(tempView, selectQuery); err != nil {
return err
}
defer txDao.DeleteView(tempView)
// extract the generated view table info
info, err := txDao.GetTableInfo(tempView)
if err != nil {
return err
}
var hasId bool
for _, row := range info {
if row.Name == schema.FieldNameId {
hasId = true
}
if list.ExistInSlice(row.Name, schema.BaseModelFieldNames()) {
continue // skip base model fields since they are not part of the schema
}
var field *schema.SchemaField
if f, ok := suggestedFields[row.Name]; ok {
field = f.field
} else {
field = defaultViewField(row.Name)
}
result.AddField(field)
}
if !hasId {
return errors.New("missing required id column (you ca use `(ROW_NUMBER() OVER()) as id` if you don't have one)")
}
return nil
})
return result, txErr
}
// FindRecordByViewFile returns the original models.Record of the
// provided view collection file.
func (dao *Dao) FindRecordByViewFile(
viewCollectionNameOrId string,
fileFieldName string,
filename string,
) (*models.Record, error) {
view, err := dao.FindCollectionByNameOrId(viewCollectionNameOrId)
if err != nil {
return nil, err
}
if !view.IsView() {
return nil, errors.New("not a view collection")
}
var findFirstNonViewQueryFileField func(int) (*queryField, error)
findFirstNonViewQueryFileField = func(level int) (*queryField, error) {
// check the level depth to prevent infinite circular recursion
// (the limit is arbitrary and may change in the future)
if level > 5 {
return nil, errors.New("reached the max recursion level of view collection file field queries")
}
queryFields, err := dao.parseQueryToFields(view.ViewOptions().Query)
if err != nil {
return nil, err
}
for _, item := range queryFields {
if item.collection == nil ||
item.original == nil ||
item.field.Name != fileFieldName {
continue
}
if item.collection.IsView() {
view = item.collection
fileFieldName = item.original.Name
return findFirstNonViewQueryFileField(level + 1)
}
return item, nil
}
return nil, errors.New("no query file field found")
}
qf, err := findFirstNonViewQueryFileField(1)
if err != nil {
return nil, err
}
cleanFieldName := inflector.Columnify(qf.original.Name)
row := dbx.NullStringMap{}
err = dao.RecordQuery(qf.collection).
InnerJoin(fmt.Sprintf(
// note: the case is used to normalize the value access
`json_each(CASE WHEN json_valid([[%s]]) THEN [[%s]] ELSE json_array([[%s]]) END) as {{_je_file}}`,
cleanFieldName, cleanFieldName, cleanFieldName,
), dbx.HashExp{"_je_file.value": filename}).
Limit(1).
One(row)
if err != nil {
return nil, err
}
return models.NewRecordFromNullStringMap(qf.collection, row), nil
}
// -------------------------------------------------------------------
// Raw query to schema helpers
// -------------------------------------------------------------------
type queryField struct {
// field is the final resolved field.
field *schema.SchemaField
// collection refers to the original field's collection model.
// It could be nil if the found query field is not from a collection schema.
collection *models.Collection
// original is the original found collection field.
// It could be nil if the found query field is not from a collection schema.
original *schema.SchemaField
}
func defaultViewField(name string) *schema.SchemaField {
return &schema.SchemaField{
Name: name,
Type: schema.FieldTypeJson,
}
}
func (dao *Dao) parseQueryToFields(selectQuery string) (map[string]*queryField, error) {
p := new(identifiersParser)
if err := p.parse(selectQuery); err != nil {
return nil, err
}
collections, err := dao.findCollectionsByIdentifiers(p.tables)
if err != nil {
return nil, err
}
result := make(map[string]*queryField, len(p.columns))
var mainTable identifier
if len(p.tables) > 0 {
mainTable = p.tables[0]
}
for _, col := range p.columns {
colLower := strings.ToLower(col.original)
// numeric expression cast
if strings.Contains(colLower, "(") &&
(strings.HasPrefix(colLower, "count(") ||
strings.HasPrefix(colLower, "total(") ||
strings.Contains(colLower, " as numeric") ||
strings.Contains(colLower, " as real") ||
strings.Contains(colLower, " as int") ||
strings.Contains(colLower, " as integer") ||
strings.Contains(colLower, " as decimal")) {
result[col.alias] = &queryField{
field: &schema.SchemaField{
Name: col.alias,
Type: schema.FieldTypeNumber,
},
}
continue
}
parts := strings.Split(col.original, ".")
var fieldName string
var collection *models.Collection
var isMainTableField bool
if len(parts) == 2 {
fieldName = parts[1]
collection = collections[parts[0]]
isMainTableField = parts[0] == mainTable.alias
} else {
fieldName = parts[0]
collection = collections[mainTable.alias]
isMainTableField = true
}
// fallback to the default field if the found column is not from a collection schema
if collection == nil {
result[col.alias] = &queryField{
field: defaultViewField(col.alias),
}
continue
}
if fieldName == "*" {
return nil, errors.New("dynamic column names are not supported")
}
// find the first field by name (case insensitive)
var field *schema.SchemaField
for _, f := range collection.Schema.Fields() {
if strings.EqualFold(f.Name, fieldName) {
field = f
break
}
}
if field != nil {
clone := *field
clone.Name = col.alias
result[col.alias] = &queryField{
field: &clone,
collection: collection,
original: field,
}
continue
}
if fieldName == schema.FieldNameId && !isMainTableField {
// convert to relation since it is a direct id reference to non-maintable collection
result[col.alias] = &queryField{
field: &schema.SchemaField{
Name: col.alias,
Type: schema.FieldTypeRelation,
Options: &schema.RelationOptions{
MaxSelect: types.Pointer(1),
CollectionId: collection.Id,
},
},
collection: collection,
}
} else if fieldName == schema.FieldNameCreated || fieldName == schema.FieldNameUpdated {
result[col.alias] = &queryField{
field: &schema.SchemaField{
Name: col.alias,
Type: schema.FieldTypeDate,
},
collection: collection,
}
} else if fieldName == schema.FieldNameUsername && collection.IsAuth() {
result[col.alias] = &queryField{
field: &schema.SchemaField{
Name: col.alias,
Type: schema.FieldTypeText,
},
collection: collection,
}
} else if fieldName == schema.FieldNameEmail && collection.IsAuth() {
result[col.alias] = &queryField{
field: &schema.SchemaField{
Name: col.alias,
Type: schema.FieldTypeEmail,
},
collection: collection,
}
} else if (fieldName == schema.FieldNameVerified || fieldName == schema.FieldNameEmailVisibility) && collection.IsAuth() {
result[col.alias] = &queryField{
field: &schema.SchemaField{
Name: col.alias,
Type: schema.FieldTypeBool,
},
collection: collection,
}
} else {
result[col.alias] = &queryField{
field: defaultViewField(col.alias),
collection: collection,
}
}
}
return result, nil
}
func (dao *Dao) findCollectionsByIdentifiers(tables []identifier) (map[string]*models.Collection, error) {
names := make([]any, 0, len(tables))
for _, table := range tables {
if strings.Contains(table.alias, "(") {
continue // skip expressions
}
names = append(names, table.original)
}
if len(names) == 0 {
return nil, nil
}
result := make(map[string]*models.Collection, len(names))
collections := make([]*models.Collection, 0, len(names))
err := dao.CollectionQuery().
AndWhere(dbx.In("name", names...)).
All(&collections)
if err != nil {
return nil, err
}
for _, table := range tables {
for _, collection := range collections {
if collection.Name == table.original {
result[table.alias] = collection
}
}
}
return result, nil
}
// -------------------------------------------------------------------
// Raw query identifiers parser
// -------------------------------------------------------------------
var joinReplaceRegex = regexp.MustCompile(`(?im)\s+(inner join|outer join|left join|right join|join)\s+?`)
var discardReplaceRegex = regexp.MustCompile(`(?im)\s+(where|group by|having|order|limit|with)\s+?`)
var commentsReplaceRegex = regexp.MustCompile(`(?m)(\/\*[\s\S]+\*\/)|(--.+$)`)
type identifier struct {
original string
alias string
}
type identifiersParser struct {
columns []identifier
tables []identifier
}
func (p *identifiersParser) parse(selectQuery string) error {
str := strings.Trim(selectQuery, ";")
str = joinReplaceRegex.ReplaceAllString(str, " _join_ ")
str = discardReplaceRegex.ReplaceAllString(str, " _discard_ ")
str = commentsReplaceRegex.ReplaceAllString(str, "")
tk := tokenizer.NewFromString(str)
tk.Separators(',', ' ', '\n', '\t')
tk.KeepSeparator(true)
var skip bool
var partType string
var activeBuilder *strings.Builder
var selectParts strings.Builder
var fromParts strings.Builder
var joinParts strings.Builder
for {
token, err := tk.Scan()
if err != nil {
if err != io.EOF {
return err
}
break
}
trimmed := strings.ToLower(strings.TrimSpace(token))
switch trimmed {
case "select":
skip = false
partType = "select"
activeBuilder = &selectParts
case "from":
skip = false
partType = "from"
activeBuilder = &fromParts
case "_join_":
skip = false
// the previous part was also a join
if partType == "join" {
joinParts.WriteString(",")
}
partType = "join"
activeBuilder = &joinParts
case "_discard_":
// do nothing...
skip = true
default:
isJoin := partType == "join"
if isJoin && trimmed == "on" {
skip = true
}
if !skip && activeBuilder != nil {
activeBuilder.WriteString(" ")
activeBuilder.WriteString(token)
}
}
}
selects, err := extractIdentifiers(selectParts.String())
if err != nil {
return err
}
froms, err := extractIdentifiers(fromParts.String())
if err != nil {
return err
}
joins, err := extractIdentifiers(joinParts.String())
if err != nil {
return err
}
p.columns = selects
p.tables = froms
p.tables = append(p.tables, joins...)
return nil
}
func extractIdentifiers(rawExpression string) ([]identifier, error) {
rawTk := tokenizer.NewFromString(rawExpression)
rawTk.Separators(',')
rawIdentifiers, err := rawTk.ScanAll()
if err != nil {
return nil, err
}
result := make([]identifier, 0, len(rawIdentifiers))
for _, rawIdentifier := range rawIdentifiers {
tk := tokenizer.NewFromString(rawIdentifier)
tk.Separators(' ', '\n', '\t')
parts, err := tk.ScanAll()
if err != nil {
return nil, err
}
resolved, err := identifierFromParts(parts)
if err != nil {
return nil, err
}
result = append(result, resolved)
}
return result, nil
}
func identifierFromParts(parts []string) (identifier, error) {
var result identifier
switch len(parts) {
case 3:
if !strings.EqualFold(parts[1], "as") {
return result, fmt.Errorf(`invalid identifier part - expected "as", got %v`, parts[1])
}
result.original = parts[0]
result.alias = parts[2]
case 2:
result.original = parts[0]
result.alias = parts[1]
case 1:
subParts := strings.Split(parts[0], ".")
result.original = parts[0]
result.alias = subParts[len(subParts)-1]
default:
return result, fmt.Errorf(`invalid identifier parts %v`, parts)
}
result.original = trimRawIdentifier(result.original)
result.alias = trimRawIdentifier(result.alias)
return result, nil
}
func trimRawIdentifier(rawIdentifier string) string {
const trimChars = "`\"[];"
parts := strings.Split(rawIdentifier, ".")
for i := range parts {
parts[i] = strings.Trim(parts[i], trimChars)
}
return strings.Join(parts, ".")
}

539
daos/view_test.go Normal file
View File

@ -0,0 +1,539 @@
package daos_test
import (
"encoding/json"
"fmt"
"testing"
"github.com/pocketbase/pocketbase/models"
"github.com/pocketbase/pocketbase/models/schema"
"github.com/pocketbase/pocketbase/tests"
"github.com/pocketbase/pocketbase/tools/list"
)
func TestDeleteView(t *testing.T) {
app, _ := tests.NewTestApp()
defer app.Cleanup()
scenarios := []struct {
viewName string
expectError bool
}{
{"", true},
{"demo1", true}, // not a view table
{"missing", false}, // missing or already deleted
{"view1", false}, // existing
{"VieW1", false}, // view names are case insensitives
}
for i, s := range scenarios {
err := app.Dao().DeleteView(s.viewName)
hasErr := err != nil
if hasErr != s.expectError {
t.Errorf("[%d - %q] Expected hasErr %v, got %v (%v)", i, s.viewName, s.expectError, hasErr, err)
}
}
}
func TestSaveView(t *testing.T) {
app, _ := tests.NewTestApp()
defer app.Cleanup()
scenarios := []struct {
scenarioName string
viewName string
query string
expectError bool
expectColumns []string
}{
{
"empty name and query",
"",
"",
true,
nil,
},
{
"empty name",
"",
"select * from _admins",
true,
nil,
},
{
"empty query",
"123Test",
"",
true,
nil,
},
{
"invalid query",
"123Test",
"123 456",
true,
nil,
},
{
"missing table",
"123Test",
"select * from missing",
true,
nil,
},
{
"non select query",
"123Test",
"drop table _admins",
true,
nil,
},
{
"multiple select queries",
"123Test",
"select *, count(id) as c from _admins; select * from demo1;",
true,
nil,
},
{
"try to break the parent parenthesis",
"123Test",
"select *, count(id) as c from `_admins`)",
true,
nil,
},
{
"simple select query (+ trimmed semicolon)",
"123Test",
";select *, count(id) as c from _admins;",
false,
[]string{
"id", "created", "updated",
"passwordHash", "tokenKey", "email",
"lastResetSentAt", "avatar", "c",
},
},
{
"update old view with new query",
"123Test",
"select 1 as test from _admins",
false,
[]string{"test"},
},
}
for _, s := range scenarios {
err := app.Dao().SaveView(s.viewName, s.query)
hasErr := err != nil
if hasErr != s.expectError {
t.Errorf("[%s] Expected hasErr %v, got %v (%v)", s.scenarioName, s.expectError, hasErr, err)
continue
}
if hasErr {
continue
}
infoRows, err := app.Dao().GetTableInfo(s.viewName)
if err != nil {
t.Errorf("[%s] Failed to fetch table info for %s: %v", s.scenarioName, s.viewName, err)
continue
}
if len(s.expectColumns) != len(infoRows) {
t.Errorf("[%s] Expected %d columns, got %d", s.scenarioName, len(s.expectColumns), len(infoRows))
continue
}
for _, row := range infoRows {
if !list.ExistInSlice(row.Name, s.expectColumns) {
t.Errorf("[%s] Missing %q column in %v", s.scenarioName, row.Name, s.expectColumns)
}
}
}
}
func TestCreateViewSchema(t *testing.T) {
app, _ := tests.NewTestApp()
defer app.Cleanup()
scenarios := []struct {
name string
query string
expectError bool
expectFields map[string]string // name-type pairs
}{
{
"empty query",
"",
true,
nil,
},
{
"invalid query",
"test 123456",
true,
nil,
},
{
"missing table",
"select * from missing",
true,
nil,
},
{
"query with wildcard column",
"select a.id, a.* from demo1 a",
true,
nil,
},
{
"query without id",
"select text, url, created, updated from demo1",
true,
nil,
},
{
"query with comments",
`
select
-- test single line
id,
text,
/* multi
line comment */
url, created, updated from demo1
`,
false,
map[string]string{
"text": schema.FieldTypeText,
"url": schema.FieldTypeUrl,
},
},
{
"query with all fields and quoted identifiers",
`
select
"id",
"created",
"updated",
[text],
` + "`bool`" + `,
"url",
"select_one",
"select_many",
"file_one",
"demo1"."file_many",
` + "`demo1`." + "`number`" + ` number_alias,
"email",
"datetime",
"json",
"rel_one",
"rel_many"
from demo1
`,
false,
map[string]string{
"text": schema.FieldTypeText,
"bool": schema.FieldTypeBool,
"url": schema.FieldTypeUrl,
"select_one": schema.FieldTypeSelect,
"select_many": schema.FieldTypeSelect,
"file_one": schema.FieldTypeFile,
"file_many": schema.FieldTypeFile,
"number_alias": schema.FieldTypeNumber,
"email": schema.FieldTypeEmail,
"datetime": schema.FieldTypeDate,
"json": schema.FieldTypeJson,
"rel_one": schema.FieldTypeRelation,
"rel_many": schema.FieldTypeRelation,
},
},
{
"query with indirect relations fields",
"select a.id, b.id as bid, b.created from demo1 as a left join demo2 b",
false,
map[string]string{
"bid": schema.FieldTypeRelation,
},
},
{
"query with multiple froms, joins and style of aliasses",
`
select
a.id as id,
b.id as bid,
lj.id cid,
ij.id as did,
a.bool,
_admins.id as eid,
_admins.email
from demo1 a, demo2 as b
left join demo3 lj on lj.id = 123
inner join demo4 as ij on ij.id = 123
join _admins
where 1=1
group by a.id
limit 10
`,
false,
map[string]string{
"bid": schema.FieldTypeRelation,
"cid": schema.FieldTypeRelation,
"did": schema.FieldTypeRelation,
"bool": schema.FieldTypeBool,
"eid": schema.FieldTypeJson, // not from collection
"email": schema.FieldTypeJson, // not from collection
},
},
{
"query with numeric casts",
`select
a.id,
count(a.id) count,
cast(a.id as int) cast_int,
cast(a.id as integer) cast_integer,
cast(a.id as real) cast_real,
cast(a.id as decimal) cast_decimal,
cast(a.id as numeric) cast_numeric,
avg(a.id) avg,
sum(a.id) sum,
total(a.id) total,
min(a.id) min,
max(a.id) max
from demo1 a`,
false,
map[string]string{
"count": schema.FieldTypeNumber,
"total": schema.FieldTypeNumber,
"cast_int": schema.FieldTypeNumber,
"cast_integer": schema.FieldTypeNumber,
"cast_real": schema.FieldTypeNumber,
"cast_decimal": schema.FieldTypeNumber,
"cast_numeric": schema.FieldTypeNumber,
// json because they are nullable
"sum": schema.FieldTypeJson,
"avg": schema.FieldTypeJson,
"min": schema.FieldTypeJson,
"max": schema.FieldTypeJson,
},
},
{
"query with reserved auth collection fields",
`
select
a.id,
a.username,
a.email,
a.emailVisibility,
a.verified,
demo1.id relid
from users a
left join demo1
`,
false,
map[string]string{
"username": schema.FieldTypeText,
"email": schema.FieldTypeEmail,
"emailVisibility": schema.FieldTypeBool,
"verified": schema.FieldTypeBool,
"relid": schema.FieldTypeRelation,
},
},
{
"query with unknown fields and aliases",
`select
id,
id as id2,
text as text_alias,
url as url_alias,
"demo1"."bool" as bool_alias,
number as number_alias,
created created_alias,
updated updated_alias,
123 as custom
from demo1
`,
false,
map[string]string{
"id2": schema.FieldTypeJson,
"text_alias": schema.FieldTypeText,
"url_alias": schema.FieldTypeUrl,
"bool_alias": schema.FieldTypeBool,
"number_alias": schema.FieldTypeNumber,
"created_alias": schema.FieldTypeDate,
"updated_alias": schema.FieldTypeDate,
"custom": schema.FieldTypeJson,
},
},
}
for _, s := range scenarios {
result, err := app.Dao().CreateViewSchema(s.query)
hasErr := err != nil
if hasErr != s.expectError {
t.Errorf("[%s] Expected hasErr %v, got %v (%v)", s.name, s.expectError, hasErr, err)
continue
}
if hasErr {
continue
}
if len(s.expectFields) != len(result.Fields()) {
serialized, _ := json.Marshal(result)
t.Errorf("[%s] Expected %d fields, got %d: \n%s", s.name, len(s.expectFields), len(result.Fields()), serialized)
continue
}
for name, typ := range s.expectFields {
field := result.GetFieldByName(name)
if field == nil {
t.Errorf("[%s] Expected to find field %s, got nil", s.name, name)
continue
}
if field.Type != typ {
t.Errorf("[%s] Expected field %s to be %q, got %s", s.name, name, typ, field.Type)
continue
}
}
}
}
func TestFindRecordByViewFile(t *testing.T) {
app, _ := tests.NewTestApp()
defer app.Cleanup()
prevCollection, err := app.Dao().FindCollectionByNameOrId("demo1")
if err != nil {
t.Fatal(err)
}
totalLevels := 6
// create collection view mocks
fileOneAlias := "file_one one0"
fileManyAlias := "file_many many0"
mockCollections := make([]*models.Collection, 0, totalLevels)
for i := 0; i <= totalLevels; i++ {
view := new(models.Collection)
view.Type = models.CollectionTypeView
view.Name = fmt.Sprintf("_test_view%d", i)
view.SetOptions(&models.CollectionViewOptions{
Query: fmt.Sprintf(
"select id, %s, %s from %s",
fileOneAlias,
fileManyAlias,
prevCollection.Name,
),
})
// save view
if err := app.Dao().SaveCollection(view); err != nil {
t.Fatalf("Failed to save view%d: %v", i, err)
}
mockCollections = append(mockCollections, view)
prevCollection = view
fileOneAlias = fmt.Sprintf("one%d one%d", i, i+1)
fileManyAlias = fmt.Sprintf("many%d many%d", i, i+1)
}
fileOneName := "test_d61b33QdDU.txt"
fileManyName := "test_QZFjKjXchk.txt"
expectedRecordId := "84nmscqy84lsi1t"
scenarios := []struct {
name string
collectionNameOrId string
fileFieldName string
filename string
expectError bool
expectRecordId string
}{
{
"missing collection",
"missing",
"a",
fileOneName,
true,
"",
},
{
"non-view collection",
"demo1",
"file_one",
fileOneName,
true,
"",
},
{
"view collection after the max recursion limit",
mockCollections[totalLevels-1].Name,
fmt.Sprintf("one%d", totalLevels-1),
fileOneName,
true,
"",
},
{
"first view collection (single file)",
mockCollections[0].Name,
"one0",
fileOneName,
false,
expectedRecordId,
},
{
"first view collection (many files)",
mockCollections[0].Name,
"many0",
fileManyName,
false,
expectedRecordId,
},
{
"last view collection before the recursion limit (single file)",
mockCollections[totalLevels-2].Name,
fmt.Sprintf("one%d", totalLevels-2),
fileOneName,
false,
expectedRecordId,
},
{
"last view collection before the recursion limit (many files)",
mockCollections[totalLevels-2].Name,
fmt.Sprintf("many%d", totalLevels-2),
fileManyName,
false,
expectedRecordId,
},
}
for _, s := range scenarios {
record, err := app.Dao().FindRecordByViewFile(
s.collectionNameOrId,
s.fileFieldName,
s.filename,
)
hasErr := err != nil
if hasErr != s.expectError {
t.Errorf("[%s] Expected hasErr %v, got %v (%v)", s.name, s.expectError, hasErr, err)
continue
}
if hasErr {
continue
}
if record.Id != s.expectRecordId {
t.Errorf("[%s] Expected recordId %q, got %q", s.name, s.expectRecordId, record.Id)
}
}
}

View File

@ -69,7 +69,7 @@ func NewCollectionUpsert(app core.App, collection *models.Collection) *Collectio
} }
clone, _ := form.collection.Schema.Clone() clone, _ := form.collection.Schema.Clone()
if clone != nil { if clone != nil && form.Type != models.CollectionTypeView {
form.Schema = *clone form.Schema = *clone
} else { } else {
form.Schema = schema.Schema{} form.Schema = schema.Schema{}
@ -86,6 +86,16 @@ func (form *CollectionUpsert) SetDao(dao *daos.Dao) {
// Validate makes the form validatable by implementing [validation.Validatable] interface. // Validate makes the form validatable by implementing [validation.Validatable] interface.
func (form *CollectionUpsert) Validate() error { func (form *CollectionUpsert) Validate() error {
isAuth := form.Type == models.CollectionTypeAuth isAuth := form.Type == models.CollectionTypeAuth
isView := form.Type == models.CollectionTypeView
// generate schema from the query (overwriting any explicit user defined schema)
if isView {
options := models.CollectionViewOptions{}
if err := decodeOptions(form.Options, &options); err != nil {
return err
}
form.Schema, _ = form.dao.CreateViewSchema(options.Query)
}
return validation.ValidateStruct(form, return validation.ValidateStruct(form,
validation.Field( validation.Field(
@ -104,7 +114,11 @@ func (form *CollectionUpsert) Validate() error {
validation.Field( validation.Field(
&form.Type, &form.Type,
validation.Required, validation.Required,
validation.In(models.CollectionTypeAuth, models.CollectionTypeBase), validation.In(
models.CollectionTypeBase,
models.CollectionTypeAuth,
models.CollectionTypeView,
),
validation.By(form.ensureNoTypeChange), validation.By(form.ensureNoTypeChange),
), ),
validation.Field( validation.Field(
@ -115,23 +129,32 @@ func (form *CollectionUpsert) Validate() error {
validation.By(form.ensureNoSystemNameChange), validation.By(form.ensureNoSystemNameChange),
validation.By(form.checkUniqueName), validation.By(form.checkUniqueName),
), ),
// validates using the type's own validation rules + some collection's specific // validates using the type's own validation rules + some collection's specifics
validation.Field( validation.Field(
&form.Schema, &form.Schema,
validation.By(form.checkMinSchemaFields), validation.By(form.checkMinSchemaFields),
validation.By(form.ensureNoSystemFieldsChange), validation.By(form.ensureNoSystemFieldsChange),
validation.By(form.ensureNoFieldsTypeChange), validation.By(form.ensureNoFieldsTypeChange),
validation.By(form.checkRelationFields), validation.By(form.checkRelationFields),
validation.When( validation.When(isAuth, validation.By(form.ensureNoAuthFieldName)),
isAuth,
validation.By(form.ensureNoAuthFieldName),
),
), ),
validation.Field(&form.ListRule, validation.By(form.checkRule)), validation.Field(&form.ListRule, validation.By(form.checkRule)),
validation.Field(&form.ViewRule, validation.By(form.checkRule)), validation.Field(&form.ViewRule, validation.By(form.checkRule)),
validation.Field(&form.CreateRule, validation.By(form.checkRule)), validation.Field(
validation.Field(&form.UpdateRule, validation.By(form.checkRule)), &form.CreateRule,
validation.Field(&form.DeleteRule, validation.By(form.checkRule)), validation.When(isView, validation.Nil),
validation.By(form.checkRule),
),
validation.Field(
&form.UpdateRule,
validation.When(isView, validation.Nil),
validation.By(form.checkRule),
),
validation.Field(
&form.DeleteRule,
validation.When(isView, validation.Nil),
validation.By(form.checkRule),
),
validation.Field(&form.Options, validation.By(form.checkOptions)), validation.Field(&form.Options, validation.By(form.checkOptions)),
) )
} }
@ -288,13 +311,15 @@ func (form *CollectionUpsert) ensureNoAuthFieldName(value any) error {
} }
func (form *CollectionUpsert) checkMinSchemaFields(value any) error { func (form *CollectionUpsert) checkMinSchemaFields(value any) error {
if form.Type == models.CollectionTypeAuth { v, _ := value.(schema.Schema)
return nil // auth collections doesn't require having additional schema fields
}
v, ok := value.(schema.Schema) switch form.Type {
if !ok || len(v.Fields()) == 0 { case models.CollectionTypeAuth, models.CollectionTypeView:
return validation.ErrRequired return nil // no schema fields constraint
default:
if len(v.Fields()) == 0 {
return validation.ErrRequired
}
} }
return nil return nil
@ -343,15 +368,11 @@ func (form *CollectionUpsert) checkRule(value any) error {
func (form *CollectionUpsert) checkOptions(value any) error { func (form *CollectionUpsert) checkOptions(value any) error {
v, _ := value.(types.JsonMap) v, _ := value.(types.JsonMap)
if form.Type == models.CollectionTypeAuth { switch form.Type {
raw, err := v.MarshalJSON() case models.CollectionTypeAuth:
if err != nil {
return validation.NewError("validation_invalid_options", "Invalid options.")
}
options := models.CollectionAuthOptions{} options := models.CollectionAuthOptions{}
if err := json.Unmarshal(raw, &options); err != nil { if err := decodeOptions(v, &options); err != nil {
return validation.NewError("validation_invalid_options", "Invalid options.") return err
} }
// check the generic validations // check the generic validations
@ -363,6 +384,39 @@ func (form *CollectionUpsert) checkOptions(value any) error {
if err := form.checkRule(options.ManageRule); err != nil { if err := form.checkRule(options.ManageRule); err != nil {
return validation.Errors{"manageRule": err} return validation.Errors{"manageRule": err}
} }
case models.CollectionTypeView:
options := models.CollectionViewOptions{}
if err := decodeOptions(v, &options); err != nil {
return err
}
// check the generic validations
if err := options.Validate(); err != nil {
return err
}
// check the query option
if _, err := form.dao.CreateViewSchema(options.Query); err != nil {
return validation.Errors{
"query": validation.NewError(
"validation_invalid_view_query",
fmt.Sprintf("Invalid query - %s", err.Error()),
),
}
}
}
return nil
}
func decodeOptions(options types.JsonMap, result any) error {
raw, err := options.MarshalJSON()
if err != nil {
return validation.NewError("validation_invalid_options", "Invalid options.")
}
if err := json.Unmarshal(raw, result); err != nil {
return validation.NewError("validation_invalid_options", "Invalid options.")
} }
return nil return nil
@ -398,7 +452,11 @@ func (form *CollectionUpsert) Submit(interceptors ...InterceptorFunc[*models.Col
form.collection.Name = form.Name form.collection.Name = form.Name
} }
form.collection.Schema = form.Schema // view schema is autogenerated on save
if !form.collection.IsView() {
form.collection.Schema = form.Schema
}
form.collection.ListRule = form.ListRule form.collection.ListRule = form.ListRule
form.collection.ViewRule = form.ViewRule form.collection.ViewRule = form.ViewRule
form.collection.CreateRule = form.CreateRule form.collection.CreateRule = form.CreateRule

View File

@ -22,7 +22,7 @@ func TestNewCollectionUpsert(t *testing.T) {
collection.Name = "test_name" collection.Name = "test_name"
collection.Type = "test_type" collection.Type = "test_type"
collection.System = true collection.System = true
listRule := "testview" listRule := "test_list"
collection.ListRule = &listRule collection.ListRule = &listRule
viewRule := "test_view" viewRule := "test_view"
collection.ViewRule = &viewRule collection.ViewRule = &viewRule
@ -98,6 +98,7 @@ func TestCollectionUpsertValidateAndSubmit(t *testing.T) {
}{ }{
{"empty create (base)", "", "{}", []string{"name", "schema"}}, {"empty create (base)", "", "{}", []string{"name", "schema"}},
{"empty create (auth)", "", `{"type":"auth"}`, []string{"name"}}, {"empty create (auth)", "", `{"type":"auth"}`, []string{"name"}},
{"empty create (view)", "", `{"type":"view"}`, []string{"name", "options"}},
{"empty update", "demo2", "{}", []string{}}, {"empty update", "demo2", "{}", []string{}},
{ {
"create failure", "create failure",
@ -188,7 +189,7 @@ func TestCollectionUpsertValidateAndSubmit(t *testing.T) {
[]string{"schema"}, []string{"schema"},
}, },
{ {
"create failure - check type options validators", "create failure - check auth options validators",
"", "",
`{ `{
"name": "test_new", "name": "test_new",
@ -200,6 +201,16 @@ func TestCollectionUpsertValidateAndSubmit(t *testing.T) {
}`, }`,
[]string{"options"}, []string{"options"},
}, },
{
"create failure - check view options validators",
"",
`{
"name": "test_new",
"type": "view",
"options": { "query": "invalid query" }
}`,
[]string{"options"},
},
{ {
"create success", "create success",
"", "",
@ -356,6 +367,99 @@ func TestCollectionUpsertValidateAndSubmit(t *testing.T) {
}`, }`,
[]string{}, []string{},
}, },
// view tests
// -----------------------------------------------------------
{
"view create failure",
"",
`{
"name": "upsert_view",
"type": "view",
"listRule": "id='123' && verified = true",
"viewRule": "id='123' && emailVisibility = true",
"schema": [
{"id":"abc123","name":"some invalid field name that will be overwritten !@#$","type":"bool"}
],
"options": {
"query": "select id, email from users; drop table _admins;"
}
}`,
[]string{
"listRule",
"viewRule",
"options",
},
},
{
"view create success",
"",
`{
"name": "upsert_view",
"type": "view",
"listRule": "id='123' && verified = true",
"viewRule": "id='123' && emailVisibility = true",
"schema": [
{"id":"abc123","name":"some invalid field name that will be overwritten !@#$","type":"bool"}
],
"options": {
"query": "select id, emailVisibility, verified from users"
}
}`,
[]string{
// "schema", should be overwritten by an autogenerated from the query
},
},
{
"view update failure (schema autogeneration and rule fields check)",
"upsert_view",
`{
"name": "upsert_view_2",
"listRule": "id='456' && verified = true",
"viewRule": "id='456'",
"createRule": "id='123'",
"updateRule": "id='123'",
"deleteRule": "id='123'",
"schema": [
{"id":"abc123","name":"verified","type":"bool"}
],
"options": {
"query": "select 1 as id"
}
}`,
[]string{
"listRule", // missing field (ignoring the old or explicit schema)
"createRule", // not allowed
"updateRule", // not allowed
"deleteRule", // not allowed
},
},
{
"view update failure (check query identifiers format)",
"upsert_view",
`{
"listRule": null,
"viewRule": null,
"options": {
"query": "select 1 as id, 2 as [invalid!@#]"
}
}`,
[]string{
"schema", // should fail due to invalid field name
},
},
{
"view update success",
"upsert_view",
`{
"listRule": null,
"viewRule": null,
"options": {
"query": "select 1 as id, 2 as valid"
}
}`,
[]string{},
},
} }
for _, s := range scenarios { for _, s := range scenarios {
@ -454,10 +558,19 @@ func TestCollectionUpsertValidateAndSubmit(t *testing.T) {
t.Errorf("[%s] Expected DeleteRule %v, got %v", s.testName, collection.DeleteRule, form.DeleteRule) t.Errorf("[%s] Expected DeleteRule %v, got %v", s.testName, collection.DeleteRule, form.DeleteRule)
} }
formSchema, _ := form.Schema.MarshalJSON() rawFormSchema, _ := form.Schema.MarshalJSON()
collectionSchema, _ := collection.Schema.MarshalJSON() rawCollectionSchema, _ := collection.Schema.MarshalJSON()
if string(formSchema) != string(collectionSchema) {
t.Errorf("[%s] Expected Schema %v, got %v", s.testName, string(collectionSchema), string(formSchema)) if len(form.Schema.Fields()) != len(collection.Schema.Fields()) {
t.Errorf("[%s] Expected Schema \n%v, \ngot \n%v", s.testName, string(rawCollectionSchema), string(rawFormSchema))
continue
}
for _, f := range form.Schema.Fields() {
if collection.Schema.GetFieldByName(f.Name) == nil {
t.Errorf("[%s] Missing field %s \nin \n%v", s.testName, f.Name, string(rawFormSchema))
continue
}
} }
} }
} }

View File

@ -38,6 +38,8 @@ func TestCollectionsImportValidate(t *testing.T) {
} }
func TestCollectionsImportSubmit(t *testing.T) { func TestCollectionsImportSubmit(t *testing.T) {
totalCollections := 10
scenarios := []struct { scenarios := []struct {
name string name string
jsonData string jsonData string
@ -52,7 +54,7 @@ func TestCollectionsImportSubmit(t *testing.T) {
"collections": [] "collections": []
}`, }`,
expectError: true, expectError: true,
expectCollectionsCount: 8, expectCollectionsCount: totalCollections,
expectEvents: nil, expectEvents: nil,
}, },
{ {
@ -82,7 +84,7 @@ func TestCollectionsImportSubmit(t *testing.T) {
] ]
}`, }`,
expectError: true, expectError: true,
expectCollectionsCount: 8, expectCollectionsCount: totalCollections,
expectEvents: map[string]int{ expectEvents: map[string]int{
"OnModelBeforeCreate": 2, "OnModelBeforeCreate": 2,
}, },
@ -101,7 +103,7 @@ func TestCollectionsImportSubmit(t *testing.T) {
] ]
}`, }`,
expectError: true, expectError: true,
expectCollectionsCount: 8, expectCollectionsCount: totalCollections,
expectEvents: map[string]int{ expectEvents: map[string]int{
"OnModelBeforeCreate": 2, "OnModelBeforeCreate": 2,
}, },
@ -137,7 +139,7 @@ func TestCollectionsImportSubmit(t *testing.T) {
] ]
}`, }`,
expectError: false, expectError: false,
expectCollectionsCount: 11, expectCollectionsCount: totalCollections + 3,
expectEvents: map[string]int{ expectEvents: map[string]int{
"OnModelBeforeCreate": 3, "OnModelBeforeCreate": 3,
"OnModelAfterCreate": 3, "OnModelAfterCreate": 3,
@ -160,7 +162,7 @@ func TestCollectionsImportSubmit(t *testing.T) {
] ]
}`, }`,
expectError: true, expectError: true,
expectCollectionsCount: 8, expectCollectionsCount: totalCollections,
expectEvents: map[string]int{ expectEvents: map[string]int{
"OnModelBeforeCreate": 1, "OnModelBeforeCreate": 1,
}, },
@ -202,7 +204,7 @@ func TestCollectionsImportSubmit(t *testing.T) {
] ]
}`, }`,
expectError: true, expectError: true,
expectCollectionsCount: 8, expectCollectionsCount: totalCollections,
expectEvents: map[string]int{ expectEvents: map[string]int{
"OnModelBeforeDelete": 5, "OnModelBeforeDelete": 5,
}, },
@ -253,7 +255,7 @@ func TestCollectionsImportSubmit(t *testing.T) {
] ]
}`, }`,
expectError: false, expectError: false,
expectCollectionsCount: 10, expectCollectionsCount: totalCollections + 2,
expectEvents: map[string]int{ expectEvents: map[string]int{
"OnModelBeforeUpdate": 1, "OnModelBeforeUpdate": 1,
"OnModelAfterUpdate": 1, "OnModelAfterUpdate": 1,
@ -341,8 +343,8 @@ func TestCollectionsImportSubmit(t *testing.T) {
"OnModelAfterUpdate": 2, "OnModelAfterUpdate": 2,
"OnModelBeforeCreate": 1, "OnModelBeforeCreate": 1,
"OnModelAfterCreate": 1, "OnModelAfterCreate": 1,
"OnModelBeforeDelete": 6, "OnModelBeforeDelete": totalCollections - 2,
"OnModelAfterDelete": 6, "OnModelAfterDelete": totalCollections - 2,
}, },
}, },
} }

View File

@ -17,6 +17,7 @@ var (
const ( const (
CollectionTypeBase = "base" CollectionTypeBase = "base"
CollectionTypeAuth = "auth" CollectionTypeAuth = "auth"
CollectionTypeView = "view"
) )
type Collection struct { type Collection struct {
@ -52,11 +53,16 @@ func (m *Collection) IsBase() bool {
return m.Type == CollectionTypeBase return m.Type == CollectionTypeBase
} }
// IsBase checks if the current collection has "auth" type. // IsAuth checks if the current collection has "auth" type.
func (m *Collection) IsAuth() bool { func (m *Collection) IsAuth() bool {
return m.Type == CollectionTypeAuth return m.Type == CollectionTypeAuth
} }
// IsView checks if the current collection has "view" type.
func (m *Collection) IsView() bool {
return m.Type == CollectionTypeView
}
// MarshalJSON implements the [json.Marshaler] interface. // MarshalJSON implements the [json.Marshaler] interface.
func (m Collection) MarshalJSON() ([]byte, error) { func (m Collection) MarshalJSON() ([]byte, error) {
type alias Collection // prevent recursion type alias Collection // prevent recursion
@ -82,6 +88,14 @@ func (m *Collection) AuthOptions() CollectionAuthOptions {
return result return result
} }
// ViewOptions decodes the current collection options and returns them
// as new [CollectionViewOptions] instance.
func (m *Collection) ViewOptions() CollectionViewOptions {
result := CollectionViewOptions{}
m.DecodeOptions(&result)
return result
}
// NormalizeOptions updates the current collection options with a // NormalizeOptions updates the current collection options with a
// new normalized state based on the collection type. // new normalized state based on the collection type.
func (m *Collection) NormalizeOptions() error { func (m *Collection) NormalizeOptions() error {
@ -89,6 +103,8 @@ func (m *Collection) NormalizeOptions() error {
switch m.Type { switch m.Type {
case CollectionTypeAuth: case CollectionTypeAuth:
typedOptions = m.AuthOptions() typedOptions = m.AuthOptions()
case CollectionTypeView:
typedOptions = m.ViewOptions()
default: default:
typedOptions = m.BaseOptions() typedOptions = m.BaseOptions()
} }
@ -143,7 +159,7 @@ func (m *Collection) SetOptions(typedOptions any) error {
// ------------------------------------------------------------------- // -------------------------------------------------------------------
// CollectionAuthOptions defines the "base" Collection.Options fields. // CollectionBaseOptions defines the "base" Collection.Options fields.
type CollectionBaseOptions struct { type CollectionBaseOptions struct {
} }
@ -152,6 +168,8 @@ func (o CollectionBaseOptions) Validate() error {
return nil return nil
} }
// -------------------------------------------------------------------
// CollectionAuthOptions defines the "auth" Collection.Options fields. // CollectionAuthOptions defines the "auth" Collection.Options fields.
type CollectionAuthOptions struct { type CollectionAuthOptions struct {
ManageRule *string `form:"manageRule" json:"manageRule"` ManageRule *string `form:"manageRule" json:"manageRule"`
@ -184,3 +202,17 @@ func (o CollectionAuthOptions) Validate() error {
), ),
) )
} }
// -------------------------------------------------------------------
// CollectionViewOptions defines the "view" Collection.Options fields.
type CollectionViewOptions struct {
Query string `form:"query" json:"query"`
}
// Validate implements [validation.Validatable] interface.
func (o CollectionViewOptions) Validate() error {
return validation.ValidateStruct(&o,
validation.Field(&o.Query, validation.Required),
)
}

View File

@ -97,11 +97,11 @@ func TestCollectionMarshalJSON(t *testing.T) {
for _, s := range scenarios { for _, s := range scenarios {
result, err := s.collection.MarshalJSON() result, err := s.collection.MarshalJSON()
if err != nil { if err != nil {
t.Errorf("(%s) Unexpected error %v", s.name, err) t.Errorf("[%s] Unexpected error %v", s.name, err)
continue continue
} }
if string(result) != s.expected { if string(result) != s.expected {
t.Errorf("(%s) Expected\n%v \ngot \n%v", s.name, s.expected, string(result)) t.Errorf("[%s] Expected\n%v \ngot \n%v", s.name, s.expected, string(result))
} }
} }
} }
@ -143,7 +143,7 @@ func TestCollectionBaseOptions(t *testing.T) {
} }
if strEncoded := string(encoded); strEncoded != s.expected { if strEncoded := string(encoded); strEncoded != s.expected {
t.Errorf("(%s) Expected \n%v \ngot \n%v", s.name, s.expected, strEncoded) t.Errorf("[%s] Expected \n%v \ngot \n%v", s.name, s.expected, strEncoded)
} }
} }
} }
@ -188,7 +188,52 @@ func TestCollectionAuthOptions(t *testing.T) {
} }
if strEncoded := string(encoded); strEncoded != s.expected { if strEncoded := string(encoded); strEncoded != s.expected {
t.Errorf("(%s) Expected \n%v \ngot \n%v", s.name, s.expected, strEncoded) t.Errorf("[%s] Expected \n%v \ngot \n%v", s.name, s.expected, strEncoded)
}
}
}
func TestCollectionViewOptions(t *testing.T) {
options := types.JsonMap{"query": "select id from demo1", "minPasswordLength": 4}
expectedSerialization := `{"query":"select id from demo1"}`
scenarios := []struct {
name string
collection models.Collection
expected string
}{
{
"no type",
models.Collection{Options: options},
expectedSerialization,
},
{
"unknown type",
models.Collection{Type: "anything", Options: options},
expectedSerialization,
},
{
"different type",
models.Collection{Type: models.CollectionTypeBase, Options: options},
expectedSerialization,
},
{
"view type",
models.Collection{Type: models.CollectionTypeView, Options: options},
expectedSerialization,
},
}
for _, s := range scenarios {
result := s.collection.ViewOptions()
encoded, err := json.Marshal(result)
if err != nil {
t.Fatal(err)
}
if strEncoded := string(encoded); strEncoded != s.expected {
t.Errorf("[%s] Expected \n%v \ngot \n%v", s.name, s.expected, strEncoded)
} }
} }
} }
@ -218,7 +263,7 @@ func TestNormalizeOptions(t *testing.T) {
for _, s := range scenarios { for _, s := range scenarios {
if err := s.collection.NormalizeOptions(); err != nil { if err := s.collection.NormalizeOptions(); err != nil {
t.Errorf("(%s) Unexpected error %v", s.name, err) t.Errorf("[%s] Unexpected error %v", s.name, err)
continue continue
} }
@ -228,7 +273,7 @@ func TestNormalizeOptions(t *testing.T) {
} }
if strEncoded := string(encoded); strEncoded != s.expected { if strEncoded := string(encoded); strEncoded != s.expected {
t.Errorf("(%s) Expected \n%v \ngot \n%v", s.name, s.expected, strEncoded) t.Errorf("[%s] Expected \n%v \ngot \n%v", s.name, s.expected, strEncoded)
} }
} }
} }
@ -286,7 +331,7 @@ func TestSetOptions(t *testing.T) {
for _, s := range scenarios { for _, s := range scenarios {
if err := s.collection.SetOptions(s.options); err != nil { if err := s.collection.SetOptions(s.options); err != nil {
t.Errorf("(%s) Unexpected error %v", s.name, err) t.Errorf("[%s] Unexpected error %v", s.name, err)
continue continue
} }
@ -296,7 +341,7 @@ func TestSetOptions(t *testing.T) {
} }
if strEncoded := string(encoded); strEncoded != s.expected { if strEncoded := string(encoded); strEncoded != s.expected {
t.Errorf("(%s) Expected \n%v \ngot \n%v", s.name, s.expected, strEncoded) t.Errorf("[%s] Expected \n%v \ngot \n%v", s.name, s.expected, strEncoded)
} }
} }
} }
@ -378,18 +423,61 @@ func TestCollectionAuthOptionsValidate(t *testing.T) {
// parse errors // parse errors
errs, ok := result.(validation.Errors) errs, ok := result.(validation.Errors)
if !ok && result != nil { if !ok && result != nil {
t.Errorf("(%s) Failed to parse errors %v", s.name, result) t.Errorf("[%s] Failed to parse errors %v", s.name, result)
continue continue
} }
if len(errs) != len(s.expectedErrors) { if len(errs) != len(s.expectedErrors) {
t.Errorf("(%s) Expected error keys %v, got errors \n%v", s.name, s.expectedErrors, result) t.Errorf("[%s] Expected error keys %v, got errors \n%v", s.name, s.expectedErrors, result)
continue continue
} }
for key := range errs { for key := range errs {
if !list.ExistInSlice(key, s.expectedErrors) { if !list.ExistInSlice(key, s.expectedErrors) {
t.Errorf("(%s) Unexpected error key %q in \n%v", s.name, key, errs) t.Errorf("[%s] Unexpected error key %q in \n%v", s.name, key, errs)
}
}
}
}
func TestCollectionViewOptionsValidate(t *testing.T) {
scenarios := []struct {
name string
options models.CollectionViewOptions
expectedErrors []string
}{
{
"empty",
models.CollectionViewOptions{},
[]string{"query"},
},
{
"valid data",
models.CollectionViewOptions{
Query: "test123",
},
[]string{},
},
}
for _, s := range scenarios {
result := s.options.Validate()
// parse errors
errs, ok := result.(validation.Errors)
if !ok && result != nil {
t.Errorf("[%s] Failed to parse errors %v", s.name, result)
continue
}
if len(errs) != len(s.expectedErrors) {
t.Errorf("[%s] Expected error keys %v, got errors \n%v", s.name, s.expectedErrors, result)
continue
}
for key := range errs {
if !list.ExistInSlice(key, s.expectedErrors) {
t.Errorf("[%s] Unexpected error key %q in \n%v", s.name, key, errs)
} }
} }
} }

View File

@ -59,6 +59,9 @@ func nullStringMapValue(data dbx.NullStringMap, key string) any {
// NewRecordFromNullStringMap initializes a single new Record model // NewRecordFromNullStringMap initializes a single new Record model
// with data loaded from the provided NullStringMap. // with data loaded from the provided NullStringMap.
//
// Note that this method is intended to load and Scan data from a database
// result and calls PostScan() which marks the record as "not new".
func NewRecordFromNullStringMap(collection *Collection, data dbx.NullStringMap) *Record { func NewRecordFromNullStringMap(collection *Collection, data dbx.NullStringMap) *Record {
resultMap := make(map[string]any, len(data)) resultMap := make(map[string]any, len(data))
@ -89,6 +92,9 @@ func NewRecordFromNullStringMap(collection *Collection, data dbx.NullStringMap)
// NewRecordsFromNullStringMaps initializes a new Record model for // NewRecordsFromNullStringMaps initializes a new Record model for
// each row in the provided NullStringMap slice. // each row in the provided NullStringMap slice.
//
// Note that this method is intended to load and Scan data from a database
// result and calls PostScan() for each record marking them as "not new".
func NewRecordsFromNullStringMaps(collection *Collection, rows []dbx.NullStringMap) []*Record { func NewRecordsFromNullStringMaps(collection *Collection, rows []dbx.NullStringMap) []*Record {
result := make([]*Record, len(rows)) result := make([]*Record, len(rows))
@ -469,8 +475,12 @@ func (m *Record) PublicExport() map[string]any {
// export base model fields // export base model fields
result[schema.FieldNameId] = m.GetId() result[schema.FieldNameId] = m.GetId()
result[schema.FieldNameCreated] = m.GetCreated() if created := m.GetCreated(); !m.Collection().IsView() || !created.IsZero() {
result[schema.FieldNameUpdated] = m.GetUpdated() result[schema.FieldNameCreated] = created
}
if updated := m.GetUpdated(); !m.Collection().IsView() || !updated.IsZero() {
result[schema.FieldNameUpdated] = updated
}
// add helper collection reference fields // add helper collection reference fields
result[schema.FieldNameCollectionId] = m.collection.Id result[schema.FieldNameCollectionId] = m.collection.Id

View File

@ -139,7 +139,7 @@ type SchemaField struct {
func (f *SchemaField) ColDefinition() string { func (f *SchemaField) ColDefinition() string {
switch f.Type { switch f.Type {
case FieldTypeNumber: case FieldTypeNumber:
return "REAL DEFAULT 0" return "NUMERIC DEFAULT 0"
case FieldTypeBool: case FieldTypeBool:
return "BOOLEAN DEFAULT FALSE" return "BOOLEAN DEFAULT FALSE"
case FieldTypeJson: case FieldTypeJson:

View File

@ -67,7 +67,7 @@ func TestSchemaFieldColDefinition(t *testing.T) {
}, },
{ {
schema.SchemaField{Type: schema.FieldTypeNumber, Name: "test"}, schema.SchemaField{Type: schema.FieldTypeNumber, Name: "test"},
"REAL DEFAULT 0", "NUMERIC DEFAULT 0",
}, },
{ {
schema.SchemaField{Type: schema.FieldTypeBool, Name: "test"}, schema.SchemaField{Type: schema.FieldTypeBool, Name: "test"},

15
models/table_info.go Normal file
View File

@ -0,0 +1,15 @@
package models
import "github.com/pocketbase/pocketbase/tools/types"
type TableInfoRow struct {
// the `db:"pk"` tag has special semantic so we cannot rename
// the original field without specifying a custom mapper
PK int
Index int `db:"cid"`
Name string `db:"name"`
Type string `db:"type"`
NotNull bool `db:"notnull"`
DefaultValue types.JsonRaw `db:"dflt_value"`
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1 @@
{"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/png","user.metadata":{"original-filename":"300_UhLKX91HVb.png"},"md5":"zZhZjzVvCvpcxtMAJie3GQ=="}

View File

@ -0,0 +1 @@
{"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/png","user.metadata":null,"md5":"R7TqfvF8HP3C4+FO2eZ9tg=="}

View File

@ -0,0 +1,9 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="25.536" y="13.4861" width="1.71467" height="16.7338" transform="rotate(45.9772 25.536 13.4861)" fill="white"/>
<path d="M26 14H36.8C37.4628 14 38 14.5373 38 15.2V36.8C38 37.4628 37.4628 38 36.8 38H15.2C14.5373 38 14 37.4628 14 36.8V26" fill="white"/>
<path d="M26 14H36.8C37.4628 14 38 14.5373 38 15.2V36.8C38 37.4628 37.4628 38 36.8 38H15.2C14.5373 38 14 37.4628 14 36.8V26" stroke="#16161a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M26 14V3.2C26 2.53726 25.4628 2 24.8 2H3.2C2.53726 2 2 2.53726 2 3.2V24.8C2 25.4628 2.53726 26 3.2 26H14" fill="white"/>
<path d="M26 14V3.2C26 2.53726 25.4628 2 24.8 2H3.2C2.53726 2 2 2.53726 2 3.2V24.8C2 25.4628 2.53726 26 3.2 26H14" stroke="#16161a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10 20C9.44772 20 9 19.5523 9 19V8C9 7.44772 9.44772 7 10 7H13.7531C14.4801 7 15.1591 7.07311 15.7901 7.21932C16.4348 7.35225 16.9904 7.58487 17.4568 7.91718C17.9369 8.2362 18.3141 8.6682 18.5885 9.21319C18.8628 9.74489 19 10.4029 19 11.1871C19 11.9448 18.856 12.6028 18.5679 13.161C18.2936 13.7193 17.9163 14.1779 17.4362 14.5368C16.9561 14.8957 16.4005 15.1616 15.7695 15.3344C15.1385 15.5072 14.4664 15.5936 13.7531 15.5936H13.0247C12.4724 15.5936 12.0247 16.0413 12.0247 16.5936V19C12.0247 19.5523 11.577 20 11.0247 20H10ZM12.0247 12.2607C12.0247 12.813 12.4724 13.2607 13.0247 13.2607H13.5679C15.214 13.2607 16.037 12.5695 16.037 11.1871C16.037 10.5092 15.8244 10.0307 15.3992 9.75153C14.9877 9.47239 14.3772 9.33282 13.5679 9.33282H13.0247C12.4724 9.33282 12.0247 9.78054 12.0247 10.3328V12.2607Z" fill="#16161a"/>
<path d="M22 33C21.4477 33 21 32.5523 21 32V21C21 20.4477 21.4477 20 22 20H25.4877C26.1844 20 26.8265 20.0532 27.4139 20.1595C28.015 20.2526 28.5342 20.4254 28.9713 20.6779C29.4085 20.9305 29.75 21.2628 29.9959 21.6748C30.2555 22.0869 30.3852 22.6053 30.3852 23.2301C30.3852 23.5225 30.3374 23.8149 30.2418 24.1074C30.1598 24.3998 30.0232 24.6723 29.832 24.9248C29.6407 25.1774 29.4016 25.4034 29.1148 25.6028C28.837 25.7958 28.5081 25.939 28.1279 26.0323C28.1058 26.0378 28.0902 26.0575 28.0902 26.0802V26.0802C28.0902 26.1039 28.1073 26.1242 28.1306 26.1286C29.0669 26.3034 29.7774 26.6332 30.2623 27.1181C30.7541 27.6099 31 28.2945 31 29.1718C31 29.8364 30.8702 30.408 30.6107 30.8865C30.3511 31.365 29.9891 31.7638 29.5246 32.0828C29.0601 32.3885 28.5137 32.6212 27.8852 32.7807C27.2705 32.9269 26.6011 33 25.8771 33H22ZM24.0123 24.2239C24.0123 24.7762 24.46 25.2239 25.0123 25.2239H25.3443C26.082 25.2239 26.6148 25.0844 26.9426 24.8052C27.2705 24.5261 27.4344 24.1339 27.4344 23.6288C27.4344 23.1503 27.2637 22.8113 26.9221 22.612C26.5943 22.3993 26.0751 22.2929 25.3648 22.2929H25.0123C24.46 22.2929 24.0123 22.7407 24.0123 23.2929V24.2239ZM24.0123 29.7071C24.0123 30.2593 24.46 30.7071 25.0123 30.7071H25.6311C27.2432 30.7071 28.0492 30.1222 28.0492 28.9525C28.0492 28.3809 27.8511 27.9688 27.4549 27.7163C27.0724 27.4637 26.4645 27.3374 25.6311 27.3374H25.0123C24.46 27.3374 24.0123 27.7851 24.0123 28.3374V29.7071Z" fill="#16161a"/>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -0,0 +1 @@
{"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/svg+xml","user.metadata":{"original-filename":"logo_vcfJJG5TAh.svg"},"md5":"9/B7afas4c3O6vbFcbpOug=="}

View File

@ -34,8 +34,9 @@ func NewFromBytes(b []byte) *Tokenizer {
// New creates new Tokenizer from the provided reader with DefaultSeparators. // New creates new Tokenizer from the provided reader with DefaultSeparators.
func New(r io.Reader) *Tokenizer { func New(r io.Reader) *Tokenizer {
return &Tokenizer{ return &Tokenizer{
r: bufio.NewReader(r), r: bufio.NewReader(r),
separators: DefaultSeparators, separators: DefaultSeparators,
keepSeparator: false,
} }
} }
@ -44,14 +45,21 @@ func New(r io.Reader) *Tokenizer {
type Tokenizer struct { type Tokenizer struct {
r *bufio.Reader r *bufio.Reader
separators []rune separators []rune
keepSeparator bool
} }
// SetSeparators specifies the provided separatos of the current Tokenizer. // Separators defines the provided separatos of the current Tokenizer.
func (s *Tokenizer) SetSeparators(separators ...rune) { func (s *Tokenizer) Separators(separators ...rune) {
s.separators = separators s.separators = separators
} }
// KeepSeparator defines whether to keep the separator rune as part
// of the token (default to false).
func (s *Tokenizer) KeepSeparator(state bool) {
s.keepSeparator = state
}
// Scan reads and returns the next available token from the Tokenizer's buffer (trimmed). // Scan reads and returns the next available token from the Tokenizer's buffer (trimmed).
// //
// Returns [io.EOF] error when there are no more tokens to scan. // Returns [io.EOF] error when there are no more tokens to scan.
@ -128,6 +136,9 @@ func (s *Tokenizer) readToken() (string, error) {
} }
if s.isSeperatorRune(ch) && parenthesis == 0 && quoteCh == eof { if s.isSeperatorRune(ch) && parenthesis == 0 && quoteCh == eof {
if s.keepSeparator {
buf.WriteRune(ch)
}
break break
} }

View File

@ -34,6 +34,10 @@ func TestFactories(t *testing.T) {
t.Fatalf("[%s] Expected reader with content %q, got %q", s.name, expectedContent, content) t.Fatalf("[%s] Expected reader with content %q, got %q", s.name, expectedContent, content)
} }
if s.tk.keepSeparator != false {
t.Fatalf("[%s] Expected false, got true", s.name)
}
if len(s.tk.separators) != len(DefaultSeparators) { if len(s.tk.separators) != len(DefaultSeparators) {
t.Fatalf("[%s] Expected \n%v, \ngot \n%v", s.name, DefaultSeparators, s.tk.separators) t.Fatalf("[%s] Expected \n%v, \ngot \n%v", s.name, DefaultSeparators, s.tk.separators)
} }
@ -81,23 +85,26 @@ func TestScan(t *testing.T) {
func TestScanAll(t *testing.T) { func TestScanAll(t *testing.T) {
scenarios := []struct { scenarios := []struct {
name string name string
content string content string
separators []rune separators []rune
expectError bool keepSeparator bool
expectTokens []string expectError bool
expectTokens []string
}{ }{
{ {
"empty string", "empty string",
"", "",
DefaultSeparators, DefaultSeparators,
false, false,
false,
nil, nil,
}, },
{ {
"unbalanced parenthesis", "unbalanced parenthesis",
`(a,b() c`, `(a,b() c`,
DefaultSeparators, DefaultSeparators,
false,
true, true,
[]string{}, []string{},
}, },
@ -105,6 +112,7 @@ func TestScanAll(t *testing.T) {
"unmatching quotes", "unmatching quotes",
`'asd"`, `'asd"`,
DefaultSeparators, DefaultSeparators,
false,
true, true,
[]string{}, []string{},
}, },
@ -113,6 +121,7 @@ func TestScanAll(t *testing.T) {
`a, b, c, d, e 123, "abc"`, `a, b, c, d, e 123, "abc"`,
nil, nil,
false, false,
false,
[]string{ []string{
`a, b, c, d, e 123, "abc"`, `a, b, c, d, e 123, "abc"`,
}, },
@ -122,6 +131,7 @@ func TestScanAll(t *testing.T) {
`a, b, c, d e, "a,b, c ", (123, 456)`, `a, b, c, d e, "a,b, c ", (123, 456)`,
DefaultSeparators, DefaultSeparators,
false, false,
false,
[]string{ []string{
"a", "a",
"b", "b",
@ -131,6 +141,21 @@ func TestScanAll(t *testing.T) {
`(123, 456)`, `(123, 456)`,
}, },
}, },
{
"default separators (with preserve)",
`a, b, c, d e, "a,b, c ", (123, 456)`,
DefaultSeparators,
true,
false,
[]string{
"a,",
"b,",
"c,",
"d e,",
`"a,b, c ",`,
`(123, 456)`,
},
},
{ {
"custom separators", "custom separators",
` a , 123.456, b, c d, ( ` a , 123.456, b, c d, (
@ -138,6 +163,7 @@ func TestScanAll(t *testing.T) {
),"(abc d", "abc) d", "(abc) d \" " 'abc "'`, ),"(abc d", "abc) d", "(abc) d \" " 'abc "'`,
[]rune{',', ' ', '\t', '\n'}, []rune{',', ' ', '\t', '\n'},
false, false,
false,
[]string{ []string{
"a", "a",
"123.456", "123.456",
@ -151,12 +177,34 @@ func TestScanAll(t *testing.T) {
`'abc "'`, `'abc "'`,
}, },
}, },
{
"custom separators (with preserve)",
` a , 123.456, b, c d, (
test (a,b,c) " 123 "
),"(abc d", "abc) d", "(abc) d \" " 'abc "'`,
[]rune{',', ' ', '\t', '\n'},
true,
false,
[]string{
"a ",
"123.456,",
"b,",
"c ",
"d,",
"(\n\t\t\t\ttest (a,b,c) \" 123 \"\n\t\t\t),",
`"(abc d",`,
`"abc) d",`,
`"(abc) d \" " `,
`'abc "'`,
},
},
} }
for _, s := range scenarios { for _, s := range scenarios {
tk := NewFromString(s.content) tk := NewFromString(s.content)
tk.SetSeparators(s.separators...) tk.Separators(s.separators...)
tk.KeepSeparator(s.keepSeparator)
tokens, err := tk.ScanAll() tokens, err := tk.ScanAll()

View File

@ -8,4 +8,4 @@ PB_DOCS_URL = "https://pocketbase.io/docs/"
PB_JS_SDK_URL = "https://github.com/pocketbase/js-sdk" PB_JS_SDK_URL = "https://github.com/pocketbase/js-sdk"
PB_DART_SDK_URL = "https://github.com/pocketbase/dart-sdk" PB_DART_SDK_URL = "https://github.com/pocketbase/dart-sdk"
PB_RELEASES = "https://github.com/pocketbase/pocketbase/releases" PB_RELEASES = "https://github.com/pocketbase/pocketbase/releases"
PB_VERSION = "v0.12.3" PB_VERSION = "v0.13.0"

View File

@ -1,4 +1,4 @@
import{S as ke,i as be,s as ge,e as r,w as b,b as g,c as me,f as k,g as h,h as n,m as _e,x as G,O as re,P as we,k as ve,Q as Ce,n as Pe,t as L,a as Y,o as m,d as pe,R as Me,C as Se,p as $e,r as H,u as je,N as Ae}from"./index-3e0f12d8.js";import{S as Be}from"./SdkTabs-ed893501.js";function ue(a,l,o){const s=a.slice();return s[5]=l[o],s}function de(a,l,o){const s=a.slice();return s[5]=l[o],s}function fe(a,l){let o,s=l[5].code+"",_,f,i,u;function d(){return l[4](l[5])}return{key:a,first:null,c(){o=r("button"),_=b(s),f=g(),k(o,"class","tab-item"),H(o,"active",l[1]===l[5].code),this.first=o},m(v,C){h(v,o,C),n(o,_),n(o,f),i||(u=je(o,"click",d),i=!0)},p(v,C){l=v,C&4&&s!==(s=l[5].code+"")&&G(_,s),C&6&&H(o,"active",l[1]===l[5].code)},d(v){v&&m(o),i=!1,u()}}}function he(a,l){let o,s,_,f;return s=new Ae({props:{content:l[5].body}}),{key:a,first:null,c(){o=r("div"),me(s.$$.fragment),_=g(),k(o,"class","tab-item"),H(o,"active",l[1]===l[5].code),this.first=o},m(i,u){h(i,o,u),_e(s,o,null),n(o,_),f=!0},p(i,u){l=i;const d={};u&4&&(d.content=l[5].body),s.$set(d),(!f||u&6)&&H(o,"active",l[1]===l[5].code)},i(i){f||(L(s.$$.fragment,i),f=!0)},o(i){Y(s.$$.fragment,i),f=!1},d(i){i&&m(o),pe(s)}}}function Oe(a){var ae,ne;let l,o,s=a[0].name+"",_,f,i,u,d,v,C,F=a[0].name+"",U,R,q,P,D,j,W,M,K,X,Q,A,Z,V,y=a[0].name+"",E,x,I,B,J,S,O,w=[],ee=new Map,te,T,p=[],le=new Map,$;P=new Be({props:{js:` import{S as ke,i as be,s as ge,e as r,w as b,b as g,c as me,f as k,g as h,h as n,m as _e,x as G,O as re,P as we,k as ve,Q as Ce,n as Pe,t as L,a as Y,o as m,d as pe,R as Me,C as Se,p as $e,r as H,u as je,N as Ae}from"./index-ffbb9561.js";import{S as Be}from"./SdkTabs-5b973c0d.js";function ue(a,l,o){const s=a.slice();return s[5]=l[o],s}function de(a,l,o){const s=a.slice();return s[5]=l[o],s}function fe(a,l){let o,s=l[5].code+"",_,f,i,u;function d(){return l[4](l[5])}return{key:a,first:null,c(){o=r("button"),_=b(s),f=g(),k(o,"class","tab-item"),H(o,"active",l[1]===l[5].code),this.first=o},m(v,C){h(v,o,C),n(o,_),n(o,f),i||(u=je(o,"click",d),i=!0)},p(v,C){l=v,C&4&&s!==(s=l[5].code+"")&&G(_,s),C&6&&H(o,"active",l[1]===l[5].code)},d(v){v&&m(o),i=!1,u()}}}function he(a,l){let o,s,_,f;return s=new Ae({props:{content:l[5].body}}),{key:a,first:null,c(){o=r("div"),me(s.$$.fragment),_=g(),k(o,"class","tab-item"),H(o,"active",l[1]===l[5].code),this.first=o},m(i,u){h(i,o,u),_e(s,o,null),n(o,_),f=!0},p(i,u){l=i;const d={};u&4&&(d.content=l[5].body),s.$set(d),(!f||u&6)&&H(o,"active",l[1]===l[5].code)},i(i){f||(L(s.$$.fragment,i),f=!0)},o(i){Y(s.$$.fragment,i),f=!1},d(i){i&&m(o),pe(s)}}}function Oe(a){var ae,ne;let l,o,s=a[0].name+"",_,f,i,u,d,v,C,F=a[0].name+"",U,R,q,P,D,j,W,M,K,X,Q,A,Z,V,y=a[0].name+"",E,x,I,B,J,S,O,w=[],ee=new Map,te,T,p=[],le=new Map,$;P=new Be({props:{js:`
import PocketBase from 'pocketbase'; import PocketBase from 'pocketbase';
const pb = new PocketBase('${a[3]}'); const pb = new PocketBase('${a[3]}');

View File

@ -1,4 +1,4 @@
import{S as ze,i as Ue,s as je,N as Ve,e as a,w as k,b as p,c as ae,f as b,g as c,h as o,m as ne,x as re,O as qe,P as xe,k as Je,Q as Ke,n as Qe,t as U,a as j,o as d,d as ie,R as Ie,C as He,p as We,r as x,u as Ge}from"./index-3e0f12d8.js";import{S as Xe}from"./SdkTabs-ed893501.js";function Ee(r,l,s){const n=r.slice();return n[5]=l[s],n}function Fe(r,l,s){const n=r.slice();return n[5]=l[s],n}function Le(r,l){let s,n=l[5].code+"",m,_,i,f;function v(){return l[4](l[5])}return{key:r,first:null,c(){s=a("button"),m=k(n),_=p(),b(s,"class","tab-item"),x(s,"active",l[1]===l[5].code),this.first=s},m(g,w){c(g,s,w),o(s,m),o(s,_),i||(f=Ge(s,"click",v),i=!0)},p(g,w){l=g,w&4&&n!==(n=l[5].code+"")&&re(m,n),w&6&&x(s,"active",l[1]===l[5].code)},d(g){g&&d(s),i=!1,f()}}}function Ne(r,l){let s,n,m,_;return n=new Ve({props:{content:l[5].body}}),{key:r,first:null,c(){s=a("div"),ae(n.$$.fragment),m=p(),b(s,"class","tab-item"),x(s,"active",l[1]===l[5].code),this.first=s},m(i,f){c(i,s,f),ne(n,s,null),o(s,m),_=!0},p(i,f){l=i;const v={};f&4&&(v.content=l[5].body),n.$set(v),(!_||f&6)&&x(s,"active",l[1]===l[5].code)},i(i){_||(U(n.$$.fragment,i),_=!0)},o(i){j(n.$$.fragment,i),_=!1},d(i){i&&d(s),ie(n)}}}function Ye(r){var Be,Me;let l,s,n=r[0].name+"",m,_,i,f,v,g,w,B,J,S,F,ce,L,M,de,K,N=r[0].name+"",Q,ue,pe,V,I,D,W,T,G,fe,X,C,Y,he,Z,be,h,me,P,_e,ke,ve,ee,ge,te,ye,Se,$e,oe,we,le,O,se,R,q,$=[],Te=new Map,Ce,H,y=[],Re=new Map,A;g=new Xe({props:{js:` import{S as ze,i as Ue,s as je,N as Ve,e as a,w as k,b as p,c as ae,f as b,g as c,h as o,m as ne,x as re,O as qe,P as xe,k as Je,Q as Ke,n as Qe,t as U,a as j,o as d,d as ie,R as Ie,C as He,p as We,r as x,u as Ge}from"./index-ffbb9561.js";import{S as Xe}from"./SdkTabs-5b973c0d.js";function Ee(r,l,s){const n=r.slice();return n[5]=l[s],n}function Fe(r,l,s){const n=r.slice();return n[5]=l[s],n}function Le(r,l){let s,n=l[5].code+"",m,_,i,f;function v(){return l[4](l[5])}return{key:r,first:null,c(){s=a("button"),m=k(n),_=p(),b(s,"class","tab-item"),x(s,"active",l[1]===l[5].code),this.first=s},m(g,w){c(g,s,w),o(s,m),o(s,_),i||(f=Ge(s,"click",v),i=!0)},p(g,w){l=g,w&4&&n!==(n=l[5].code+"")&&re(m,n),w&6&&x(s,"active",l[1]===l[5].code)},d(g){g&&d(s),i=!1,f()}}}function Ne(r,l){let s,n,m,_;return n=new Ve({props:{content:l[5].body}}),{key:r,first:null,c(){s=a("div"),ae(n.$$.fragment),m=p(),b(s,"class","tab-item"),x(s,"active",l[1]===l[5].code),this.first=s},m(i,f){c(i,s,f),ne(n,s,null),o(s,m),_=!0},p(i,f){l=i;const v={};f&4&&(v.content=l[5].body),n.$set(v),(!_||f&6)&&x(s,"active",l[1]===l[5].code)},i(i){_||(U(n.$$.fragment,i),_=!0)},o(i){j(n.$$.fragment,i),_=!1},d(i){i&&d(s),ie(n)}}}function Ye(r){var Be,Me;let l,s,n=r[0].name+"",m,_,i,f,v,g,w,B,J,S,F,ce,L,M,de,K,N=r[0].name+"",Q,ue,pe,V,I,D,W,T,G,fe,X,C,Y,he,Z,be,h,me,P,_e,ke,ve,ee,ge,te,ye,Se,$e,oe,we,le,O,se,R,q,$=[],Te=new Map,Ce,H,y=[],Re=new Map,A;g=new Xe({props:{js:`
import PocketBase from 'pocketbase'; import PocketBase from 'pocketbase';
const pb = new PocketBase('${r[3]}'); const pb = new PocketBase('${r[3]}');

View File

@ -1,4 +1,4 @@
import{S as je,i as He,s as Je,N as We,e as s,w as v,b as p,c as re,f as h,g as r,h as a,m as ce,x as de,O as Ve,P as Ne,k as Qe,Q as ze,n as Ke,t as j,a as H,o as c,d as ue,R as Ye,C as Be,p as Ge,r as J,u as Xe}from"./index-3e0f12d8.js";import{S as Ze}from"./SdkTabs-ed893501.js";function Fe(i,l,o){const n=i.slice();return n[5]=l[o],n}function Le(i,l,o){const n=i.slice();return n[5]=l[o],n}function xe(i,l){let o,n=l[5].code+"",m,_,d,b;function g(){return l[4](l[5])}return{key:i,first:null,c(){o=s("button"),m=v(n),_=p(),h(o,"class","tab-item"),J(o,"active",l[1]===l[5].code),this.first=o},m(k,R){r(k,o,R),a(o,m),a(o,_),d||(b=Xe(o,"click",g),d=!0)},p(k,R){l=k,R&4&&n!==(n=l[5].code+"")&&de(m,n),R&6&&J(o,"active",l[1]===l[5].code)},d(k){k&&c(o),d=!1,b()}}}function Me(i,l){let o,n,m,_;return n=new We({props:{content:l[5].body}}),{key:i,first:null,c(){o=s("div"),re(n.$$.fragment),m=p(),h(o,"class","tab-item"),J(o,"active",l[1]===l[5].code),this.first=o},m(d,b){r(d,o,b),ce(n,o,null),a(o,m),_=!0},p(d,b){l=d;const g={};b&4&&(g.content=l[5].body),n.$set(g),(!_||b&6)&&J(o,"active",l[1]===l[5].code)},i(d){_||(j(n.$$.fragment,d),_=!0)},o(d){H(n.$$.fragment,d),_=!1},d(d){d&&c(o),ue(n)}}}function et(i){var qe,Ie;let l,o,n=i[0].name+"",m,_,d,b,g,k,R,C,N,y,L,pe,x,D,he,Q,M=i[0].name+"",z,be,K,q,Y,I,G,P,X,O,Z,fe,ee,$,te,me,ae,_e,f,ve,E,ge,ke,we,le,Se,oe,Re,ye,Oe,se,$e,ne,U,ie,A,V,S=[],Ae=new Map,Ee,B,w=[],Te=new Map,T;k=new Ze({props:{js:` import{S as je,i as He,s as Je,N as We,e as s,w as v,b as p,c as re,f as h,g as r,h as a,m as ce,x as de,O as Ve,P as Ne,k as Qe,Q as ze,n as Ke,t as j,a as H,o as c,d as ue,R as Ye,C as Be,p as Ge,r as J,u as Xe}from"./index-ffbb9561.js";import{S as Ze}from"./SdkTabs-5b973c0d.js";function Fe(i,l,o){const n=i.slice();return n[5]=l[o],n}function Le(i,l,o){const n=i.slice();return n[5]=l[o],n}function xe(i,l){let o,n=l[5].code+"",m,_,d,b;function g(){return l[4](l[5])}return{key:i,first:null,c(){o=s("button"),m=v(n),_=p(),h(o,"class","tab-item"),J(o,"active",l[1]===l[5].code),this.first=o},m(k,R){r(k,o,R),a(o,m),a(o,_),d||(b=Xe(o,"click",g),d=!0)},p(k,R){l=k,R&4&&n!==(n=l[5].code+"")&&de(m,n),R&6&&J(o,"active",l[1]===l[5].code)},d(k){k&&c(o),d=!1,b()}}}function Me(i,l){let o,n,m,_;return n=new We({props:{content:l[5].body}}),{key:i,first:null,c(){o=s("div"),re(n.$$.fragment),m=p(),h(o,"class","tab-item"),J(o,"active",l[1]===l[5].code),this.first=o},m(d,b){r(d,o,b),ce(n,o,null),a(o,m),_=!0},p(d,b){l=d;const g={};b&4&&(g.content=l[5].body),n.$set(g),(!_||b&6)&&J(o,"active",l[1]===l[5].code)},i(d){_||(j(n.$$.fragment,d),_=!0)},o(d){H(n.$$.fragment,d),_=!1},d(d){d&&c(o),ue(n)}}}function et(i){var qe,Ie;let l,o,n=i[0].name+"",m,_,d,b,g,k,R,C,N,y,L,pe,x,D,he,Q,M=i[0].name+"",z,be,K,q,Y,I,G,P,X,O,Z,fe,ee,$,te,me,ae,_e,f,ve,E,ge,ke,we,le,Se,oe,Re,ye,Oe,se,$e,ne,U,ie,A,V,S=[],Ae=new Map,Ee,B,w=[],Te=new Map,T;k=new Ze({props:{js:`
import PocketBase from 'pocketbase'; import PocketBase from 'pocketbase';
const pb = new PocketBase('${i[3]}'); const pb = new PocketBase('${i[3]}');

View File

@ -1,4 +1,4 @@
import{S as Se,i as ve,s as we,N as ke,e as s,w as f,b as u,c as Ot,f as h,g as r,h as o,m as At,x as Tt,O as ce,P as ye,k as ge,Q as Pe,n as Re,t as tt,a as et,o as c,d as Ut,R as $e,C as de,p as Ce,r as lt,u as Oe}from"./index-3e0f12d8.js";import{S as Ae}from"./SdkTabs-ed893501.js";function ue(n,e,l){const i=n.slice();return i[8]=e[l],i}function fe(n,e,l){const i=n.slice();return i[8]=e[l],i}function Te(n){let e;return{c(){e=f("email")},m(l,i){r(l,e,i)},d(l){l&&c(e)}}}function Ue(n){let e;return{c(){e=f("username")},m(l,i){r(l,e,i)},d(l){l&&c(e)}}}function Me(n){let e;return{c(){e=f("username/email")},m(l,i){r(l,e,i)},d(l){l&&c(e)}}}function pe(n){let e;return{c(){e=s("strong"),e.textContent="username"},m(l,i){r(l,e,i)},d(l){l&&c(e)}}}function be(n){let e;return{c(){e=f("or")},m(l,i){r(l,e,i)},d(l){l&&c(e)}}}function me(n){let e;return{c(){e=s("strong"),e.textContent="email"},m(l,i){r(l,e,i)},d(l){l&&c(e)}}}function he(n,e){let l,i=e[8].code+"",S,m,p,d;function _(){return e[7](e[8])}return{key:n,first:null,c(){l=s("button"),S=f(i),m=u(),h(l,"class","tab-item"),lt(l,"active",e[3]===e[8].code),this.first=l},m($,C){r($,l,C),o(l,S),o(l,m),p||(d=Oe(l,"click",_),p=!0)},p($,C){e=$,C&16&&i!==(i=e[8].code+"")&&Tt(S,i),C&24&&lt(l,"active",e[3]===e[8].code)},d($){$&&c(l),p=!1,d()}}}function _e(n,e){let l,i,S,m;return i=new ke({props:{content:e[8].body}}),{key:n,first:null,c(){l=s("div"),Ot(i.$$.fragment),S=u(),h(l,"class","tab-item"),lt(l,"active",e[3]===e[8].code),this.first=l},m(p,d){r(p,l,d),At(i,l,null),o(l,S),m=!0},p(p,d){e=p;const _={};d&16&&(_.content=e[8].body),i.$set(_),(!m||d&24)&&lt(l,"active",e[3]===e[8].code)},i(p){m||(tt(i.$$.fragment,p),m=!0)},o(p){et(i.$$.fragment,p),m=!1},d(p){p&&c(l),Ut(i)}}}function De(n){var se,ne;let e,l,i=n[0].name+"",S,m,p,d,_,$,C,O,B,Mt,ot,T,at,F,st,U,G,Dt,X,N,Et,nt,Z=n[0].name+"",it,Wt,rt,I,ct,M,dt,Lt,V,D,ut,Bt,ft,Ht,g,Yt,pt,bt,mt,qt,ht,_t,j,kt,E,St,Ft,vt,W,wt,Nt,yt,It,k,Vt,H,jt,Jt,Qt,gt,Kt,Pt,zt,Gt,Xt,Rt,Zt,$t,J,Ct,L,Q,A=[],xt=new Map,te,K,P=[],ee=new Map,Y;function le(t,a){if(t[1]&&t[2])return Me;if(t[1])return Ue;if(t[2])return Te}let q=le(n),R=q&&q(n);T=new Ae({props:{js:` import{S as Se,i as ve,s as we,N as ke,e as s,w as f,b as u,c as Ot,f as h,g as r,h as o,m as At,x as Tt,O as ce,P as ye,k as ge,Q as Pe,n as Re,t as tt,a as et,o as c,d as Ut,R as $e,C as de,p as Ce,r as lt,u as Oe}from"./index-ffbb9561.js";import{S as Ae}from"./SdkTabs-5b973c0d.js";function ue(n,e,l){const i=n.slice();return i[8]=e[l],i}function fe(n,e,l){const i=n.slice();return i[8]=e[l],i}function Te(n){let e;return{c(){e=f("email")},m(l,i){r(l,e,i)},d(l){l&&c(e)}}}function Ue(n){let e;return{c(){e=f("username")},m(l,i){r(l,e,i)},d(l){l&&c(e)}}}function Me(n){let e;return{c(){e=f("username/email")},m(l,i){r(l,e,i)},d(l){l&&c(e)}}}function pe(n){let e;return{c(){e=s("strong"),e.textContent="username"},m(l,i){r(l,e,i)},d(l){l&&c(e)}}}function be(n){let e;return{c(){e=f("or")},m(l,i){r(l,e,i)},d(l){l&&c(e)}}}function me(n){let e;return{c(){e=s("strong"),e.textContent="email"},m(l,i){r(l,e,i)},d(l){l&&c(e)}}}function he(n,e){let l,i=e[8].code+"",S,m,p,d;function _(){return e[7](e[8])}return{key:n,first:null,c(){l=s("button"),S=f(i),m=u(),h(l,"class","tab-item"),lt(l,"active",e[3]===e[8].code),this.first=l},m($,C){r($,l,C),o(l,S),o(l,m),p||(d=Oe(l,"click",_),p=!0)},p($,C){e=$,C&16&&i!==(i=e[8].code+"")&&Tt(S,i),C&24&&lt(l,"active",e[3]===e[8].code)},d($){$&&c(l),p=!1,d()}}}function _e(n,e){let l,i,S,m;return i=new ke({props:{content:e[8].body}}),{key:n,first:null,c(){l=s("div"),Ot(i.$$.fragment),S=u(),h(l,"class","tab-item"),lt(l,"active",e[3]===e[8].code),this.first=l},m(p,d){r(p,l,d),At(i,l,null),o(l,S),m=!0},p(p,d){e=p;const _={};d&16&&(_.content=e[8].body),i.$set(_),(!m||d&24)&&lt(l,"active",e[3]===e[8].code)},i(p){m||(tt(i.$$.fragment,p),m=!0)},o(p){et(i.$$.fragment,p),m=!1},d(p){p&&c(l),Ut(i)}}}function De(n){var se,ne;let e,l,i=n[0].name+"",S,m,p,d,_,$,C,O,B,Mt,ot,T,at,F,st,U,G,Dt,X,N,Et,nt,Z=n[0].name+"",it,Wt,rt,I,ct,M,dt,Lt,V,D,ut,Bt,ft,Ht,g,Yt,pt,bt,mt,qt,ht,_t,j,kt,E,St,Ft,vt,W,wt,Nt,yt,It,k,Vt,H,jt,Jt,Qt,gt,Kt,Pt,zt,Gt,Xt,Rt,Zt,$t,J,Ct,L,Q,A=[],xt=new Map,te,K,P=[],ee=new Map,Y;function le(t,a){if(t[1]&&t[2])return Me;if(t[1])return Ue;if(t[2])return Te}let q=le(n),R=q&&q(n);T=new Ae({props:{js:`
import PocketBase from 'pocketbase'; import PocketBase from 'pocketbase';
const pb = new PocketBase('${n[6]}'); const pb = new PocketBase('${n[6]}');

14
ui/dist/assets/CodeEditor-4caff52a.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
import{S as Ce,i as $e,s as we,e as c,w as v,b as h,c as he,f as b,g as r,h as n,m as ve,x as Y,O as pe,P as Pe,k as Se,Q as Oe,n as Re,t as Z,a as x,o as f,d as ge,R as Te,C as Ee,p as ye,r as j,u as Be,N as qe}from"./index-3e0f12d8.js";import{S as Ae}from"./SdkTabs-ed893501.js";function ue(o,l,s){const a=o.slice();return a[5]=l[s],a}function be(o,l,s){const a=o.slice();return a[5]=l[s],a}function _e(o,l){let s,a=l[5].code+"",_,u,i,d;function p(){return l[4](l[5])}return{key:o,first:null,c(){s=c("button"),_=v(a),u=h(),b(s,"class","tab-item"),j(s,"active",l[1]===l[5].code),this.first=s},m(C,$){r(C,s,$),n(s,_),n(s,u),i||(d=Be(s,"click",p),i=!0)},p(C,$){l=C,$&4&&a!==(a=l[5].code+"")&&Y(_,a),$&6&&j(s,"active",l[1]===l[5].code)},d(C){C&&f(s),i=!1,d()}}}function ke(o,l){let s,a,_,u;return a=new qe({props:{content:l[5].body}}),{key:o,first:null,c(){s=c("div"),he(a.$$.fragment),_=h(),b(s,"class","tab-item"),j(s,"active",l[1]===l[5].code),this.first=s},m(i,d){r(i,s,d),ve(a,s,null),n(s,_),u=!0},p(i,d){l=i;const p={};d&4&&(p.content=l[5].body),a.$set(p),(!u||d&6)&&j(s,"active",l[1]===l[5].code)},i(i){u||(Z(a.$$.fragment,i),u=!0)},o(i){x(a.$$.fragment,i),u=!1},d(i){i&&f(s),ge(a)}}}function Ue(o){var re,fe;let l,s,a=o[0].name+"",_,u,i,d,p,C,$,D=o[0].name+"",H,ee,F,w,I,R,L,P,N,te,K,T,le,Q,M=o[0].name+"",z,se,G,E,J,y,V,B,X,S,q,g=[],ae=new Map,oe,A,k=[],ne=new Map,O;w=new Ae({props:{js:` import{S as Ce,i as $e,s as we,e as c,w as v,b as h,c as he,f as b,g as r,h as n,m as ve,x as Y,O as pe,P as Pe,k as Se,Q as Oe,n as Re,t as Z,a as x,o as f,d as ge,R as Te,C as Ee,p as ye,r as j,u as Be,N as qe}from"./index-ffbb9561.js";import{S as Ae}from"./SdkTabs-5b973c0d.js";function ue(o,l,s){const a=o.slice();return a[5]=l[s],a}function be(o,l,s){const a=o.slice();return a[5]=l[s],a}function _e(o,l){let s,a=l[5].code+"",_,u,i,d;function p(){return l[4](l[5])}return{key:o,first:null,c(){s=c("button"),_=v(a),u=h(),b(s,"class","tab-item"),j(s,"active",l[1]===l[5].code),this.first=s},m(C,$){r(C,s,$),n(s,_),n(s,u),i||(d=Be(s,"click",p),i=!0)},p(C,$){l=C,$&4&&a!==(a=l[5].code+"")&&Y(_,a),$&6&&j(s,"active",l[1]===l[5].code)},d(C){C&&f(s),i=!1,d()}}}function ke(o,l){let s,a,_,u;return a=new qe({props:{content:l[5].body}}),{key:o,first:null,c(){s=c("div"),he(a.$$.fragment),_=h(),b(s,"class","tab-item"),j(s,"active",l[1]===l[5].code),this.first=s},m(i,d){r(i,s,d),ve(a,s,null),n(s,_),u=!0},p(i,d){l=i;const p={};d&4&&(p.content=l[5].body),a.$set(p),(!u||d&6)&&j(s,"active",l[1]===l[5].code)},i(i){u||(Z(a.$$.fragment,i),u=!0)},o(i){x(a.$$.fragment,i),u=!1},d(i){i&&f(s),ge(a)}}}function Ue(o){var re,fe;let l,s,a=o[0].name+"",_,u,i,d,p,C,$,D=o[0].name+"",H,ee,F,w,I,R,L,P,N,te,K,T,le,Q,M=o[0].name+"",z,se,G,E,J,y,V,B,X,S,q,g=[],ae=new Map,oe,A,k=[],ne=new Map,O;w=new Ae({props:{js:`
import PocketBase from 'pocketbase'; import PocketBase from 'pocketbase';
const pb = new PocketBase('${o[3]}'); const pb = new PocketBase('${o[3]}');

View File

@ -1,4 +1,4 @@
import{S as Se,i as he,s as Re,e as c,w,b as v,c as ve,f as b,g as r,h as n,m as we,x as K,O as me,P as Oe,k as Ne,Q as Ce,n as We,t as Z,a as x,o as d,d as Pe,R as $e,C as Ee,p as Te,r as U,u as ge,N as Ae}from"./index-3e0f12d8.js";import{S as De}from"./SdkTabs-ed893501.js";function ue(o,s,l){const a=o.slice();return a[5]=s[l],a}function be(o,s,l){const a=o.slice();return a[5]=s[l],a}function _e(o,s){let l,a=s[5].code+"",_,u,i,p;function m(){return s[4](s[5])}return{key:o,first:null,c(){l=c("button"),_=w(a),u=v(),b(l,"class","tab-item"),U(l,"active",s[1]===s[5].code),this.first=l},m(S,h){r(S,l,h),n(l,_),n(l,u),i||(p=ge(l,"click",m),i=!0)},p(S,h){s=S,h&4&&a!==(a=s[5].code+"")&&K(_,a),h&6&&U(l,"active",s[1]===s[5].code)},d(S){S&&d(l),i=!1,p()}}}function ke(o,s){let l,a,_,u;return a=new Ae({props:{content:s[5].body}}),{key:o,first:null,c(){l=c("div"),ve(a.$$.fragment),_=v(),b(l,"class","tab-item"),U(l,"active",s[1]===s[5].code),this.first=l},m(i,p){r(i,l,p),we(a,l,null),n(l,_),u=!0},p(i,p){s=i;const m={};p&4&&(m.content=s[5].body),a.$set(m),(!u||p&6)&&U(l,"active",s[1]===s[5].code)},i(i){u||(Z(a.$$.fragment,i),u=!0)},o(i){x(a.$$.fragment,i),u=!1},d(i){i&&d(l),Pe(a)}}}function ye(o){var re,de;let s,l,a=o[0].name+"",_,u,i,p,m,S,h,q=o[0].name+"",j,ee,H,R,L,W,Q,O,B,te,M,$,se,z,F=o[0].name+"",G,le,J,E,V,T,X,g,Y,N,A,P=[],ae=new Map,oe,D,k=[],ne=new Map,C;R=new De({props:{js:` import{S as Se,i as he,s as Re,e as c,w,b as v,c as ve,f as b,g as r,h as n,m as we,x as K,O as me,P as Oe,k as Ne,Q as Ce,n as We,t as Z,a as x,o as d,d as Pe,R as $e,C as Ee,p as Te,r as U,u as ge,N as Ae}from"./index-ffbb9561.js";import{S as De}from"./SdkTabs-5b973c0d.js";function ue(o,s,l){const a=o.slice();return a[5]=s[l],a}function be(o,s,l){const a=o.slice();return a[5]=s[l],a}function _e(o,s){let l,a=s[5].code+"",_,u,i,p;function m(){return s[4](s[5])}return{key:o,first:null,c(){l=c("button"),_=w(a),u=v(),b(l,"class","tab-item"),U(l,"active",s[1]===s[5].code),this.first=l},m(S,h){r(S,l,h),n(l,_),n(l,u),i||(p=ge(l,"click",m),i=!0)},p(S,h){s=S,h&4&&a!==(a=s[5].code+"")&&K(_,a),h&6&&U(l,"active",s[1]===s[5].code)},d(S){S&&d(l),i=!1,p()}}}function ke(o,s){let l,a,_,u;return a=new Ae({props:{content:s[5].body}}),{key:o,first:null,c(){l=c("div"),ve(a.$$.fragment),_=v(),b(l,"class","tab-item"),U(l,"active",s[1]===s[5].code),this.first=l},m(i,p){r(i,l,p),we(a,l,null),n(l,_),u=!0},p(i,p){s=i;const m={};p&4&&(m.content=s[5].body),a.$set(m),(!u||p&6)&&U(l,"active",s[1]===s[5].code)},i(i){u||(Z(a.$$.fragment,i),u=!0)},o(i){x(a.$$.fragment,i),u=!1},d(i){i&&d(l),Pe(a)}}}function ye(o){var re,de;let s,l,a=o[0].name+"",_,u,i,p,m,S,h,q=o[0].name+"",j,ee,H,R,L,W,Q,O,B,te,M,$,se,z,F=o[0].name+"",G,le,J,E,V,T,X,g,Y,N,A,P=[],ae=new Map,oe,D,k=[],ne=new Map,C;R=new De({props:{js:`
import PocketBase from 'pocketbase'; import PocketBase from 'pocketbase';
const pb = new PocketBase('${o[3]}'); const pb = new PocketBase('${o[3]}');

View File

@ -1,4 +1,4 @@
import{S as we,i as Ce,s as Pe,e as c,w as h,b as v,c as ve,f as b,g as r,h as n,m as he,x as D,O as de,P as Te,k as ge,Q as ye,n as Be,t as Z,a as x,o as f,d as $e,R as qe,C as Oe,p as Se,r as H,u as Ee,N as Ne}from"./index-3e0f12d8.js";import{S as Ve}from"./SdkTabs-ed893501.js";function ue(i,l,s){const o=i.slice();return o[5]=l[s],o}function be(i,l,s){const o=i.slice();return o[5]=l[s],o}function _e(i,l){let s,o=l[5].code+"",_,u,a,p;function d(){return l[4](l[5])}return{key:i,first:null,c(){s=c("button"),_=h(o),u=v(),b(s,"class","tab-item"),H(s,"active",l[1]===l[5].code),this.first=s},m(w,C){r(w,s,C),n(s,_),n(s,u),a||(p=Ee(s,"click",d),a=!0)},p(w,C){l=w,C&4&&o!==(o=l[5].code+"")&&D(_,o),C&6&&H(s,"active",l[1]===l[5].code)},d(w){w&&f(s),a=!1,p()}}}function ke(i,l){let s,o,_,u;return o=new Ne({props:{content:l[5].body}}),{key:i,first:null,c(){s=c("div"),ve(o.$$.fragment),_=v(),b(s,"class","tab-item"),H(s,"active",l[1]===l[5].code),this.first=s},m(a,p){r(a,s,p),he(o,s,null),n(s,_),u=!0},p(a,p){l=a;const d={};p&4&&(d.content=l[5].body),o.$set(d),(!u||p&6)&&H(s,"active",l[1]===l[5].code)},i(a){u||(Z(o.$$.fragment,a),u=!0)},o(a){x(o.$$.fragment,a),u=!1},d(a){a&&f(s),$e(o)}}}function Ke(i){var re,fe;let l,s,o=i[0].name+"",_,u,a,p,d,w,C,M=i[0].name+"",F,ee,I,P,L,B,Q,T,A,te,R,q,le,z,U=i[0].name+"",G,se,J,O,W,S,X,E,Y,g,N,$=[],oe=new Map,ie,V,k=[],ne=new Map,y;P=new Ve({props:{js:` import{S as we,i as Ce,s as Pe,e as c,w as h,b as v,c as ve,f as b,g as r,h as n,m as he,x as D,O as de,P as Te,k as ge,Q as ye,n as Be,t as Z,a as x,o as f,d as $e,R as qe,C as Oe,p as Se,r as H,u as Ee,N as Ne}from"./index-ffbb9561.js";import{S as Ve}from"./SdkTabs-5b973c0d.js";function ue(i,l,s){const o=i.slice();return o[5]=l[s],o}function be(i,l,s){const o=i.slice();return o[5]=l[s],o}function _e(i,l){let s,o=l[5].code+"",_,u,a,p;function d(){return l[4](l[5])}return{key:i,first:null,c(){s=c("button"),_=h(o),u=v(),b(s,"class","tab-item"),H(s,"active",l[1]===l[5].code),this.first=s},m(w,C){r(w,s,C),n(s,_),n(s,u),a||(p=Ee(s,"click",d),a=!0)},p(w,C){l=w,C&4&&o!==(o=l[5].code+"")&&D(_,o),C&6&&H(s,"active",l[1]===l[5].code)},d(w){w&&f(s),a=!1,p()}}}function ke(i,l){let s,o,_,u;return o=new Ne({props:{content:l[5].body}}),{key:i,first:null,c(){s=c("div"),ve(o.$$.fragment),_=v(),b(s,"class","tab-item"),H(s,"active",l[1]===l[5].code),this.first=s},m(a,p){r(a,s,p),he(o,s,null),n(s,_),u=!0},p(a,p){l=a;const d={};p&4&&(d.content=l[5].body),o.$set(d),(!u||p&6)&&H(s,"active",l[1]===l[5].code)},i(a){u||(Z(o.$$.fragment,a),u=!0)},o(a){x(o.$$.fragment,a),u=!1},d(a){a&&f(s),$e(o)}}}function Ke(i){var re,fe;let l,s,o=i[0].name+"",_,u,a,p,d,w,C,M=i[0].name+"",F,ee,I,P,L,B,Q,T,A,te,R,q,le,z,U=i[0].name+"",G,se,J,O,W,S,X,E,Y,g,N,$=[],oe=new Map,ie,V,k=[],ne=new Map,y;P=new Ve({props:{js:`
import PocketBase from 'pocketbase'; import PocketBase from 'pocketbase';
const pb = new PocketBase('${i[3]}'); const pb = new PocketBase('${i[3]}');

View File

@ -1,4 +1,4 @@
import{S as Ht,i as Lt,s as Pt,C as Q,N as At,e as a,w as k,b as m,c as Pe,f as h,g as r,h as n,m as Re,x,O as Le,P as ht,k as Rt,Q as Bt,n as Ft,t as fe,a as pe,o as d,d as Be,R as gt,p as jt,r as ue,u as Dt,y as le}from"./index-3e0f12d8.js";import{S as Nt}from"./SdkTabs-ed893501.js";function wt(o,e,l){const s=o.slice();return s[7]=e[l],s}function Ct(o,e,l){const s=o.slice();return s[7]=e[l],s}function St(o,e,l){const s=o.slice();return s[12]=e[l],s}function $t(o){let e;return{c(){e=a("p"),e.innerHTML="Requires admin <code>Authorization:TOKEN</code> header",h(e,"class","txt-hint txt-sm txt-right")},m(l,s){r(l,e,s)},d(l){l&&d(e)}}}function Tt(o){let e,l,s,b,p,c,f,y,T,w,O,g,D,V,L,J,j,B,S,N,q,C,_;function M(u,$){var ee,K;return(K=(ee=u[0])==null?void 0:ee.options)!=null&&K.requireEmail?Jt:Vt}let z=M(o),P=z(o);return{c(){e=a("tr"),e.innerHTML='<td colspan="3" class="txt-hint">Auth fields</td>',l=m(),s=a("tr"),s.innerHTML=`<td><div class="inline-flex"><span class="label label-warning">Optional</span> import{S as Ht,i as Lt,s as Pt,C as Q,N as At,e as a,w as k,b as m,c as Pe,f as h,g as r,h as n,m as Re,x,O as Le,P as ht,k as Rt,Q as Bt,n as Ft,t as fe,a as pe,o as d,d as Be,R as gt,p as jt,r as ue,u as Dt,y as le}from"./index-ffbb9561.js";import{S as Nt}from"./SdkTabs-5b973c0d.js";function wt(o,e,l){const s=o.slice();return s[7]=e[l],s}function Ct(o,e,l){const s=o.slice();return s[7]=e[l],s}function St(o,e,l){const s=o.slice();return s[12]=e[l],s}function $t(o){let e;return{c(){e=a("p"),e.innerHTML="Requires admin <code>Authorization:TOKEN</code> header",h(e,"class","txt-hint txt-sm txt-right")},m(l,s){r(l,e,s)},d(l){l&&d(e)}}}function Tt(o){let e,l,s,b,p,c,f,y,T,w,O,g,D,V,L,J,j,B,S,N,q,C,_;function M(u,$){var ee,K;return(K=(ee=u[0])==null?void 0:ee.options)!=null&&K.requireEmail?Jt:Vt}let z=M(o),P=z(o);return{c(){e=a("tr"),e.innerHTML='<td colspan="3" class="txt-hint">Auth fields</td>',l=m(),s=a("tr"),s.innerHTML=`<td><div class="inline-flex"><span class="label label-warning">Optional</span>
<span>username</span></div></td> <span>username</span></div></td>
<td><span class="label">String</span></td> <td><span class="label">String</span></td>
<td>The username of the auth record. <td>The username of the auth record.

View File

@ -1,4 +1,4 @@
import{S as Ce,i as Re,s as Pe,e as c,w as D,b as k,c as $e,f as m,g as d,h as n,m as we,x,O as _e,P as Ee,k as Oe,Q as Te,n as Be,t as ee,a as te,o as f,d as ge,R as Ie,C as Ae,p as Me,r as N,u as Se,N as qe}from"./index-3e0f12d8.js";import{S as He}from"./SdkTabs-ed893501.js";function ke(o,l,s){const a=o.slice();return a[6]=l[s],a}function he(o,l,s){const a=o.slice();return a[6]=l[s],a}function ve(o){let l;return{c(){l=c("p"),l.innerHTML="Requires admin <code>Authorization:TOKEN</code> header",m(l,"class","txt-hint txt-sm txt-right")},m(s,a){d(s,l,a)},d(s){s&&f(l)}}}function ye(o,l){let s,a=l[6].code+"",h,i,r,u;function $(){return l[5](l[6])}return{key:o,first:null,c(){s=c("button"),h=D(a),i=k(),m(s,"class","tab-item"),N(s,"active",l[2]===l[6].code),this.first=s},m(b,g){d(b,s,g),n(s,h),n(s,i),r||(u=Se(s,"click",$),r=!0)},p(b,g){l=b,g&20&&N(s,"active",l[2]===l[6].code)},d(b){b&&f(s),r=!1,u()}}}function De(o,l){let s,a,h,i;return a=new qe({props:{content:l[6].body}}),{key:o,first:null,c(){s=c("div"),$e(a.$$.fragment),h=k(),m(s,"class","tab-item"),N(s,"active",l[2]===l[6].code),this.first=s},m(r,u){d(r,s,u),we(a,s,null),n(s,h),i=!0},p(r,u){l=r,(!i||u&20)&&N(s,"active",l[2]===l[6].code)},i(r){i||(ee(a.$$.fragment,r),i=!0)},o(r){te(a.$$.fragment,r),i=!1},d(r){r&&f(s),ge(a)}}}function Le(o){var ue,pe;let l,s,a=o[0].name+"",h,i,r,u,$,b,g,q=o[0].name+"",z,le,F,C,K,O,Q,y,H,se,L,E,oe,G,U=o[0].name+"",J,ae,V,ne,W,T,X,B,Y,I,Z,R,A,w=[],ie=new Map,re,M,v=[],ce=new Map,P;C=new He({props:{js:` import{S as Ce,i as Re,s as Pe,e as c,w as D,b as k,c as $e,f as m,g as d,h as n,m as we,x,O as _e,P as Ee,k as Oe,Q as Te,n as Be,t as ee,a as te,o as f,d as ge,R as Ie,C as Ae,p as Me,r as N,u as Se,N as qe}from"./index-ffbb9561.js";import{S as He}from"./SdkTabs-5b973c0d.js";function ke(o,l,s){const a=o.slice();return a[6]=l[s],a}function he(o,l,s){const a=o.slice();return a[6]=l[s],a}function ve(o){let l;return{c(){l=c("p"),l.innerHTML="Requires admin <code>Authorization:TOKEN</code> header",m(l,"class","txt-hint txt-sm txt-right")},m(s,a){d(s,l,a)},d(s){s&&f(l)}}}function ye(o,l){let s,a=l[6].code+"",h,i,r,u;function $(){return l[5](l[6])}return{key:o,first:null,c(){s=c("button"),h=D(a),i=k(),m(s,"class","tab-item"),N(s,"active",l[2]===l[6].code),this.first=s},m(b,g){d(b,s,g),n(s,h),n(s,i),r||(u=Se(s,"click",$),r=!0)},p(b,g){l=b,g&20&&N(s,"active",l[2]===l[6].code)},d(b){b&&f(s),r=!1,u()}}}function De(o,l){let s,a,h,i;return a=new qe({props:{content:l[6].body}}),{key:o,first:null,c(){s=c("div"),$e(a.$$.fragment),h=k(),m(s,"class","tab-item"),N(s,"active",l[2]===l[6].code),this.first=s},m(r,u){d(r,s,u),we(a,s,null),n(s,h),i=!0},p(r,u){l=r,(!i||u&20)&&N(s,"active",l[2]===l[6].code)},i(r){i||(ee(a.$$.fragment,r),i=!0)},o(r){te(a.$$.fragment,r),i=!1},d(r){r&&f(s),ge(a)}}}function Le(o){var ue,pe;let l,s,a=o[0].name+"",h,i,r,u,$,b,g,q=o[0].name+"",z,le,F,C,K,O,Q,y,H,se,L,E,oe,G,U=o[0].name+"",J,ae,V,ne,W,T,X,B,Y,I,Z,R,A,w=[],ie=new Map,re,M,v=[],ce=new Map,P;C=new He({props:{js:`
import PocketBase from 'pocketbase'; import PocketBase from 'pocketbase';
const pb = new PocketBase('${o[3]}'); const pb = new PocketBase('${o[3]}');

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
import{S as Se,i as Ne,s as qe,e,b as s,E as He,f as o,g as u,u as De,y as Fe,o as m,w as _,h as t,N as he,c as Yt,m as Zt,x as we,O as Le,P as Me,k as Be,Q as Ie,n as Ge,t as Bt,a as It,d as te,R as ze,C as _e,p as Ue,r as xe}from"./index-3e0f12d8.js";import{S as je}from"./SdkTabs-ed893501.js";function Qe(d){let n,a,r;return{c(){n=e("span"),n.textContent="Show details",a=s(),r=e("i"),o(n,"class","txt"),o(r,"class","ri-arrow-down-s-line")},m(f,p){u(f,n,p),u(f,a,p),u(f,r,p)},d(f){f&&m(n),f&&m(a),f&&m(r)}}}function Je(d){let n,a,r;return{c(){n=e("span"),n.textContent="Hide details",a=s(),r=e("i"),o(n,"class","txt"),o(r,"class","ri-arrow-up-s-line")},m(f,p){u(f,n,p),u(f,a,p),u(f,r,p)},d(f){f&&m(n),f&&m(a),f&&m(r)}}}function Ae(d){let n,a,r,f,p,b,x,$,h,w,c,V,bt,Gt,R,zt,q,it,F,W,ee,I,G,le,at,ht,X,xt,se,rt,ct,Y,O,Ut,wt,y,Z,_t,jt,$t,z,tt,Ct,Qt,kt,L,dt,gt,ne,ft,oe,D,vt,et,yt,U,pt,ie,H,Ft,lt,Lt,st,At,nt,j,E,Jt,Tt,Kt,Pt,C,Q,M,ae,Rt,re,ut,ce,B,Ot,de,Et,Vt,St,Wt,A,mt,J,K,S,Nt,fe,T,k,pe,N,v,ot,ue,P,qt,me,Dt,be,Ht,Xt,Mt;return{c(){n=e("p"),n.innerHTML=`The syntax basically follows the format import{S as Se,i as Ne,s as qe,e,b as s,E as He,f as o,g as u,u as De,y as Fe,o as m,w as _,h as t,N as he,c as Yt,m as Zt,x as we,O as Le,P as Me,k as Be,Q as Ie,n as Ge,t as Bt,a as It,d as te,R as ze,C as _e,p as Ue,r as xe}from"./index-ffbb9561.js";import{S as je}from"./SdkTabs-5b973c0d.js";function Qe(d){let n,a,r;return{c(){n=e("span"),n.textContent="Show details",a=s(),r=e("i"),o(n,"class","txt"),o(r,"class","ri-arrow-down-s-line")},m(f,p){u(f,n,p),u(f,a,p),u(f,r,p)},d(f){f&&m(n),f&&m(a),f&&m(r)}}}function Je(d){let n,a,r;return{c(){n=e("span"),n.textContent="Hide details",a=s(),r=e("i"),o(n,"class","txt"),o(r,"class","ri-arrow-up-s-line")},m(f,p){u(f,n,p),u(f,a,p),u(f,r,p)},d(f){f&&m(n),f&&m(a),f&&m(r)}}}function Ae(d){let n,a,r,f,p,b,x,$,h,w,c,V,bt,Gt,R,zt,q,it,F,W,ee,I,G,le,at,ht,X,xt,se,rt,ct,Y,O,Ut,wt,y,Z,_t,jt,$t,z,tt,Ct,Qt,kt,L,dt,gt,ne,ft,oe,D,vt,et,yt,U,pt,ie,H,Ft,lt,Lt,st,At,nt,j,E,Jt,Tt,Kt,Pt,C,Q,M,ae,Rt,re,ut,ce,B,Ot,de,Et,Vt,St,Wt,A,mt,J,K,S,Nt,fe,T,k,pe,N,v,ot,ue,P,qt,me,Dt,be,Ht,Xt,Mt;return{c(){n=e("p"),n.innerHTML=`The syntax basically follows the format
<code><span class="txt-success">OPERAND</span> <code><span class="txt-success">OPERAND</span>
<span class="txt-danger">OPERATOR</span> <span class="txt-danger">OPERATOR</span>
<span class="txt-success">OPERAND</span></code>, where:`,a=s(),r=e("ul"),f=e("li"),f.innerHTML=`<code class="txt-success">OPERAND</code> - could be any of the above field literal, string (single <span class="txt-success">OPERAND</span></code>, where:`,a=s(),r=e("ul"),f=e("li"),f.innerHTML=`<code class="txt-success">OPERAND</code> - could be any of the above field literal, string (single

View File

@ -1,4 +1,4 @@
import{S as Be,i as qe,s as Oe,e as i,w as v,b as _,c as Se,f as b,g as r,h as s,m as Ee,x as U,O as Pe,P as Le,k as Me,Q as Re,n as We,t as te,a as le,o as d,d as Ie,R as ze,C as De,p as He,r as j,u as Ue,N as je}from"./index-3e0f12d8.js";import{S as Ne}from"./SdkTabs-ed893501.js";function ye(a,l,o){const n=a.slice();return n[5]=l[o],n}function Ae(a,l,o){const n=a.slice();return n[5]=l[o],n}function Ce(a,l){let o,n=l[5].code+"",f,h,c,u;function m(){return l[4](l[5])}return{key:a,first:null,c(){o=i("button"),f=v(n),h=_(),b(o,"class","tab-item"),j(o,"active",l[1]===l[5].code),this.first=o},m(g,P){r(g,o,P),s(o,f),s(o,h),c||(u=Ue(o,"click",m),c=!0)},p(g,P){l=g,P&4&&n!==(n=l[5].code+"")&&U(f,n),P&6&&j(o,"active",l[1]===l[5].code)},d(g){g&&d(o),c=!1,u()}}}function Te(a,l){let o,n,f,h;return n=new je({props:{content:l[5].body}}),{key:a,first:null,c(){o=i("div"),Se(n.$$.fragment),f=_(),b(o,"class","tab-item"),j(o,"active",l[1]===l[5].code),this.first=o},m(c,u){r(c,o,u),Ee(n,o,null),s(o,f),h=!0},p(c,u){l=c;const m={};u&4&&(m.content=l[5].body),n.$set(m),(!h||u&6)&&j(o,"active",l[1]===l[5].code)},i(c){h||(te(n.$$.fragment,c),h=!0)},o(c){le(n.$$.fragment,c),h=!1},d(c){c&&d(o),Ie(n)}}}function Ge(a){var be,he,_e,ke;let l,o,n=a[0].name+"",f,h,c,u,m,g,P,M=a[0].name+"",N,oe,se,G,K,y,Q,S,F,w,R,ae,W,A,ne,J,z=a[0].name+"",V,ie,X,ce,re,D,Y,E,Z,I,x,B,ee,C,q,$=[],de=new Map,ue,O,k=[],pe=new Map,T;y=new Ne({props:{js:` import{S as Be,i as qe,s as Oe,e as i,w as v,b as _,c as Se,f as b,g as r,h as s,m as Ee,x as U,O as Pe,P as Le,k as Me,Q as Re,n as We,t as te,a as le,o as d,d as Ie,R as ze,C as De,p as He,r as j,u as Ue,N as je}from"./index-ffbb9561.js";import{S as Ne}from"./SdkTabs-5b973c0d.js";function ye(a,l,o){const n=a.slice();return n[5]=l[o],n}function Ae(a,l,o){const n=a.slice();return n[5]=l[o],n}function Ce(a,l){let o,n=l[5].code+"",f,h,c,u;function m(){return l[4](l[5])}return{key:a,first:null,c(){o=i("button"),f=v(n),h=_(),b(o,"class","tab-item"),j(o,"active",l[1]===l[5].code),this.first=o},m(g,P){r(g,o,P),s(o,f),s(o,h),c||(u=Ue(o,"click",m),c=!0)},p(g,P){l=g,P&4&&n!==(n=l[5].code+"")&&U(f,n),P&6&&j(o,"active",l[1]===l[5].code)},d(g){g&&d(o),c=!1,u()}}}function Te(a,l){let o,n,f,h;return n=new je({props:{content:l[5].body}}),{key:a,first:null,c(){o=i("div"),Se(n.$$.fragment),f=_(),b(o,"class","tab-item"),j(o,"active",l[1]===l[5].code),this.first=o},m(c,u){r(c,o,u),Ee(n,o,null),s(o,f),h=!0},p(c,u){l=c;const m={};u&4&&(m.content=l[5].body),n.$set(m),(!h||u&6)&&j(o,"active",l[1]===l[5].code)},i(c){h||(te(n.$$.fragment,c),h=!0)},o(c){le(n.$$.fragment,c),h=!1},d(c){c&&d(o),Ie(n)}}}function Ge(a){var be,he,_e,ke;let l,o,n=a[0].name+"",f,h,c,u,m,g,P,M=a[0].name+"",N,oe,se,G,K,y,Q,S,F,w,R,ae,W,A,ne,J,z=a[0].name+"",V,ie,X,ce,re,D,Y,E,Z,I,x,B,ee,C,q,$=[],de=new Map,ue,O,k=[],pe=new Map,T;y=new Ne({props:{js:`
import PocketBase from 'pocketbase'; import PocketBase from 'pocketbase';
const pb = new PocketBase('${a[3]}'); const pb = new PocketBase('${a[3]}');

View File

@ -1,2 +1,2 @@
import{S as E,i as G,s as I,F as K,c as A,m as B,t as H,a as N,d as T,C as M,q as J,e as c,w as q,b as C,f as u,r as L,g as b,h as _,u as h,v as O,j as Q,l as U,o as w,A as V,p as W,B as X,D as Y,x as Z,z as S}from"./index-3e0f12d8.js";function y(f){let e,o,s;return{c(){e=q("for "),o=c("strong"),s=q(f[3]),u(o,"class","txt-nowrap")},m(l,t){b(l,e,t),b(l,o,t),_(o,s)},p(l,t){t&8&&Z(s,l[3])},d(l){l&&w(e),l&&w(o)}}}function x(f){let e,o,s,l,t,r,p,d;return{c(){e=c("label"),o=q("New password"),l=C(),t=c("input"),u(e,"for",s=f[8]),u(t,"type","password"),u(t,"id",r=f[8]),t.required=!0,t.autofocus=!0},m(n,i){b(n,e,i),_(e,o),b(n,l,i),b(n,t,i),S(t,f[0]),t.focus(),p||(d=h(t,"input",f[6]),p=!0)},p(n,i){i&256&&s!==(s=n[8])&&u(e,"for",s),i&256&&r!==(r=n[8])&&u(t,"id",r),i&1&&t.value!==n[0]&&S(t,n[0])},d(n){n&&w(e),n&&w(l),n&&w(t),p=!1,d()}}}function ee(f){let e,o,s,l,t,r,p,d;return{c(){e=c("label"),o=q("New password confirm"),l=C(),t=c("input"),u(e,"for",s=f[8]),u(t,"type","password"),u(t,"id",r=f[8]),t.required=!0},m(n,i){b(n,e,i),_(e,o),b(n,l,i),b(n,t,i),S(t,f[1]),p||(d=h(t,"input",f[7]),p=!0)},p(n,i){i&256&&s!==(s=n[8])&&u(e,"for",s),i&256&&r!==(r=n[8])&&u(t,"id",r),i&2&&t.value!==n[1]&&S(t,n[1])},d(n){n&&w(e),n&&w(l),n&&w(t),p=!1,d()}}}function te(f){let e,o,s,l,t,r,p,d,n,i,g,R,P,v,k,F,j,m=f[3]&&y(f);return r=new J({props:{class:"form-field required",name:"password",$$slots:{default:[x,({uniqueId:a})=>({8:a}),({uniqueId:a})=>a?256:0]},$$scope:{ctx:f}}}),d=new J({props:{class:"form-field required",name:"passwordConfirm",$$slots:{default:[ee,({uniqueId:a})=>({8:a}),({uniqueId:a})=>a?256:0]},$$scope:{ctx:f}}}),{c(){e=c("form"),o=c("div"),s=c("h4"),l=q(`Reset your admin password import{S as E,i as G,s as I,F as K,c as A,m as B,t as H,a as N,d as T,C as M,q as J,e as c,w as q,b as C,f as u,r as L,g as b,h as _,u as h,v as O,j as Q,l as U,o as w,A as V,p as W,B as X,D as Y,x as Z,z as S}from"./index-ffbb9561.js";function y(f){let e,o,s;return{c(){e=q("for "),o=c("strong"),s=q(f[3]),u(o,"class","txt-nowrap")},m(l,t){b(l,e,t),b(l,o,t),_(o,s)},p(l,t){t&8&&Z(s,l[3])},d(l){l&&w(e),l&&w(o)}}}function x(f){let e,o,s,l,t,r,p,d;return{c(){e=c("label"),o=q("New password"),l=C(),t=c("input"),u(e,"for",s=f[8]),u(t,"type","password"),u(t,"id",r=f[8]),t.required=!0,t.autofocus=!0},m(n,i){b(n,e,i),_(e,o),b(n,l,i),b(n,t,i),S(t,f[0]),t.focus(),p||(d=h(t,"input",f[6]),p=!0)},p(n,i){i&256&&s!==(s=n[8])&&u(e,"for",s),i&256&&r!==(r=n[8])&&u(t,"id",r),i&1&&t.value!==n[0]&&S(t,n[0])},d(n){n&&w(e),n&&w(l),n&&w(t),p=!1,d()}}}function ee(f){let e,o,s,l,t,r,p,d;return{c(){e=c("label"),o=q("New password confirm"),l=C(),t=c("input"),u(e,"for",s=f[8]),u(t,"type","password"),u(t,"id",r=f[8]),t.required=!0},m(n,i){b(n,e,i),_(e,o),b(n,l,i),b(n,t,i),S(t,f[1]),p||(d=h(t,"input",f[7]),p=!0)},p(n,i){i&256&&s!==(s=n[8])&&u(e,"for",s),i&256&&r!==(r=n[8])&&u(t,"id",r),i&2&&t.value!==n[1]&&S(t,n[1])},d(n){n&&w(e),n&&w(l),n&&w(t),p=!1,d()}}}function te(f){let e,o,s,l,t,r,p,d,n,i,g,R,P,v,k,F,j,m=f[3]&&y(f);return r=new J({props:{class:"form-field required",name:"password",$$slots:{default:[x,({uniqueId:a})=>({8:a}),({uniqueId:a})=>a?256:0]},$$scope:{ctx:f}}}),d=new J({props:{class:"form-field required",name:"passwordConfirm",$$slots:{default:[ee,({uniqueId:a})=>({8:a}),({uniqueId:a})=>a?256:0]},$$scope:{ctx:f}}}),{c(){e=c("form"),o=c("div"),s=c("h4"),l=q(`Reset your admin password
`),m&&m.c(),t=C(),A(r.$$.fragment),p=C(),A(d.$$.fragment),n=C(),i=c("button"),g=c("span"),g.textContent="Set new password",R=C(),P=c("div"),v=c("a"),v.textContent="Back to login",u(s,"class","m-b-xs"),u(o,"class","content txt-center m-b-sm"),u(g,"class","txt"),u(i,"type","submit"),u(i,"class","btn btn-lg btn-block"),i.disabled=f[2],L(i,"btn-loading",f[2]),u(e,"class","m-b-base"),u(v,"href","/login"),u(v,"class","link-hint"),u(P,"class","content txt-center")},m(a,$){b(a,e,$),_(e,o),_(o,s),_(s,l),m&&m.m(s,null),_(e,t),B(r,e,null),_(e,p),B(d,e,null),_(e,n),_(e,i),_(i,g),b(a,R,$),b(a,P,$),_(P,v),k=!0,F||(j=[h(e,"submit",O(f[4])),Q(U.call(null,v))],F=!0)},p(a,$){a[3]?m?m.p(a,$):(m=y(a),m.c(),m.m(s,null)):m&&(m.d(1),m=null);const z={};$&769&&(z.$$scope={dirty:$,ctx:a}),r.$set(z);const D={};$&770&&(D.$$scope={dirty:$,ctx:a}),d.$set(D),(!k||$&4)&&(i.disabled=a[2]),(!k||$&4)&&L(i,"btn-loading",a[2])},i(a){k||(H(r.$$.fragment,a),H(d.$$.fragment,a),k=!0)},o(a){N(r.$$.fragment,a),N(d.$$.fragment,a),k=!1},d(a){a&&w(e),m&&m.d(),T(r),T(d),a&&w(R),a&&w(P),F=!1,V(j)}}}function se(f){let e,o;return e=new K({props:{$$slots:{default:[te]},$$scope:{ctx:f}}}),{c(){A(e.$$.fragment)},m(s,l){B(e,s,l),o=!0},p(s,[l]){const t={};l&527&&(t.$$scope={dirty:l,ctx:s}),e.$set(t)},i(s){o||(H(e.$$.fragment,s),o=!0)},o(s){N(e.$$.fragment,s),o=!1},d(s){T(e,s)}}}function le(f,e,o){let s,{params:l}=e,t="",r="",p=!1;async function d(){if(!p){o(2,p=!0);try{await W.admins.confirmPasswordReset(l==null?void 0:l.token,t,r),X("Successfully set a new admin password."),Y("/")}catch(g){W.errorResponseHandler(g)}o(2,p=!1)}}function n(){t=this.value,o(0,t)}function i(){r=this.value,o(1,r)}return f.$$set=g=>{"params"in g&&o(5,l=g.params)},f.$$.update=()=>{f.$$.dirty&32&&o(3,s=M.getJWTPayload(l==null?void 0:l.token).email||"")},[t,r,p,s,d,l,n,i]}class ae extends E{constructor(e){super(),G(this,e,le,se,I,{params:5})}}export{ae as default}; `),m&&m.c(),t=C(),A(r.$$.fragment),p=C(),A(d.$$.fragment),n=C(),i=c("button"),g=c("span"),g.textContent="Set new password",R=C(),P=c("div"),v=c("a"),v.textContent="Back to login",u(s,"class","m-b-xs"),u(o,"class","content txt-center m-b-sm"),u(g,"class","txt"),u(i,"type","submit"),u(i,"class","btn btn-lg btn-block"),i.disabled=f[2],L(i,"btn-loading",f[2]),u(e,"class","m-b-base"),u(v,"href","/login"),u(v,"class","link-hint"),u(P,"class","content txt-center")},m(a,$){b(a,e,$),_(e,o),_(o,s),_(s,l),m&&m.m(s,null),_(e,t),B(r,e,null),_(e,p),B(d,e,null),_(e,n),_(e,i),_(i,g),b(a,R,$),b(a,P,$),_(P,v),k=!0,F||(j=[h(e,"submit",O(f[4])),Q(U.call(null,v))],F=!0)},p(a,$){a[3]?m?m.p(a,$):(m=y(a),m.c(),m.m(s,null)):m&&(m.d(1),m=null);const z={};$&769&&(z.$$scope={dirty:$,ctx:a}),r.$set(z);const D={};$&770&&(D.$$scope={dirty:$,ctx:a}),d.$set(D),(!k||$&4)&&(i.disabled=a[2]),(!k||$&4)&&L(i,"btn-loading",a[2])},i(a){k||(H(r.$$.fragment,a),H(d.$$.fragment,a),k=!0)},o(a){N(r.$$.fragment,a),N(d.$$.fragment,a),k=!1},d(a){a&&w(e),m&&m.d(),T(r),T(d),a&&w(R),a&&w(P),F=!1,V(j)}}}function se(f){let e,o;return e=new K({props:{$$slots:{default:[te]},$$scope:{ctx:f}}}),{c(){A(e.$$.fragment)},m(s,l){B(e,s,l),o=!0},p(s,[l]){const t={};l&527&&(t.$$scope={dirty:l,ctx:s}),e.$set(t)},i(s){o||(H(e.$$.fragment,s),o=!0)},o(s){N(e.$$.fragment,s),o=!1},d(s){T(e,s)}}}function le(f,e,o){let s,{params:l}=e,t="",r="",p=!1;async function d(){if(!p){o(2,p=!0);try{await W.admins.confirmPasswordReset(l==null?void 0:l.token,t,r),X("Successfully set a new admin password."),Y("/")}catch(g){W.errorResponseHandler(g)}o(2,p=!1)}}function n(){t=this.value,o(0,t)}function i(){r=this.value,o(1,r)}return f.$$set=g=>{"params"in g&&o(5,l=g.params)},f.$$.update=()=>{f.$$.dirty&32&&o(3,s=M.getJWTPayload(l==null?void 0:l.token).email||"")},[t,r,p,s,d,l,n,i]}class ae extends E{constructor(e){super(),G(this,e,le,se,I,{params:5})}}export{ae as default};

View File

@ -1,2 +1,2 @@
import{S as M,i as T,s as j,F as z,c as H,m as L,t as w,a as y,d as S,b as g,e as _,f as p,g as k,h as d,j as A,l as B,k as N,n as D,o as v,p as C,q as G,r as F,u as E,v as I,w as h,x as J,y as P,z as R}from"./index-3e0f12d8.js";function K(c){let e,s,n,l,t,o,f,m,i,a,b,u;return l=new G({props:{class:"form-field required",name:"email",$$slots:{default:[Q,({uniqueId:r})=>({5:r}),({uniqueId:r})=>r?32:0]},$$scope:{ctx:c}}}),{c(){e=_("form"),s=_("div"),s.innerHTML=`<h4 class="m-b-xs">Forgotten admin password</h4> import{S as M,i as T,s as j,F as z,c as H,m as L,t as w,a as y,d as S,b as g,e as _,f as p,g as k,h as d,j as A,l as B,k as N,n as D,o as v,p as C,q as G,r as F,u as E,v as I,w as h,x as J,y as P,z as R}from"./index-ffbb9561.js";function K(c){let e,s,n,l,t,o,f,m,i,a,b,u;return l=new G({props:{class:"form-field required",name:"email",$$slots:{default:[Q,({uniqueId:r})=>({5:r}),({uniqueId:r})=>r?32:0]},$$scope:{ctx:c}}}),{c(){e=_("form"),s=_("div"),s.innerHTML=`<h4 class="m-b-xs">Forgotten admin password</h4>
<p>Enter the email associated with your account and well send you a recovery link:</p>`,n=g(),H(l.$$.fragment),t=g(),o=_("button"),f=_("i"),m=g(),i=_("span"),i.textContent="Send recovery link",p(s,"class","content txt-center m-b-sm"),p(f,"class","ri-mail-send-line"),p(i,"class","txt"),p(o,"type","submit"),p(o,"class","btn btn-lg btn-block"),o.disabled=c[1],F(o,"btn-loading",c[1]),p(e,"class","m-b-base")},m(r,$){k(r,e,$),d(e,s),d(e,n),L(l,e,null),d(e,t),d(e,o),d(o,f),d(o,m),d(o,i),a=!0,b||(u=E(e,"submit",I(c[3])),b=!0)},p(r,$){const q={};$&97&&(q.$$scope={dirty:$,ctx:r}),l.$set(q),(!a||$&2)&&(o.disabled=r[1]),(!a||$&2)&&F(o,"btn-loading",r[1])},i(r){a||(w(l.$$.fragment,r),a=!0)},o(r){y(l.$$.fragment,r),a=!1},d(r){r&&v(e),S(l),b=!1,u()}}}function O(c){let e,s,n,l,t,o,f,m,i;return{c(){e=_("div"),s=_("div"),s.innerHTML='<i class="ri-checkbox-circle-line"></i>',n=g(),l=_("div"),t=_("p"),o=h("Check "),f=_("strong"),m=h(c[0]),i=h(" for the recovery link."),p(s,"class","icon"),p(f,"class","txt-nowrap"),p(l,"class","content"),p(e,"class","alert alert-success")},m(a,b){k(a,e,b),d(e,s),d(e,n),d(e,l),d(l,t),d(t,o),d(t,f),d(f,m),d(t,i)},p(a,b){b&1&&J(m,a[0])},i:P,o:P,d(a){a&&v(e)}}}function Q(c){let e,s,n,l,t,o,f,m;return{c(){e=_("label"),s=h("Email"),l=g(),t=_("input"),p(e,"for",n=c[5]),p(t,"type","email"),p(t,"id",o=c[5]),t.required=!0,t.autofocus=!0},m(i,a){k(i,e,a),d(e,s),k(i,l,a),k(i,t,a),R(t,c[0]),t.focus(),f||(m=E(t,"input",c[4]),f=!0)},p(i,a){a&32&&n!==(n=i[5])&&p(e,"for",n),a&32&&o!==(o=i[5])&&p(t,"id",o),a&1&&t.value!==i[0]&&R(t,i[0])},d(i){i&&v(e),i&&v(l),i&&v(t),f=!1,m()}}}function U(c){let e,s,n,l,t,o,f,m;const i=[O,K],a=[];function b(u,r){return u[2]?0:1}return e=b(c),s=a[e]=i[e](c),{c(){s.c(),n=g(),l=_("div"),t=_("a"),t.textContent="Back to login",p(t,"href","/login"),p(t,"class","link-hint"),p(l,"class","content txt-center")},m(u,r){a[e].m(u,r),k(u,n,r),k(u,l,r),d(l,t),o=!0,f||(m=A(B.call(null,t)),f=!0)},p(u,r){let $=e;e=b(u),e===$?a[e].p(u,r):(N(),y(a[$],1,1,()=>{a[$]=null}),D(),s=a[e],s?s.p(u,r):(s=a[e]=i[e](u),s.c()),w(s,1),s.m(n.parentNode,n))},i(u){o||(w(s),o=!0)},o(u){y(s),o=!1},d(u){a[e].d(u),u&&v(n),u&&v(l),f=!1,m()}}}function V(c){let e,s;return e=new z({props:{$$slots:{default:[U]},$$scope:{ctx:c}}}),{c(){H(e.$$.fragment)},m(n,l){L(e,n,l),s=!0},p(n,[l]){const t={};l&71&&(t.$$scope={dirty:l,ctx:n}),e.$set(t)},i(n){s||(w(e.$$.fragment,n),s=!0)},o(n){y(e.$$.fragment,n),s=!1},d(n){S(e,n)}}}function W(c,e,s){let n="",l=!1,t=!1;async function o(){if(!l){s(1,l=!0);try{await C.admins.requestPasswordReset(n),s(2,t=!0)}catch(m){C.errorResponseHandler(m)}s(1,l=!1)}}function f(){n=this.value,s(0,n)}return[n,l,t,o,f]}class Y extends M{constructor(e){super(),T(this,e,W,V,j,{})}}export{Y as default}; <p>Enter the email associated with your account and well send you a recovery link:</p>`,n=g(),H(l.$$.fragment),t=g(),o=_("button"),f=_("i"),m=g(),i=_("span"),i.textContent="Send recovery link",p(s,"class","content txt-center m-b-sm"),p(f,"class","ri-mail-send-line"),p(i,"class","txt"),p(o,"type","submit"),p(o,"class","btn btn-lg btn-block"),o.disabled=c[1],F(o,"btn-loading",c[1]),p(e,"class","m-b-base")},m(r,$){k(r,e,$),d(e,s),d(e,n),L(l,e,null),d(e,t),d(e,o),d(o,f),d(o,m),d(o,i),a=!0,b||(u=E(e,"submit",I(c[3])),b=!0)},p(r,$){const q={};$&97&&(q.$$scope={dirty:$,ctx:r}),l.$set(q),(!a||$&2)&&(o.disabled=r[1]),(!a||$&2)&&F(o,"btn-loading",r[1])},i(r){a||(w(l.$$.fragment,r),a=!0)},o(r){y(l.$$.fragment,r),a=!1},d(r){r&&v(e),S(l),b=!1,u()}}}function O(c){let e,s,n,l,t,o,f,m,i;return{c(){e=_("div"),s=_("div"),s.innerHTML='<i class="ri-checkbox-circle-line"></i>',n=g(),l=_("div"),t=_("p"),o=h("Check "),f=_("strong"),m=h(c[0]),i=h(" for the recovery link."),p(s,"class","icon"),p(f,"class","txt-nowrap"),p(l,"class","content"),p(e,"class","alert alert-success")},m(a,b){k(a,e,b),d(e,s),d(e,n),d(e,l),d(l,t),d(t,o),d(t,f),d(f,m),d(t,i)},p(a,b){b&1&&J(m,a[0])},i:P,o:P,d(a){a&&v(e)}}}function Q(c){let e,s,n,l,t,o,f,m;return{c(){e=_("label"),s=h("Email"),l=g(),t=_("input"),p(e,"for",n=c[5]),p(t,"type","email"),p(t,"id",o=c[5]),t.required=!0,t.autofocus=!0},m(i,a){k(i,e,a),d(e,s),k(i,l,a),k(i,t,a),R(t,c[0]),t.focus(),f||(m=E(t,"input",c[4]),f=!0)},p(i,a){a&32&&n!==(n=i[5])&&p(e,"for",n),a&32&&o!==(o=i[5])&&p(t,"id",o),a&1&&t.value!==i[0]&&R(t,i[0])},d(i){i&&v(e),i&&v(l),i&&v(t),f=!1,m()}}}function U(c){let e,s,n,l,t,o,f,m;const i=[O,K],a=[];function b(u,r){return u[2]?0:1}return e=b(c),s=a[e]=i[e](c),{c(){s.c(),n=g(),l=_("div"),t=_("a"),t.textContent="Back to login",p(t,"href","/login"),p(t,"class","link-hint"),p(l,"class","content txt-center")},m(u,r){a[e].m(u,r),k(u,n,r),k(u,l,r),d(l,t),o=!0,f||(m=A(B.call(null,t)),f=!0)},p(u,r){let $=e;e=b(u),e===$?a[e].p(u,r):(N(),y(a[$],1,1,()=>{a[$]=null}),D(),s=a[e],s?s.p(u,r):(s=a[e]=i[e](u),s.c()),w(s,1),s.m(n.parentNode,n))},i(u){o||(w(s),o=!0)},o(u){y(s),o=!1},d(u){a[e].d(u),u&&v(n),u&&v(l),f=!1,m()}}}function V(c){let e,s;return e=new z({props:{$$slots:{default:[U]},$$scope:{ctx:c}}}),{c(){H(e.$$.fragment)},m(n,l){L(e,n,l),s=!0},p(n,[l]){const t={};l&71&&(t.$$scope={dirty:l,ctx:n}),e.$set(t)},i(n){s||(w(e.$$.fragment,n),s=!0)},o(n){y(e.$$.fragment,n),s=!1},d(n){S(e,n)}}}function W(c,e,s){let n="",l=!1,t=!1;async function o(){if(!l){s(1,l=!0);try{await C.admins.requestPasswordReset(n),s(2,t=!0)}catch(m){C.errorResponseHandler(m)}s(1,l=!1)}}function f(){n=this.value,s(0,n)}return[n,l,t,o,f]}class Y extends M{constructor(e){super(),T(this,e,W,V,j,{})}}export{Y as default};

View File

@ -1,4 +1,4 @@
import{S as z,i as G,s as I,F as J,c as S,m as L,t as v,a as y,d as R,C as M,E as N,g as _,k as W,n as Y,o as b,G as j,H as A,p as B,q as D,e as m,w as C,b as h,f as d,r as H,h as k,u as P,v as K,y as E,x as O,z as T}from"./index-3e0f12d8.js";function Q(r){let e,t,l,s,n,o,c,a,i,u,g,$,p=r[3]&&F(r);return o=new D({props:{class:"form-field required",name:"password",$$slots:{default:[V,({uniqueId:f})=>({8:f}),({uniqueId:f})=>f?256:0]},$$scope:{ctx:r}}}),{c(){e=m("form"),t=m("div"),l=m("h5"),s=C(`Type your password to confirm changing your email address import{S as z,i as G,s as I,F as J,c as S,m as L,t as v,a as y,d as R,C as M,E as N,g as _,k as W,n as Y,o as b,G as j,H as A,p as B,q as D,e as m,w as C,b as h,f as d,r as H,h as k,u as P,v as K,y as E,x as O,z as T}from"./index-ffbb9561.js";function Q(r){let e,t,l,s,n,o,c,a,i,u,g,$,p=r[3]&&F(r);return o=new D({props:{class:"form-field required",name:"password",$$slots:{default:[V,({uniqueId:f})=>({8:f}),({uniqueId:f})=>f?256:0]},$$scope:{ctx:r}}}),{c(){e=m("form"),t=m("div"),l=m("h5"),s=C(`Type your password to confirm changing your email address
`),p&&p.c(),n=h(),S(o.$$.fragment),c=h(),a=m("button"),i=m("span"),i.textContent="Confirm new email",d(t,"class","content txt-center m-b-base"),d(i,"class","txt"),d(a,"type","submit"),d(a,"class","btn btn-lg btn-block"),a.disabled=r[1],H(a,"btn-loading",r[1])},m(f,w){_(f,e,w),k(e,t),k(t,l),k(l,s),p&&p.m(l,null),k(e,n),L(o,e,null),k(e,c),k(e,a),k(a,i),u=!0,g||($=P(e,"submit",K(r[4])),g=!0)},p(f,w){f[3]?p?p.p(f,w):(p=F(f),p.c(),p.m(l,null)):p&&(p.d(1),p=null);const q={};w&769&&(q.$$scope={dirty:w,ctx:f}),o.$set(q),(!u||w&2)&&(a.disabled=f[1]),(!u||w&2)&&H(a,"btn-loading",f[1])},i(f){u||(v(o.$$.fragment,f),u=!0)},o(f){y(o.$$.fragment,f),u=!1},d(f){f&&b(e),p&&p.d(),R(o),g=!1,$()}}}function U(r){let e,t,l,s,n;return{c(){e=m("div"),e.innerHTML=`<div class="icon"><i class="ri-checkbox-circle-line"></i></div> `),p&&p.c(),n=h(),S(o.$$.fragment),c=h(),a=m("button"),i=m("span"),i.textContent="Confirm new email",d(t,"class","content txt-center m-b-base"),d(i,"class","txt"),d(a,"type","submit"),d(a,"class","btn btn-lg btn-block"),a.disabled=r[1],H(a,"btn-loading",r[1])},m(f,w){_(f,e,w),k(e,t),k(t,l),k(l,s),p&&p.m(l,null),k(e,n),L(o,e,null),k(e,c),k(e,a),k(a,i),u=!0,g||($=P(e,"submit",K(r[4])),g=!0)},p(f,w){f[3]?p?p.p(f,w):(p=F(f),p.c(),p.m(l,null)):p&&(p.d(1),p=null);const q={};w&769&&(q.$$scope={dirty:w,ctx:f}),o.$set(q),(!u||w&2)&&(a.disabled=f[1]),(!u||w&2)&&H(a,"btn-loading",f[1])},i(f){u||(v(o.$$.fragment,f),u=!0)},o(f){y(o.$$.fragment,f),u=!1},d(f){f&&b(e),p&&p.d(),R(o),g=!1,$()}}}function U(r){let e,t,l,s,n;return{c(){e=m("div"),e.innerHTML=`<div class="icon"><i class="ri-checkbox-circle-line"></i></div>
<div class="content txt-bold"><p>Successfully changed the user email address.</p> <div class="content txt-bold"><p>Successfully changed the user email address.</p>
<p>You can now sign in with your new email address.</p></div>`,t=h(),l=m("button"),l.textContent="Close",d(e,"class","alert alert-success"),d(l,"type","button"),d(l,"class","btn btn-transparent btn-block")},m(o,c){_(o,e,c),_(o,t,c),_(o,l,c),s||(n=P(l,"click",r[6]),s=!0)},p:E,i:E,o:E,d(o){o&&b(e),o&&b(t),o&&b(l),s=!1,n()}}}function F(r){let e,t,l;return{c(){e=C("to "),t=m("strong"),l=C(r[3]),d(t,"class","txt-nowrap")},m(s,n){_(s,e,n),_(s,t,n),k(t,l)},p(s,n){n&8&&O(l,s[3])},d(s){s&&b(e),s&&b(t)}}}function V(r){let e,t,l,s,n,o,c,a;return{c(){e=m("label"),t=C("Password"),s=h(),n=m("input"),d(e,"for",l=r[8]),d(n,"type","password"),d(n,"id",o=r[8]),n.required=!0,n.autofocus=!0},m(i,u){_(i,e,u),k(e,t),_(i,s,u),_(i,n,u),T(n,r[0]),n.focus(),c||(a=P(n,"input",r[7]),c=!0)},p(i,u){u&256&&l!==(l=i[8])&&d(e,"for",l),u&256&&o!==(o=i[8])&&d(n,"id",o),u&1&&n.value!==i[0]&&T(n,i[0])},d(i){i&&b(e),i&&b(s),i&&b(n),c=!1,a()}}}function X(r){let e,t,l,s;const n=[U,Q],o=[];function c(a,i){return a[2]?0:1}return e=c(r),t=o[e]=n[e](r),{c(){t.c(),l=N()},m(a,i){o[e].m(a,i),_(a,l,i),s=!0},p(a,i){let u=e;e=c(a),e===u?o[e].p(a,i):(W(),y(o[u],1,1,()=>{o[u]=null}),Y(),t=o[e],t?t.p(a,i):(t=o[e]=n[e](a),t.c()),v(t,1),t.m(l.parentNode,l))},i(a){s||(v(t),s=!0)},o(a){y(t),s=!1},d(a){o[e].d(a),a&&b(l)}}}function Z(r){let e,t;return e=new J({props:{nobranding:!0,$$slots:{default:[X]},$$scope:{ctx:r}}}),{c(){S(e.$$.fragment)},m(l,s){L(e,l,s),t=!0},p(l,[s]){const n={};s&527&&(n.$$scope={dirty:s,ctx:l}),e.$set(n)},i(l){t||(v(e.$$.fragment,l),t=!0)},o(l){y(e.$$.fragment,l),t=!1},d(l){R(e,l)}}}function x(r,e,t){let l,{params:s}=e,n="",o=!1,c=!1;async function a(){if(o)return;t(1,o=!0);const g=new j("../");try{const $=A(s==null?void 0:s.token);await g.collection($.collectionId).confirmEmailChange(s==null?void 0:s.token,n),t(2,c=!0)}catch($){B.errorResponseHandler($)}t(1,o=!1)}const i=()=>window.close();function u(){n=this.value,t(0,n)}return r.$$set=g=>{"params"in g&&t(5,s=g.params)},r.$$.update=()=>{r.$$.dirty&32&&t(3,l=M.getJWTPayload(s==null?void 0:s.token).newEmail||"")},[n,o,c,l,a,s,i,u]}class te extends z{constructor(e){super(),G(this,e,x,Z,I,{params:5})}}export{te as default}; <p>You can now sign in with your new email address.</p></div>`,t=h(),l=m("button"),l.textContent="Close",d(e,"class","alert alert-success"),d(l,"type","button"),d(l,"class","btn btn-transparent btn-block")},m(o,c){_(o,e,c),_(o,t,c),_(o,l,c),s||(n=P(l,"click",r[6]),s=!0)},p:E,i:E,o:E,d(o){o&&b(e),o&&b(t),o&&b(l),s=!1,n()}}}function F(r){let e,t,l;return{c(){e=C("to "),t=m("strong"),l=C(r[3]),d(t,"class","txt-nowrap")},m(s,n){_(s,e,n),_(s,t,n),k(t,l)},p(s,n){n&8&&O(l,s[3])},d(s){s&&b(e),s&&b(t)}}}function V(r){let e,t,l,s,n,o,c,a;return{c(){e=m("label"),t=C("Password"),s=h(),n=m("input"),d(e,"for",l=r[8]),d(n,"type","password"),d(n,"id",o=r[8]),n.required=!0,n.autofocus=!0},m(i,u){_(i,e,u),k(e,t),_(i,s,u),_(i,n,u),T(n,r[0]),n.focus(),c||(a=P(n,"input",r[7]),c=!0)},p(i,u){u&256&&l!==(l=i[8])&&d(e,"for",l),u&256&&o!==(o=i[8])&&d(n,"id",o),u&1&&n.value!==i[0]&&T(n,i[0])},d(i){i&&b(e),i&&b(s),i&&b(n),c=!1,a()}}}function X(r){let e,t,l,s;const n=[U,Q],o=[];function c(a,i){return a[2]?0:1}return e=c(r),t=o[e]=n[e](r),{c(){t.c(),l=N()},m(a,i){o[e].m(a,i),_(a,l,i),s=!0},p(a,i){let u=e;e=c(a),e===u?o[e].p(a,i):(W(),y(o[u],1,1,()=>{o[u]=null}),Y(),t=o[e],t?t.p(a,i):(t=o[e]=n[e](a),t.c()),v(t,1),t.m(l.parentNode,l))},i(a){s||(v(t),s=!0)},o(a){y(t),s=!1},d(a){o[e].d(a),a&&b(l)}}}function Z(r){let e,t;return e=new J({props:{nobranding:!0,$$slots:{default:[X]},$$scope:{ctx:r}}}),{c(){S(e.$$.fragment)},m(l,s){L(e,l,s),t=!0},p(l,[s]){const n={};s&527&&(n.$$scope={dirty:s,ctx:l}),e.$set(n)},i(l){t||(v(e.$$.fragment,l),t=!0)},o(l){y(e.$$.fragment,l),t=!1},d(l){R(e,l)}}}function x(r,e,t){let l,{params:s}=e,n="",o=!1,c=!1;async function a(){if(o)return;t(1,o=!0);const g=new j("../");try{const $=A(s==null?void 0:s.token);await g.collection($.collectionId).confirmEmailChange(s==null?void 0:s.token,n),t(2,c=!0)}catch($){B.errorResponseHandler($)}t(1,o=!1)}const i=()=>window.close();function u(){n=this.value,t(0,n)}return r.$$set=g=>{"params"in g&&t(5,s=g.params)},r.$$.update=()=>{r.$$.dirty&32&&t(3,l=M.getJWTPayload(s==null?void 0:s.token).newEmail||"")},[n,o,c,l,a,s,i,u]}class te extends z{constructor(e){super(),G(this,e,x,Z,I,{params:5})}}export{te as default};

View File

@ -1,4 +1,4 @@
import{S as J,i as M,s as W,F as Y,c as F,m as N,t as y,a as q,d as T,C as j,E as A,g as _,k as B,n as D,o as m,G as K,H as O,p as Q,q as E,e as b,w as R,b as P,f as p,r as G,h as w,u as H,v as U,y as S,x as V,z as h}from"./index-3e0f12d8.js";function X(r){let e,l,s,n,t,o,c,a,i,u,v,k,g,C,d=r[4]&&I(r);return o=new E({props:{class:"form-field required",name:"password",$$slots:{default:[x,({uniqueId:f})=>({10:f}),({uniqueId:f})=>f?1024:0]},$$scope:{ctx:r}}}),a=new E({props:{class:"form-field required",name:"passwordConfirm",$$slots:{default:[ee,({uniqueId:f})=>({10:f}),({uniqueId:f})=>f?1024:0]},$$scope:{ctx:r}}}),{c(){e=b("form"),l=b("div"),s=b("h5"),n=R(`Reset your user password import{S as J,i as M,s as W,F as Y,c as F,m as N,t as y,a as q,d as T,C as j,E as A,g as _,k as B,n as D,o as m,G as K,H as O,p as Q,q as E,e as b,w as R,b as P,f as p,r as G,h as w,u as H,v as U,y as S,x as V,z as h}from"./index-ffbb9561.js";function X(r){let e,l,s,n,t,o,c,a,i,u,v,k,g,C,d=r[4]&&I(r);return o=new E({props:{class:"form-field required",name:"password",$$slots:{default:[x,({uniqueId:f})=>({10:f}),({uniqueId:f})=>f?1024:0]},$$scope:{ctx:r}}}),a=new E({props:{class:"form-field required",name:"passwordConfirm",$$slots:{default:[ee,({uniqueId:f})=>({10:f}),({uniqueId:f})=>f?1024:0]},$$scope:{ctx:r}}}),{c(){e=b("form"),l=b("div"),s=b("h5"),n=R(`Reset your user password
`),d&&d.c(),t=P(),F(o.$$.fragment),c=P(),F(a.$$.fragment),i=P(),u=b("button"),v=b("span"),v.textContent="Set new password",p(l,"class","content txt-center m-b-base"),p(v,"class","txt"),p(u,"type","submit"),p(u,"class","btn btn-lg btn-block"),u.disabled=r[2],G(u,"btn-loading",r[2])},m(f,$){_(f,e,$),w(e,l),w(l,s),w(s,n),d&&d.m(s,null),w(e,t),N(o,e,null),w(e,c),N(a,e,null),w(e,i),w(e,u),w(u,v),k=!0,g||(C=H(e,"submit",U(r[5])),g=!0)},p(f,$){f[4]?d?d.p(f,$):(d=I(f),d.c(),d.m(s,null)):d&&(d.d(1),d=null);const L={};$&3073&&(L.$$scope={dirty:$,ctx:f}),o.$set(L);const z={};$&3074&&(z.$$scope={dirty:$,ctx:f}),a.$set(z),(!k||$&4)&&(u.disabled=f[2]),(!k||$&4)&&G(u,"btn-loading",f[2])},i(f){k||(y(o.$$.fragment,f),y(a.$$.fragment,f),k=!0)},o(f){q(o.$$.fragment,f),q(a.$$.fragment,f),k=!1},d(f){f&&m(e),d&&d.d(),T(o),T(a),g=!1,C()}}}function Z(r){let e,l,s,n,t;return{c(){e=b("div"),e.innerHTML=`<div class="icon"><i class="ri-checkbox-circle-line"></i></div> `),d&&d.c(),t=P(),F(o.$$.fragment),c=P(),F(a.$$.fragment),i=P(),u=b("button"),v=b("span"),v.textContent="Set new password",p(l,"class","content txt-center m-b-base"),p(v,"class","txt"),p(u,"type","submit"),p(u,"class","btn btn-lg btn-block"),u.disabled=r[2],G(u,"btn-loading",r[2])},m(f,$){_(f,e,$),w(e,l),w(l,s),w(s,n),d&&d.m(s,null),w(e,t),N(o,e,null),w(e,c),N(a,e,null),w(e,i),w(e,u),w(u,v),k=!0,g||(C=H(e,"submit",U(r[5])),g=!0)},p(f,$){f[4]?d?d.p(f,$):(d=I(f),d.c(),d.m(s,null)):d&&(d.d(1),d=null);const L={};$&3073&&(L.$$scope={dirty:$,ctx:f}),o.$set(L);const z={};$&3074&&(z.$$scope={dirty:$,ctx:f}),a.$set(z),(!k||$&4)&&(u.disabled=f[2]),(!k||$&4)&&G(u,"btn-loading",f[2])},i(f){k||(y(o.$$.fragment,f),y(a.$$.fragment,f),k=!0)},o(f){q(o.$$.fragment,f),q(a.$$.fragment,f),k=!1},d(f){f&&m(e),d&&d.d(),T(o),T(a),g=!1,C()}}}function Z(r){let e,l,s,n,t;return{c(){e=b("div"),e.innerHTML=`<div class="icon"><i class="ri-checkbox-circle-line"></i></div>
<div class="content txt-bold"><p>Successfully changed the user password.</p> <div class="content txt-bold"><p>Successfully changed the user password.</p>
<p>You can now sign in with your new password.</p></div>`,l=P(),s=b("button"),s.textContent="Close",p(e,"class","alert alert-success"),p(s,"type","button"),p(s,"class","btn btn-transparent btn-block")},m(o,c){_(o,e,c),_(o,l,c),_(o,s,c),n||(t=H(s,"click",r[7]),n=!0)},p:S,i:S,o:S,d(o){o&&m(e),o&&m(l),o&&m(s),n=!1,t()}}}function I(r){let e,l,s;return{c(){e=R("for "),l=b("strong"),s=R(r[4])},m(n,t){_(n,e,t),_(n,l,t),w(l,s)},p(n,t){t&16&&V(s,n[4])},d(n){n&&m(e),n&&m(l)}}}function x(r){let e,l,s,n,t,o,c,a;return{c(){e=b("label"),l=R("New password"),n=P(),t=b("input"),p(e,"for",s=r[10]),p(t,"type","password"),p(t,"id",o=r[10]),t.required=!0,t.autofocus=!0},m(i,u){_(i,e,u),w(e,l),_(i,n,u),_(i,t,u),h(t,r[0]),t.focus(),c||(a=H(t,"input",r[8]),c=!0)},p(i,u){u&1024&&s!==(s=i[10])&&p(e,"for",s),u&1024&&o!==(o=i[10])&&p(t,"id",o),u&1&&t.value!==i[0]&&h(t,i[0])},d(i){i&&m(e),i&&m(n),i&&m(t),c=!1,a()}}}function ee(r){let e,l,s,n,t,o,c,a;return{c(){e=b("label"),l=R("New password confirm"),n=P(),t=b("input"),p(e,"for",s=r[10]),p(t,"type","password"),p(t,"id",o=r[10]),t.required=!0},m(i,u){_(i,e,u),w(e,l),_(i,n,u),_(i,t,u),h(t,r[1]),c||(a=H(t,"input",r[9]),c=!0)},p(i,u){u&1024&&s!==(s=i[10])&&p(e,"for",s),u&1024&&o!==(o=i[10])&&p(t,"id",o),u&2&&t.value!==i[1]&&h(t,i[1])},d(i){i&&m(e),i&&m(n),i&&m(t),c=!1,a()}}}function te(r){let e,l,s,n;const t=[Z,X],o=[];function c(a,i){return a[3]?0:1}return e=c(r),l=o[e]=t[e](r),{c(){l.c(),s=A()},m(a,i){o[e].m(a,i),_(a,s,i),n=!0},p(a,i){let u=e;e=c(a),e===u?o[e].p(a,i):(B(),q(o[u],1,1,()=>{o[u]=null}),D(),l=o[e],l?l.p(a,i):(l=o[e]=t[e](a),l.c()),y(l,1),l.m(s.parentNode,s))},i(a){n||(y(l),n=!0)},o(a){q(l),n=!1},d(a){o[e].d(a),a&&m(s)}}}function se(r){let e,l;return e=new Y({props:{nobranding:!0,$$slots:{default:[te]},$$scope:{ctx:r}}}),{c(){F(e.$$.fragment)},m(s,n){N(e,s,n),l=!0},p(s,[n]){const t={};n&2079&&(t.$$scope={dirty:n,ctx:s}),e.$set(t)},i(s){l||(y(e.$$.fragment,s),l=!0)},o(s){q(e.$$.fragment,s),l=!1},d(s){T(e,s)}}}function le(r,e,l){let s,{params:n}=e,t="",o="",c=!1,a=!1;async function i(){if(c)return;l(2,c=!0);const g=new K("../");try{const C=O(n==null?void 0:n.token);await g.collection(C.collectionId).confirmPasswordReset(n==null?void 0:n.token,t,o),l(3,a=!0)}catch(C){Q.errorResponseHandler(C)}l(2,c=!1)}const u=()=>window.close();function v(){t=this.value,l(0,t)}function k(){o=this.value,l(1,o)}return r.$$set=g=>{"params"in g&&l(6,n=g.params)},r.$$.update=()=>{r.$$.dirty&64&&l(4,s=j.getJWTPayload(n==null?void 0:n.token).email||"")},[t,o,c,a,s,i,n,u,v,k]}class oe extends J{constructor(e){super(),M(this,e,le,se,W,{params:6})}}export{oe as default}; <p>You can now sign in with your new password.</p></div>`,l=P(),s=b("button"),s.textContent="Close",p(e,"class","alert alert-success"),p(s,"type","button"),p(s,"class","btn btn-transparent btn-block")},m(o,c){_(o,e,c),_(o,l,c),_(o,s,c),n||(t=H(s,"click",r[7]),n=!0)},p:S,i:S,o:S,d(o){o&&m(e),o&&m(l),o&&m(s),n=!1,t()}}}function I(r){let e,l,s;return{c(){e=R("for "),l=b("strong"),s=R(r[4])},m(n,t){_(n,e,t),_(n,l,t),w(l,s)},p(n,t){t&16&&V(s,n[4])},d(n){n&&m(e),n&&m(l)}}}function x(r){let e,l,s,n,t,o,c,a;return{c(){e=b("label"),l=R("New password"),n=P(),t=b("input"),p(e,"for",s=r[10]),p(t,"type","password"),p(t,"id",o=r[10]),t.required=!0,t.autofocus=!0},m(i,u){_(i,e,u),w(e,l),_(i,n,u),_(i,t,u),h(t,r[0]),t.focus(),c||(a=H(t,"input",r[8]),c=!0)},p(i,u){u&1024&&s!==(s=i[10])&&p(e,"for",s),u&1024&&o!==(o=i[10])&&p(t,"id",o),u&1&&t.value!==i[0]&&h(t,i[0])},d(i){i&&m(e),i&&m(n),i&&m(t),c=!1,a()}}}function ee(r){let e,l,s,n,t,o,c,a;return{c(){e=b("label"),l=R("New password confirm"),n=P(),t=b("input"),p(e,"for",s=r[10]),p(t,"type","password"),p(t,"id",o=r[10]),t.required=!0},m(i,u){_(i,e,u),w(e,l),_(i,n,u),_(i,t,u),h(t,r[1]),c||(a=H(t,"input",r[9]),c=!0)},p(i,u){u&1024&&s!==(s=i[10])&&p(e,"for",s),u&1024&&o!==(o=i[10])&&p(t,"id",o),u&2&&t.value!==i[1]&&h(t,i[1])},d(i){i&&m(e),i&&m(n),i&&m(t),c=!1,a()}}}function te(r){let e,l,s,n;const t=[Z,X],o=[];function c(a,i){return a[3]?0:1}return e=c(r),l=o[e]=t[e](r),{c(){l.c(),s=A()},m(a,i){o[e].m(a,i),_(a,s,i),n=!0},p(a,i){let u=e;e=c(a),e===u?o[e].p(a,i):(B(),q(o[u],1,1,()=>{o[u]=null}),D(),l=o[e],l?l.p(a,i):(l=o[e]=t[e](a),l.c()),y(l,1),l.m(s.parentNode,s))},i(a){n||(y(l),n=!0)},o(a){q(l),n=!1},d(a){o[e].d(a),a&&m(s)}}}function se(r){let e,l;return e=new Y({props:{nobranding:!0,$$slots:{default:[te]},$$scope:{ctx:r}}}),{c(){F(e.$$.fragment)},m(s,n){N(e,s,n),l=!0},p(s,[n]){const t={};n&2079&&(t.$$scope={dirty:n,ctx:s}),e.$set(t)},i(s){l||(y(e.$$.fragment,s),l=!0)},o(s){q(e.$$.fragment,s),l=!1},d(s){T(e,s)}}}function le(r,e,l){let s,{params:n}=e,t="",o="",c=!1,a=!1;async function i(){if(c)return;l(2,c=!0);const g=new K("../");try{const C=O(n==null?void 0:n.token);await g.collection(C.collectionId).confirmPasswordReset(n==null?void 0:n.token,t,o),l(3,a=!0)}catch(C){Q.errorResponseHandler(C)}l(2,c=!1)}const u=()=>window.close();function v(){t=this.value,l(0,t)}function k(){o=this.value,l(1,o)}return r.$$set=g=>{"params"in g&&l(6,n=g.params)},r.$$.update=()=>{r.$$.dirty&64&&l(4,s=j.getJWTPayload(n==null?void 0:n.token).email||"")},[t,o,c,a,s,i,n,u,v,k]}class oe extends J{constructor(e){super(),M(this,e,le,se,W,{params:6})}}export{oe as default};

View File

@ -1,3 +1,3 @@
import{S as v,i as y,s as w,F as C,c as g,m as x,t as $,a as H,d as L,G as P,H as T,E as M,g as r,o as a,e as u,b as _,f,u as b,y as p}from"./index-3e0f12d8.js";function S(o){let t,s,e,n,l;return{c(){t=u("div"),t.innerHTML=`<div class="icon"><i class="ri-error-warning-line"></i></div> import{S as v,i as y,s as w,F as C,c as g,m as x,t as $,a as H,d as L,G as P,H as T,E as M,g as r,o as a,e as u,b as _,f,u as b,y as p}from"./index-ffbb9561.js";function S(o){let t,s,e,n,l;return{c(){t=u("div"),t.innerHTML=`<div class="icon"><i class="ri-error-warning-line"></i></div>
<div class="content txt-bold"><p>Invalid or expired verification token.</p></div>`,s=_(),e=u("button"),e.textContent="Close",f(t,"class","alert alert-danger"),f(e,"type","button"),f(e,"class","btn btn-transparent btn-block")},m(i,c){r(i,t,c),r(i,s,c),r(i,e,c),n||(l=b(e,"click",o[4]),n=!0)},p,d(i){i&&a(t),i&&a(s),i&&a(e),n=!1,l()}}}function F(o){let t,s,e,n,l;return{c(){t=u("div"),t.innerHTML=`<div class="icon"><i class="ri-checkbox-circle-line"></i></div> <div class="content txt-bold"><p>Invalid or expired verification token.</p></div>`,s=_(),e=u("button"),e.textContent="Close",f(t,"class","alert alert-danger"),f(e,"type","button"),f(e,"class","btn btn-transparent btn-block")},m(i,c){r(i,t,c),r(i,s,c),r(i,e,c),n||(l=b(e,"click",o[4]),n=!0)},p,d(i){i&&a(t),i&&a(s),i&&a(e),n=!1,l()}}}function F(o){let t,s,e,n,l;return{c(){t=u("div"),t.innerHTML=`<div class="icon"><i class="ri-checkbox-circle-line"></i></div>
<div class="content txt-bold"><p>Successfully verified email address.</p></div>`,s=_(),e=u("button"),e.textContent="Close",f(t,"class","alert alert-success"),f(e,"type","button"),f(e,"class","btn btn-transparent btn-block")},m(i,c){r(i,t,c),r(i,s,c),r(i,e,c),n||(l=b(e,"click",o[3]),n=!0)},p,d(i){i&&a(t),i&&a(s),i&&a(e),n=!1,l()}}}function I(o){let t;return{c(){t=u("div"),t.innerHTML='<div class="loader loader-lg"><em>Please wait...</em></div>',f(t,"class","txt-center")},m(s,e){r(s,t,e)},p,d(s){s&&a(t)}}}function V(o){let t;function s(l,i){return l[1]?I:l[0]?F:S}let e=s(o),n=e(o);return{c(){n.c(),t=M()},m(l,i){n.m(l,i),r(l,t,i)},p(l,i){e===(e=s(l))&&n?n.p(l,i):(n.d(1),n=e(l),n&&(n.c(),n.m(t.parentNode,t)))},d(l){n.d(l),l&&a(t)}}}function q(o){let t,s;return t=new C({props:{nobranding:!0,$$slots:{default:[V]},$$scope:{ctx:o}}}),{c(){g(t.$$.fragment)},m(e,n){x(t,e,n),s=!0},p(e,[n]){const l={};n&67&&(l.$$scope={dirty:n,ctx:e}),t.$set(l)},i(e){s||($(t.$$.fragment,e),s=!0)},o(e){H(t.$$.fragment,e),s=!1},d(e){L(t,e)}}}function E(o,t,s){let{params:e}=t,n=!1,l=!1;i();async function i(){s(1,l=!0);const d=new P("../");try{const m=T(e==null?void 0:e.token);await d.collection(m.collectionId).confirmVerification(e==null?void 0:e.token),s(0,n=!0)}catch{s(0,n=!1)}s(1,l=!1)}const c=()=>window.close(),k=()=>window.close();return o.$$set=d=>{"params"in d&&s(2,e=d.params)},[n,l,e,c,k]}class N extends v{constructor(t){super(),y(this,t,E,q,w,{params:2})}}export{N as default}; <div class="content txt-bold"><p>Successfully verified email address.</p></div>`,s=_(),e=u("button"),e.textContent="Close",f(t,"class","alert alert-success"),f(e,"type","button"),f(e,"class","btn btn-transparent btn-block")},m(i,c){r(i,t,c),r(i,s,c),r(i,e,c),n||(l=b(e,"click",o[3]),n=!0)},p,d(i){i&&a(t),i&&a(s),i&&a(e),n=!1,l()}}}function I(o){let t;return{c(){t=u("div"),t.innerHTML='<div class="loader loader-lg"><em>Please wait...</em></div>',f(t,"class","txt-center")},m(s,e){r(s,t,e)},p,d(s){s&&a(t)}}}function V(o){let t;function s(l,i){return l[1]?I:l[0]?F:S}let e=s(o),n=e(o);return{c(){n.c(),t=M()},m(l,i){n.m(l,i),r(l,t,i)},p(l,i){e===(e=s(l))&&n?n.p(l,i):(n.d(1),n=e(l),n&&(n.c(),n.m(t.parentNode,t)))},d(l){n.d(l),l&&a(t)}}}function q(o){let t,s;return t=new C({props:{nobranding:!0,$$slots:{default:[V]},$$scope:{ctx:o}}}),{c(){g(t.$$.fragment)},m(e,n){x(t,e,n),s=!0},p(e,[n]){const l={};n&67&&(l.$$scope={dirty:n,ctx:e}),t.$set(l)},i(e){s||($(t.$$.fragment,e),s=!0)},o(e){H(t.$$.fragment,e),s=!1},d(e){L(t,e)}}}function E(o,t,s){let{params:e}=t,n=!1,l=!1;i();async function i(){s(1,l=!0);const d=new P("../");try{const m=T(e==null?void 0:e.token);await d.collection(m.collectionId).confirmVerification(e==null?void 0:e.token),s(0,n=!0)}catch{s(0,n=!1)}s(1,l=!1)}const c=()=>window.close(),k=()=>window.close();return o.$$set=d=>{"params"in d&&s(2,e=d.params)},[n,l,e,c,k]}class N extends v{constructor(t){super(),y(this,t,E,q,w,{params:2})}}export{N as default};

View File

@ -1,4 +1,4 @@
import{S as re,i as ae,s as be,N as ue,C as P,e as u,w as y,b as a,c as te,f as p,g as t,h as I,m as ne,x as pe,t as ie,a as le,o as n,d as ce,R as me,p as de}from"./index-3e0f12d8.js";import{S as fe}from"./SdkTabs-ed893501.js";function $e(o){var B,U,W,A,H,L,T,q,M,N,j,J;let i,m,l=o[0].name+"",b,d,h,f,_,$,k,c,S,v,w,R,C,g,E,r,D;return c=new fe({props:{js:` import{S as re,i as ae,s as be,N as ue,C as P,e as u,w as y,b as a,c as te,f as p,g as t,h as I,m as ne,x as pe,t as ie,a as le,o as n,d as ce,R as me,p as de}from"./index-ffbb9561.js";import{S as fe}from"./SdkTabs-5b973c0d.js";function $e(o){var B,U,W,A,H,L,T,q,M,N,j,J;let i,m,l=o[0].name+"",b,d,h,f,_,$,k,c,S,v,w,R,C,g,E,r,D;return c=new fe({props:{js:`
import PocketBase from 'pocketbase'; import PocketBase from 'pocketbase';
const pb = new PocketBase('${o[1]}'); const pb = new PocketBase('${o[1]}');

View File

@ -1,4 +1,4 @@
import{S as Te,i as Ee,s as Be,e as c,w as v,b as h,c as Pe,f,g as r,h as n,m as Ce,x as L,O as ve,P as Se,k as Re,Q as Me,n as Ae,t as x,a as ee,o as m,d as ye,R as We,C as ze,p as He,r as N,u as Oe,N as Ue}from"./index-3e0f12d8.js";import{S as je}from"./SdkTabs-ed893501.js";function we(o,l,s){const a=o.slice();return a[5]=l[s],a}function ge(o,l,s){const a=o.slice();return a[5]=l[s],a}function $e(o,l){let s,a=l[5].code+"",_,b,i,p;function u(){return l[4](l[5])}return{key:o,first:null,c(){s=c("button"),_=v(a),b=h(),f(s,"class","tab-item"),N(s,"active",l[1]===l[5].code),this.first=s},m($,q){r($,s,q),n(s,_),n(s,b),i||(p=Oe(s,"click",u),i=!0)},p($,q){l=$,q&4&&a!==(a=l[5].code+"")&&L(_,a),q&6&&N(s,"active",l[1]===l[5].code)},d($){$&&m(s),i=!1,p()}}}function qe(o,l){let s,a,_,b;return a=new Ue({props:{content:l[5].body}}),{key:o,first:null,c(){s=c("div"),Pe(a.$$.fragment),_=h(),f(s,"class","tab-item"),N(s,"active",l[1]===l[5].code),this.first=s},m(i,p){r(i,s,p),Ce(a,s,null),n(s,_),b=!0},p(i,p){l=i;const u={};p&4&&(u.content=l[5].body),a.$set(u),(!b||p&6)&&N(s,"active",l[1]===l[5].code)},i(i){b||(x(a.$$.fragment,i),b=!0)},o(i){ee(a.$$.fragment,i),b=!1},d(i){i&&m(s),ye(a)}}}function De(o){var de,pe,ue,fe;let l,s,a=o[0].name+"",_,b,i,p,u,$,q,z=o[0].name+"",F,te,I,P,K,T,Q,w,H,le,O,E,se,G,U=o[0].name+"",J,ae,oe,j,V,B,X,S,Y,R,Z,C,M,g=[],ne=new Map,ie,A,k=[],ce=new Map,y;P=new je({props:{js:` import{S as Te,i as Ee,s as Be,e as c,w as v,b as h,c as Pe,f,g as r,h as n,m as Ce,x as L,O as ve,P as Se,k as Re,Q as Me,n as Ae,t as x,a as ee,o as m,d as ye,R as We,C as ze,p as He,r as N,u as Oe,N as Ue}from"./index-ffbb9561.js";import{S as je}from"./SdkTabs-5b973c0d.js";function we(o,l,s){const a=o.slice();return a[5]=l[s],a}function ge(o,l,s){const a=o.slice();return a[5]=l[s],a}function $e(o,l){let s,a=l[5].code+"",_,b,i,p;function u(){return l[4](l[5])}return{key:o,first:null,c(){s=c("button"),_=v(a),b=h(),f(s,"class","tab-item"),N(s,"active",l[1]===l[5].code),this.first=s},m($,q){r($,s,q),n(s,_),n(s,b),i||(p=Oe(s,"click",u),i=!0)},p($,q){l=$,q&4&&a!==(a=l[5].code+"")&&L(_,a),q&6&&N(s,"active",l[1]===l[5].code)},d($){$&&m(s),i=!1,p()}}}function qe(o,l){let s,a,_,b;return a=new Ue({props:{content:l[5].body}}),{key:o,first:null,c(){s=c("div"),Pe(a.$$.fragment),_=h(),f(s,"class","tab-item"),N(s,"active",l[1]===l[5].code),this.first=s},m(i,p){r(i,s,p),Ce(a,s,null),n(s,_),b=!0},p(i,p){l=i;const u={};p&4&&(u.content=l[5].body),a.$set(u),(!b||p&6)&&N(s,"active",l[1]===l[5].code)},i(i){b||(x(a.$$.fragment,i),b=!0)},o(i){ee(a.$$.fragment,i),b=!1},d(i){i&&m(s),ye(a)}}}function De(o){var de,pe,ue,fe;let l,s,a=o[0].name+"",_,b,i,p,u,$,q,z=o[0].name+"",F,te,I,P,K,T,Q,w,H,le,O,E,se,G,U=o[0].name+"",J,ae,oe,j,V,B,X,S,Y,R,Z,C,M,g=[],ne=new Map,ie,A,k=[],ce=new Map,y;P=new je({props:{js:`
import PocketBase from 'pocketbase'; import PocketBase from 'pocketbase';
const pb = new PocketBase('${o[3]}'); const pb = new PocketBase('${o[3]}');

View File

@ -1,4 +1,4 @@
import{S as Pe,i as $e,s as qe,e as c,w,b as v,c as ve,f as b,g as r,h as n,m as we,x as I,O as me,P as Re,k as ge,Q as ye,n as Be,t as Z,a as x,o as d,d as he,R as Ce,C as Se,p as Te,r as L,u as Me,N as Ae}from"./index-3e0f12d8.js";import{S as Ue}from"./SdkTabs-ed893501.js";function ue(a,s,l){const o=a.slice();return o[5]=s[l],o}function be(a,s,l){const o=a.slice();return o[5]=s[l],o}function _e(a,s){let l,o=s[5].code+"",_,u,i,p;function m(){return s[4](s[5])}return{key:a,first:null,c(){l=c("button"),_=w(o),u=v(),b(l,"class","tab-item"),L(l,"active",s[1]===s[5].code),this.first=l},m(P,$){r(P,l,$),n(l,_),n(l,u),i||(p=Me(l,"click",m),i=!0)},p(P,$){s=P,$&4&&o!==(o=s[5].code+"")&&I(_,o),$&6&&L(l,"active",s[1]===s[5].code)},d(P){P&&d(l),i=!1,p()}}}function ke(a,s){let l,o,_,u;return o=new Ae({props:{content:s[5].body}}),{key:a,first:null,c(){l=c("div"),ve(o.$$.fragment),_=v(),b(l,"class","tab-item"),L(l,"active",s[1]===s[5].code),this.first=l},m(i,p){r(i,l,p),we(o,l,null),n(l,_),u=!0},p(i,p){s=i;const m={};p&4&&(m.content=s[5].body),o.$set(m),(!u||p&6)&&L(l,"active",s[1]===s[5].code)},i(i){u||(Z(o.$$.fragment,i),u=!0)},o(i){x(o.$$.fragment,i),u=!1},d(i){i&&d(l),he(o)}}}function je(a){var re,de;let s,l,o=a[0].name+"",_,u,i,p,m,P,$,D=a[0].name+"",N,ee,Q,q,z,B,G,R,H,te,O,C,se,J,E=a[0].name+"",K,le,V,S,W,T,X,M,Y,g,A,h=[],oe=new Map,ae,U,k=[],ne=new Map,y;q=new Ue({props:{js:` import{S as Pe,i as $e,s as qe,e as c,w,b as v,c as ve,f as b,g as r,h as n,m as we,x as I,O as me,P as Re,k as ge,Q as ye,n as Be,t as Z,a as x,o as d,d as he,R as Ce,C as Se,p as Te,r as L,u as Me,N as Ae}from"./index-ffbb9561.js";import{S as Ue}from"./SdkTabs-5b973c0d.js";function ue(a,s,l){const o=a.slice();return o[5]=s[l],o}function be(a,s,l){const o=a.slice();return o[5]=s[l],o}function _e(a,s){let l,o=s[5].code+"",_,u,i,p;function m(){return s[4](s[5])}return{key:a,first:null,c(){l=c("button"),_=w(o),u=v(),b(l,"class","tab-item"),L(l,"active",s[1]===s[5].code),this.first=l},m(P,$){r(P,l,$),n(l,_),n(l,u),i||(p=Me(l,"click",m),i=!0)},p(P,$){s=P,$&4&&o!==(o=s[5].code+"")&&I(_,o),$&6&&L(l,"active",s[1]===s[5].code)},d(P){P&&d(l),i=!1,p()}}}function ke(a,s){let l,o,_,u;return o=new Ae({props:{content:s[5].body}}),{key:a,first:null,c(){l=c("div"),ve(o.$$.fragment),_=v(),b(l,"class","tab-item"),L(l,"active",s[1]===s[5].code),this.first=l},m(i,p){r(i,l,p),we(o,l,null),n(l,_),u=!0},p(i,p){s=i;const m={};p&4&&(m.content=s[5].body),o.$set(m),(!u||p&6)&&L(l,"active",s[1]===s[5].code)},i(i){u||(Z(o.$$.fragment,i),u=!0)},o(i){x(o.$$.fragment,i),u=!1},d(i){i&&d(l),he(o)}}}function je(a){var re,de;let s,l,o=a[0].name+"",_,u,i,p,m,P,$,D=a[0].name+"",N,ee,Q,q,z,B,G,R,H,te,O,C,se,J,E=a[0].name+"",K,le,V,S,W,T,X,M,Y,g,A,h=[],oe=new Map,ae,U,k=[],ne=new Map,y;q=new Ue({props:{js:`
import PocketBase from 'pocketbase'; import PocketBase from 'pocketbase';
const pb = new PocketBase('${a[3]}'); const pb = new PocketBase('${a[3]}');

View File

@ -1,4 +1,4 @@
import{S as qe,i as we,s as Pe,e as c,w as h,b as v,c as ve,f as b,g as r,h as i,m as he,x as F,O as de,P as ge,k as ye,Q as Be,n as Ce,t as Z,a as x,o as f,d as $e,R as Se,C as Te,p as Re,r as I,u as Ve,N as Me}from"./index-3e0f12d8.js";import{S as Ae}from"./SdkTabs-ed893501.js";function pe(a,l,s){const o=a.slice();return o[5]=l[s],o}function be(a,l,s){const o=a.slice();return o[5]=l[s],o}function _e(a,l){let s,o=l[5].code+"",_,p,n,u;function d(){return l[4](l[5])}return{key:a,first:null,c(){s=c("button"),_=h(o),p=v(),b(s,"class","tab-item"),I(s,"active",l[1]===l[5].code),this.first=s},m(q,w){r(q,s,w),i(s,_),i(s,p),n||(u=Ve(s,"click",d),n=!0)},p(q,w){l=q,w&4&&o!==(o=l[5].code+"")&&F(_,o),w&6&&I(s,"active",l[1]===l[5].code)},d(q){q&&f(s),n=!1,u()}}}function ke(a,l){let s,o,_,p;return o=new Me({props:{content:l[5].body}}),{key:a,first:null,c(){s=c("div"),ve(o.$$.fragment),_=v(),b(s,"class","tab-item"),I(s,"active",l[1]===l[5].code),this.first=s},m(n,u){r(n,s,u),he(o,s,null),i(s,_),p=!0},p(n,u){l=n;const d={};u&4&&(d.content=l[5].body),o.$set(d),(!p||u&6)&&I(s,"active",l[1]===l[5].code)},i(n){p||(Z(o.$$.fragment,n),p=!0)},o(n){x(o.$$.fragment,n),p=!1},d(n){n&&f(s),$e(o)}}}function Ue(a){var re,fe;let l,s,o=a[0].name+"",_,p,n,u,d,q,w,j=a[0].name+"",L,ee,N,P,Q,C,z,g,D,te,H,S,le,G,O=a[0].name+"",J,se,K,T,W,R,X,V,Y,y,M,$=[],oe=new Map,ae,A,k=[],ie=new Map,B;P=new Ae({props:{js:` import{S as qe,i as we,s as Pe,e as c,w as h,b as v,c as ve,f as b,g as r,h as i,m as he,x as F,O as de,P as ge,k as ye,Q as Be,n as Ce,t as Z,a as x,o as f,d as $e,R as Se,C as Te,p as Re,r as I,u as Ve,N as Me}from"./index-ffbb9561.js";import{S as Ae}from"./SdkTabs-5b973c0d.js";function pe(a,l,s){const o=a.slice();return o[5]=l[s],o}function be(a,l,s){const o=a.slice();return o[5]=l[s],o}function _e(a,l){let s,o=l[5].code+"",_,p,n,u;function d(){return l[4](l[5])}return{key:a,first:null,c(){s=c("button"),_=h(o),p=v(),b(s,"class","tab-item"),I(s,"active",l[1]===l[5].code),this.first=s},m(q,w){r(q,s,w),i(s,_),i(s,p),n||(u=Ve(s,"click",d),n=!0)},p(q,w){l=q,w&4&&o!==(o=l[5].code+"")&&F(_,o),w&6&&I(s,"active",l[1]===l[5].code)},d(q){q&&f(s),n=!1,u()}}}function ke(a,l){let s,o,_,p;return o=new Me({props:{content:l[5].body}}),{key:a,first:null,c(){s=c("div"),ve(o.$$.fragment),_=v(),b(s,"class","tab-item"),I(s,"active",l[1]===l[5].code),this.first=s},m(n,u){r(n,s,u),he(o,s,null),i(s,_),p=!0},p(n,u){l=n;const d={};u&4&&(d.content=l[5].body),o.$set(d),(!p||u&6)&&I(s,"active",l[1]===l[5].code)},i(n){p||(Z(o.$$.fragment,n),p=!0)},o(n){x(o.$$.fragment,n),p=!1},d(n){n&&f(s),$e(o)}}}function Ue(a){var re,fe;let l,s,o=a[0].name+"",_,p,n,u,d,q,w,j=a[0].name+"",L,ee,N,P,Q,C,z,g,D,te,H,S,le,G,O=a[0].name+"",J,se,K,T,W,R,X,V,Y,y,M,$=[],oe=new Map,ae,A,k=[],ie=new Map,B;P=new Ae({props:{js:`
import PocketBase from 'pocketbase'; import PocketBase from 'pocketbase';
const pb = new PocketBase('${a[3]}'); const pb = new PocketBase('${a[3]}');

View File

@ -1 +1 @@
import{S as q,i as B,s as F,e as v,b as j,f as h,g as y,h as m,O as C,P as J,k as O,Q,n as Y,t as N,a as P,o as w,w as E,r as S,u as z,x as R,N as A,c as G,m as H,d as L}from"./index-3e0f12d8.js";function D(o,e,l){const s=o.slice();return s[6]=e[l],s}function K(o,e,l){const s=o.slice();return s[6]=e[l],s}function T(o,e){let l,s,g=e[6].title+"",r,i,n,k;function c(){return e[5](e[6])}return{key:o,first:null,c(){l=v("button"),s=v("div"),r=E(g),i=j(),h(s,"class","txt"),h(l,"class","tab-item svelte-1maocj6"),S(l,"active",e[1]===e[6].language),this.first=l},m(u,_){y(u,l,_),m(l,s),m(s,r),m(l,i),n||(k=z(l,"click",c),n=!0)},p(u,_){e=u,_&4&&g!==(g=e[6].title+"")&&R(r,g),_&6&&S(l,"active",e[1]===e[6].language)},d(u){u&&w(l),n=!1,k()}}}function I(o,e){let l,s,g,r,i,n,k=e[6].title+"",c,u,_,p,f;return s=new A({props:{language:e[6].language,content:e[6].content}}),{key:o,first:null,c(){l=v("div"),G(s.$$.fragment),g=j(),r=v("div"),i=v("em"),n=v("a"),c=E(k),u=E(" SDK"),p=j(),h(n,"href",_=e[6].url),h(n,"target","_blank"),h(n,"rel","noopener noreferrer"),h(i,"class","txt-sm txt-hint"),h(r,"class","txt-right"),h(l,"class","tab-item svelte-1maocj6"),S(l,"active",e[1]===e[6].language),this.first=l},m(b,t){y(b,l,t),H(s,l,null),m(l,g),m(l,r),m(r,i),m(i,n),m(n,c),m(n,u),m(l,p),f=!0},p(b,t){e=b;const a={};t&4&&(a.language=e[6].language),t&4&&(a.content=e[6].content),s.$set(a),(!f||t&4)&&k!==(k=e[6].title+"")&&R(c,k),(!f||t&4&&_!==(_=e[6].url))&&h(n,"href",_),(!f||t&6)&&S(l,"active",e[1]===e[6].language)},i(b){f||(N(s.$$.fragment,b),f=!0)},o(b){P(s.$$.fragment,b),f=!1},d(b){b&&w(l),L(s)}}}function U(o){let e,l,s=[],g=new Map,r,i,n=[],k=new Map,c,u,_=o[2];const p=t=>t[6].language;for(let t=0;t<_.length;t+=1){let a=K(o,_,t),d=p(a);g.set(d,s[t]=T(d,a))}let f=o[2];const b=t=>t[6].language;for(let t=0;t<f.length;t+=1){let a=D(o,f,t),d=b(a);k.set(d,n[t]=I(d,a))}return{c(){e=v("div"),l=v("div");for(let t=0;t<s.length;t+=1)s[t].c();r=j(),i=v("div");for(let t=0;t<n.length;t+=1)n[t].c();h(l,"class","tabs-header compact left"),h(i,"class","tabs-content"),h(e,"class",c="tabs sdk-tabs "+o[0]+" svelte-1maocj6")},m(t,a){y(t,e,a),m(e,l);for(let d=0;d<s.length;d+=1)s[d].m(l,null);m(e,r),m(e,i);for(let d=0;d<n.length;d+=1)n[d].m(i,null);u=!0},p(t,[a]){a&6&&(_=t[2],s=C(s,a,p,1,t,_,g,l,J,T,null,K)),a&6&&(f=t[2],O(),n=C(n,a,b,1,t,f,k,i,Q,I,null,D),Y()),(!u||a&1&&c!==(c="tabs sdk-tabs "+t[0]+" svelte-1maocj6"))&&h(e,"class",c)},i(t){if(!u){for(let a=0;a<f.length;a+=1)N(n[a]);u=!0}},o(t){for(let a=0;a<n.length;a+=1)P(n[a]);u=!1},d(t){t&&w(e);for(let a=0;a<s.length;a+=1)s[a].d();for(let a=0;a<n.length;a+=1)n[a].d()}}}const M="pb_sdk_preference";function V(o,e,l){let s,{class:g="m-b-base"}=e,{js:r=""}=e,{dart:i=""}=e,n=localStorage.getItem(M)||"javascript";const k=c=>l(1,n=c.language);return o.$$set=c=>{"class"in c&&l(0,g=c.class),"js"in c&&l(3,r=c.js),"dart"in c&&l(4,i=c.dart)},o.$$.update=()=>{o.$$.dirty&2&&n&&localStorage.setItem(M,n),o.$$.dirty&24&&l(2,s=[{title:"JavaScript",language:"javascript",content:r,url:"https://github.com/pocketbase/js-sdk"},{title:"Dart",language:"dart",content:i,url:"https://github.com/pocketbase/dart-sdk"}])},[g,n,s,r,i,k]}class X extends q{constructor(e){super(),B(this,e,V,U,F,{class:0,js:3,dart:4})}}export{X as S}; import{S as q,i as B,s as F,e as v,b as j,f as h,g as y,h as m,O as C,P as J,k as O,Q,n as Y,t as N,a as P,o as w,w as E,r as S,u as z,x as R,N as A,c as G,m as H,d as L}from"./index-ffbb9561.js";function D(o,e,l){const s=o.slice();return s[6]=e[l],s}function K(o,e,l){const s=o.slice();return s[6]=e[l],s}function T(o,e){let l,s,g=e[6].title+"",r,i,n,k;function c(){return e[5](e[6])}return{key:o,first:null,c(){l=v("button"),s=v("div"),r=E(g),i=j(),h(s,"class","txt"),h(l,"class","tab-item svelte-1maocj6"),S(l,"active",e[1]===e[6].language),this.first=l},m(u,_){y(u,l,_),m(l,s),m(s,r),m(l,i),n||(k=z(l,"click",c),n=!0)},p(u,_){e=u,_&4&&g!==(g=e[6].title+"")&&R(r,g),_&6&&S(l,"active",e[1]===e[6].language)},d(u){u&&w(l),n=!1,k()}}}function I(o,e){let l,s,g,r,i,n,k=e[6].title+"",c,u,_,p,f;return s=new A({props:{language:e[6].language,content:e[6].content}}),{key:o,first:null,c(){l=v("div"),G(s.$$.fragment),g=j(),r=v("div"),i=v("em"),n=v("a"),c=E(k),u=E(" SDK"),p=j(),h(n,"href",_=e[6].url),h(n,"target","_blank"),h(n,"rel","noopener noreferrer"),h(i,"class","txt-sm txt-hint"),h(r,"class","txt-right"),h(l,"class","tab-item svelte-1maocj6"),S(l,"active",e[1]===e[6].language),this.first=l},m(b,t){y(b,l,t),H(s,l,null),m(l,g),m(l,r),m(r,i),m(i,n),m(n,c),m(n,u),m(l,p),f=!0},p(b,t){e=b;const a={};t&4&&(a.language=e[6].language),t&4&&(a.content=e[6].content),s.$set(a),(!f||t&4)&&k!==(k=e[6].title+"")&&R(c,k),(!f||t&4&&_!==(_=e[6].url))&&h(n,"href",_),(!f||t&6)&&S(l,"active",e[1]===e[6].language)},i(b){f||(N(s.$$.fragment,b),f=!0)},o(b){P(s.$$.fragment,b),f=!1},d(b){b&&w(l),L(s)}}}function U(o){let e,l,s=[],g=new Map,r,i,n=[],k=new Map,c,u,_=o[2];const p=t=>t[6].language;for(let t=0;t<_.length;t+=1){let a=K(o,_,t),d=p(a);g.set(d,s[t]=T(d,a))}let f=o[2];const b=t=>t[6].language;for(let t=0;t<f.length;t+=1){let a=D(o,f,t),d=b(a);k.set(d,n[t]=I(d,a))}return{c(){e=v("div"),l=v("div");for(let t=0;t<s.length;t+=1)s[t].c();r=j(),i=v("div");for(let t=0;t<n.length;t+=1)n[t].c();h(l,"class","tabs-header compact left"),h(i,"class","tabs-content"),h(e,"class",c="tabs sdk-tabs "+o[0]+" svelte-1maocj6")},m(t,a){y(t,e,a),m(e,l);for(let d=0;d<s.length;d+=1)s[d].m(l,null);m(e,r),m(e,i);for(let d=0;d<n.length;d+=1)n[d].m(i,null);u=!0},p(t,[a]){a&6&&(_=t[2],s=C(s,a,p,1,t,_,g,l,J,T,null,K)),a&6&&(f=t[2],O(),n=C(n,a,b,1,t,f,k,i,Q,I,null,D),Y()),(!u||a&1&&c!==(c="tabs sdk-tabs "+t[0]+" svelte-1maocj6"))&&h(e,"class",c)},i(t){if(!u){for(let a=0;a<f.length;a+=1)N(n[a]);u=!0}},o(t){for(let a=0;a<n.length;a+=1)P(n[a]);u=!1},d(t){t&&w(e);for(let a=0;a<s.length;a+=1)s[a].d();for(let a=0;a<n.length;a+=1)n[a].d()}}}const M="pb_sdk_preference";function V(o,e,l){let s,{class:g="m-b-base"}=e,{js:r=""}=e,{dart:i=""}=e,n=localStorage.getItem(M)||"javascript";const k=c=>l(1,n=c.language);return o.$$set=c=>{"class"in c&&l(0,g=c.class),"js"in c&&l(3,r=c.js),"dart"in c&&l(4,i=c.dart)},o.$$.update=()=>{o.$$.dirty&2&&n&&localStorage.setItem(M,n),o.$$.dirty&24&&l(2,s=[{title:"JavaScript",language:"javascript",content:r,url:"https://github.com/pocketbase/js-sdk"},{title:"Dart",language:"dart",content:i,url:"https://github.com/pocketbase/dart-sdk"}])},[g,n,s,r,i,k]}class X extends q{constructor(e){super(),B(this,e,V,U,F,{class:0,js:3,dart:4})}}export{X as S};

View File

@ -1,4 +1,4 @@
import{S as qe,i as Oe,s as De,e as i,w as v,b as h,c as Se,f,g as r,h as s,m as Be,x as j,O as ye,P as Me,k as We,Q as ze,n as He,t as le,a as oe,o as d,d as Ue,R as Le,C as Re,p as je,r as I,u as Ie,N as Ne}from"./index-3e0f12d8.js";import{S as Ke}from"./SdkTabs-ed893501.js";function Ae(n,l,o){const a=n.slice();return a[5]=l[o],a}function Ce(n,l,o){const a=n.slice();return a[5]=l[o],a}function Te(n,l){let o,a=l[5].code+"",_,b,c,u;function m(){return l[4](l[5])}return{key:n,first:null,c(){o=i("button"),_=v(a),b=h(),f(o,"class","tab-item"),I(o,"active",l[1]===l[5].code),this.first=o},m($,P){r($,o,P),s(o,_),s(o,b),c||(u=Ie(o,"click",m),c=!0)},p($,P){l=$,P&4&&a!==(a=l[5].code+"")&&j(_,a),P&6&&I(o,"active",l[1]===l[5].code)},d($){$&&d(o),c=!1,u()}}}function Ee(n,l){let o,a,_,b;return a=new Ne({props:{content:l[5].body}}),{key:n,first:null,c(){o=i("div"),Se(a.$$.fragment),_=h(),f(o,"class","tab-item"),I(o,"active",l[1]===l[5].code),this.first=o},m(c,u){r(c,o,u),Be(a,o,null),s(o,_),b=!0},p(c,u){l=c;const m={};u&4&&(m.content=l[5].body),a.$set(m),(!b||u&6)&&I(o,"active",l[1]===l[5].code)},i(c){b||(le(a.$$.fragment,c),b=!0)},o(c){oe(a.$$.fragment,c),b=!1},d(c){c&&d(o),Ue(a)}}}function Qe(n){var he,_e,ke,ve;let l,o,a=n[0].name+"",_,b,c,u,m,$,P,M=n[0].name+"",N,se,ae,K,Q,A,F,E,G,g,W,ne,z,y,ie,J,H=n[0].name+"",V,ce,X,re,Y,de,L,Z,S,x,B,ee,U,te,C,q,w=[],ue=new Map,pe,O,k=[],me=new Map,T;A=new Ke({props:{js:` import{S as qe,i as Oe,s as De,e as i,w as v,b as h,c as Se,f,g as r,h as s,m as Be,x as j,O as ye,P as Me,k as We,Q as ze,n as He,t as le,a as oe,o as d,d as Ue,R as Le,C as Re,p as je,r as I,u as Ie,N as Ne}from"./index-ffbb9561.js";import{S as Ke}from"./SdkTabs-5b973c0d.js";function Ae(n,l,o){const a=n.slice();return a[5]=l[o],a}function Ce(n,l,o){const a=n.slice();return a[5]=l[o],a}function Te(n,l){let o,a=l[5].code+"",_,b,c,u;function m(){return l[4](l[5])}return{key:n,first:null,c(){o=i("button"),_=v(a),b=h(),f(o,"class","tab-item"),I(o,"active",l[1]===l[5].code),this.first=o},m($,P){r($,o,P),s(o,_),s(o,b),c||(u=Ie(o,"click",m),c=!0)},p($,P){l=$,P&4&&a!==(a=l[5].code+"")&&j(_,a),P&6&&I(o,"active",l[1]===l[5].code)},d($){$&&d(o),c=!1,u()}}}function Ee(n,l){let o,a,_,b;return a=new Ne({props:{content:l[5].body}}),{key:n,first:null,c(){o=i("div"),Se(a.$$.fragment),_=h(),f(o,"class","tab-item"),I(o,"active",l[1]===l[5].code),this.first=o},m(c,u){r(c,o,u),Be(a,o,null),s(o,_),b=!0},p(c,u){l=c;const m={};u&4&&(m.content=l[5].body),a.$set(m),(!b||u&6)&&I(o,"active",l[1]===l[5].code)},i(c){b||(le(a.$$.fragment,c),b=!0)},o(c){oe(a.$$.fragment,c),b=!1},d(c){c&&d(o),Ue(a)}}}function Qe(n){var he,_e,ke,ve;let l,o,a=n[0].name+"",_,b,c,u,m,$,P,M=n[0].name+"",N,se,ae,K,Q,A,F,E,G,g,W,ne,z,y,ie,J,H=n[0].name+"",V,ce,X,re,Y,de,L,Z,S,x,B,ee,U,te,C,q,w=[],ue=new Map,pe,O,k=[],me=new Map,T;A=new Ke({props:{js:`
import PocketBase from 'pocketbase'; import PocketBase from 'pocketbase';
const pb = new PocketBase('${n[3]}'); const pb = new PocketBase('${n[3]}');

View File

@ -1,4 +1,4 @@
import{S as Ct,i as St,s as Ot,C as U,N as Tt,e as r,w as y,b as m,c as Ae,f as T,g as a,h as i,m as Be,x as I,O as Pe,P as ut,k as Mt,Q as $t,n as Rt,t as pe,a as fe,o,d as Fe,R as qt,p as Dt,r as ce,u as Ht,y as G}from"./index-3e0f12d8.js";import{S as Lt}from"./SdkTabs-ed893501.js";function bt(p,t,l){const s=p.slice();return s[7]=t[l],s}function mt(p,t,l){const s=p.slice();return s[7]=t[l],s}function _t(p,t,l){const s=p.slice();return s[12]=t[l],s}function yt(p){let t;return{c(){t=r("p"),t.innerHTML="Requires admin <code>Authorization:TOKEN</code> header",T(t,"class","txt-hint txt-sm txt-right")},m(l,s){a(l,t,s)},d(l){l&&o(t)}}}function kt(p){let t,l,s,b,u,d,f,k,C,v,O,D,A,F,M,N,B;return{c(){t=r("tr"),t.innerHTML='<td colspan="3" class="txt-hint">Auth fields</td>',l=m(),s=r("tr"),s.innerHTML=`<td><div class="inline-flex"><span class="label label-warning">Optional</span> import{S as Ct,i as St,s as Ot,C as U,N as Tt,e as r,w as y,b as m,c as Ae,f as T,g as a,h as i,m as Be,x as I,O as Pe,P as ut,k as Mt,Q as $t,n as Rt,t as pe,a as fe,o,d as Fe,R as qt,p as Dt,r as ce,u as Ht,y as G}from"./index-ffbb9561.js";import{S as Lt}from"./SdkTabs-5b973c0d.js";function bt(p,t,l){const s=p.slice();return s[7]=t[l],s}function mt(p,t,l){const s=p.slice();return s[7]=t[l],s}function _t(p,t,l){const s=p.slice();return s[12]=t[l],s}function yt(p){let t;return{c(){t=r("p"),t.innerHTML="Requires admin <code>Authorization:TOKEN</code> header",T(t,"class","txt-hint txt-sm txt-right")},m(l,s){a(l,t,s)},d(l){l&&o(t)}}}function kt(p){let t,l,s,b,u,d,f,k,C,v,O,D,A,F,M,N,B;return{c(){t=r("tr"),t.innerHTML='<td colspan="3" class="txt-hint">Auth fields</td>',l=m(),s=r("tr"),s.innerHTML=`<td><div class="inline-flex"><span class="label label-warning">Optional</span>
<span>username</span></div></td> <span>username</span></div></td>
<td><span class="label">String</span></td> <td><span class="label">String</span></td>
<td>The username of the auth record.</td>`,b=m(),u=r("tr"),u.innerHTML=`<td><div class="inline-flex"><span class="label label-warning">Optional</span> <td>The username of the auth record.</td>`,b=m(),u=r("tr"),u.innerHTML=`<td><div class="inline-flex"><span class="label label-warning">Optional</span>

View File

@ -1,4 +1,4 @@
import{S as Ze,i as et,s as tt,N as Ye,e as o,w as m,b as f,c as _e,f as _,g as r,h as l,m as ke,x as me,O as Ve,P as lt,k as st,Q as nt,n as ot,t as z,a as G,o as d,d as he,R as it,C as ze,p as at,r as J,u as rt}from"./index-3e0f12d8.js";import{S as dt}from"./SdkTabs-ed893501.js";function Ge(i,s,n){const a=i.slice();return a[6]=s[n],a}function Je(i,s,n){const a=i.slice();return a[6]=s[n],a}function Ke(i){let s;return{c(){s=o("p"),s.innerHTML="Requires admin <code>Authorization:TOKEN</code> header",_(s,"class","txt-hint txt-sm txt-right")},m(n,a){r(n,s,a)},d(n){n&&d(s)}}}function We(i,s){let n,a=s[6].code+"",w,c,p,u;function C(){return s[5](s[6])}return{key:i,first:null,c(){n=o("button"),w=m(a),c=f(),_(n,"class","tab-item"),J(n,"active",s[2]===s[6].code),this.first=n},m(h,F){r(h,n,F),l(n,w),l(n,c),p||(u=rt(n,"click",C),p=!0)},p(h,F){s=h,F&20&&J(n,"active",s[2]===s[6].code)},d(h){h&&d(n),p=!1,u()}}}function Xe(i,s){let n,a,w,c;return a=new Ye({props:{content:s[6].body}}),{key:i,first:null,c(){n=o("div"),_e(a.$$.fragment),w=f(),_(n,"class","tab-item"),J(n,"active",s[2]===s[6].code),this.first=n},m(p,u){r(p,n,u),ke(a,n,null),l(n,w),c=!0},p(p,u){s=p,(!c||u&20)&&J(n,"active",s[2]===s[6].code)},i(p){c||(z(a.$$.fragment,p),c=!0)},o(p){G(a.$$.fragment,p),c=!1},d(p){p&&d(n),he(a)}}}function ct(i){var Ne,Ue;let s,n,a=i[0].name+"",w,c,p,u,C,h,F,N=i[0].name+"",K,ve,W,g,X,B,Y,$,U,we,j,E,ye,Z,Q=i[0].name+"",ee,$e,te,Ce,le,x,se,A,ne,I,oe,O,ie,Re,ae,D,re,Fe,de,ge,k,Oe,S,De,Pe,Te,ce,Ee,pe,Se,Be,xe,fe,Ae,ue,M,be,P,H,R=[],Ie=new Map,Me,q,y=[],He=new Map,T;g=new dt({props:{js:` import{S as Ze,i as et,s as tt,N as Ye,e as o,w as m,b as f,c as _e,f as _,g as r,h as l,m as ke,x as me,O as Ve,P as lt,k as st,Q as nt,n as ot,t as z,a as G,o as d,d as he,R as it,C as ze,p as at,r as J,u as rt}from"./index-ffbb9561.js";import{S as dt}from"./SdkTabs-5b973c0d.js";function Ge(i,s,n){const a=i.slice();return a[6]=s[n],a}function Je(i,s,n){const a=i.slice();return a[6]=s[n],a}function Ke(i){let s;return{c(){s=o("p"),s.innerHTML="Requires admin <code>Authorization:TOKEN</code> header",_(s,"class","txt-hint txt-sm txt-right")},m(n,a){r(n,s,a)},d(n){n&&d(s)}}}function We(i,s){let n,a=s[6].code+"",w,c,p,u;function C(){return s[5](s[6])}return{key:i,first:null,c(){n=o("button"),w=m(a),c=f(),_(n,"class","tab-item"),J(n,"active",s[2]===s[6].code),this.first=n},m(h,F){r(h,n,F),l(n,w),l(n,c),p||(u=rt(n,"click",C),p=!0)},p(h,F){s=h,F&20&&J(n,"active",s[2]===s[6].code)},d(h){h&&d(n),p=!1,u()}}}function Xe(i,s){let n,a,w,c;return a=new Ye({props:{content:s[6].body}}),{key:i,first:null,c(){n=o("div"),_e(a.$$.fragment),w=f(),_(n,"class","tab-item"),J(n,"active",s[2]===s[6].code),this.first=n},m(p,u){r(p,n,u),ke(a,n,null),l(n,w),c=!0},p(p,u){s=p,(!c||u&20)&&J(n,"active",s[2]===s[6].code)},i(p){c||(z(a.$$.fragment,p),c=!0)},o(p){G(a.$$.fragment,p),c=!1},d(p){p&&d(n),he(a)}}}function ct(i){var Ne,Ue;let s,n,a=i[0].name+"",w,c,p,u,C,h,F,N=i[0].name+"",K,ve,W,g,X,B,Y,$,U,we,j,E,ye,Z,Q=i[0].name+"",ee,$e,te,Ce,le,x,se,A,ne,I,oe,O,ie,Re,ae,D,re,Fe,de,ge,k,Oe,S,De,Pe,Te,ce,Ee,pe,Se,Be,xe,fe,Ae,ue,M,be,P,H,R=[],Ie=new Map,Me,q,y=[],He=new Map,T;g=new dt({props:{js:`
import PocketBase from 'pocketbase'; import PocketBase from 'pocketbase';
const pb = new PocketBase('${i[3]}'); const pb = new PocketBase('${i[3]}');

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

13
ui/dist/assets/index-a6ccb683.js vendored Normal file

File diff suppressed because one or more lines are too long

1
ui/dist/assets/index-b8b82b53.css vendored Normal file

File diff suppressed because one or more lines are too long

204
ui/dist/assets/index-ffbb9561.js vendored Normal file

File diff suppressed because one or more lines are too long

26
ui/dist/index.html vendored
View File

@ -17,15 +17,35 @@
<link rel="mask-icon" href="./images/favicon/safari-pinned-tab.svg" color="#000000"> <link rel="mask-icon" href="./images/favicon/safari-pinned-tab.svg" color="#000000">
<link rel="shortcut icon" href="./images/favicon/favicon.ico"> <link rel="shortcut icon" href="./images/favicon/favicon.ico">
<meta name="msapplication-TileColor" content="#ffffff"> <meta name="msapplication-TileColor" content="#ffffff">
<meta name="msapplication-config" content="./images/favicon/browserconfig.xml"> <meta name="msapplication-config" content="/images/favicon/browserconfig.xml">
<meta name="theme-color" content="#ffffff"> <meta name="theme-color" content="#ffffff">
<!-- prefetch common tinymce resources to speed up the initial loading times -->
<link rel="prefetch" href="./libs/tinymce/skins/content/default/content.min.css" as="style" />
<link rel="prefetch" href="./libs/tinymce/skins/ui/pocketbase/skin.min.css" as="style" />
<link rel="prefetch" href="./libs/tinymce/skins/ui/pocketbase/content.min.css" as="style" />
<link rel="prefetch" href="./libs/tinymce/tinymce.min.js" as="script" />
<link rel="prefetch" href="./libs/tinymce/themes/silver/theme.min.js" as="script" />
<link rel="prefetch" href="./libs/tinymce/models/dom/model.min.js" as="script" />
<link rel="prefetch" href="./libs/tinymce/icons/default/icons.min.js" as="script" />
<link rel="prefetch" href="./libs/tinymce/plugins/autoresize/plugin.min.js" as="script" />
<link rel="prefetch" href="./libs/tinymce/plugins/autolink/plugin.min.js" as="script" />
<link rel="prefetch" href="./libs/tinymce/plugins/lists/plugin.min.js" as="script" />
<link rel="prefetch" href="./libs/tinymce/plugins/link/plugin.min.js" as="script" />
<link rel="prefetch" href="./libs/tinymce/plugins/image/plugin.min.js" as="script" />
<link rel="prefetch" href="./libs/tinymce/plugins/searchreplace/plugin.min.js" as="script" />
<link rel="prefetch" href="./libs/tinymce/plugins/fullscreen/plugin.min.js" as="script" />
<link rel="prefetch" href="./libs/tinymce/plugins/media/plugin.min.js" as="script" />
<link rel="prefetch" href="./libs/tinymce/plugins/table/plugin.min.js" as="script" />
<link rel="prefetch" href="./libs/tinymce/plugins/code/plugin.min.js" as="script" />
<link rel="prefetch" href="./libs/tinymce/plugins/codesample/plugin.min.js" as="script" />
<script> <script>
window.Prism = window.Prism || {}; window.Prism = window.Prism || {};
window.Prism.manual = true; window.Prism.manual = true;
</script> </script>
<script type="module" crossorigin src="./assets/index-3e0f12d8.js"></script> <script type="module" crossorigin src="./assets/index-ffbb9561.js"></script>
<link rel="stylesheet" href="./assets/index-7f786dc0.css"> <link rel="stylesheet" href="./assets/index-b8b82b53.css">
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

View File

@ -10,16 +10,36 @@
<title>PocketBase</title> <title>PocketBase</title>
<link rel="apple-touch-icon" sizes="180x180" href="./images/favicon/apple-touch-icon.png"> <link rel="apple-touch-icon" sizes="180x180" href="/images/favicon/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="./images/favicon/favicon-32x32.png"> <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="./images/favicon/favicon-16x16.png"> <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon/favicon-16x16.png">
<link rel="manifest" href="./images/favicon/site.webmanifest"> <link rel="manifest" href="/images/favicon/site.webmanifest">
<link rel="mask-icon" href="./images/favicon/safari-pinned-tab.svg" color="#000000"> <link rel="mask-icon" href="/images/favicon/safari-pinned-tab.svg" color="#000000">
<link rel="shortcut icon" href="./images/favicon/favicon.ico"> <link rel="shortcut icon" href="/images/favicon/favicon.ico">
<meta name="msapplication-TileColor" content="#ffffff"> <meta name="msapplication-TileColor" content="#ffffff">
<meta name="msapplication-config" content="./images/favicon/browserconfig.xml"> <meta name="msapplication-config" content="/images/favicon/browserconfig.xml">
<meta name="theme-color" content="#ffffff"> <meta name="theme-color" content="#ffffff">
<!-- prefetch common tinymce resources to speed up the initial loading times -->
<link rel="prefetch" href="/libs/tinymce/skins/content/default/content.min.css" as="style" />
<link rel="prefetch" href="/libs/tinymce/skins/ui/pocketbase/skin.min.css" as="style" />
<link rel="prefetch" href="/libs/tinymce/skins/ui/pocketbase/content.min.css" as="style" />
<link rel="prefetch" href="/libs/tinymce/tinymce.min.js" as="script" />
<link rel="prefetch" href="/libs/tinymce/themes/silver/theme.min.js" as="script" />
<link rel="prefetch" href="/libs/tinymce/models/dom/model.min.js" as="script" />
<link rel="prefetch" href="/libs/tinymce/icons/default/icons.min.js" as="script" />
<link rel="prefetch" href="/libs/tinymce/plugins/autoresize/plugin.min.js" as="script" />
<link rel="prefetch" href="/libs/tinymce/plugins/autolink/plugin.min.js" as="script" />
<link rel="prefetch" href="/libs/tinymce/plugins/lists/plugin.min.js" as="script" />
<link rel="prefetch" href="/libs/tinymce/plugins/link/plugin.min.js" as="script" />
<link rel="prefetch" href="/libs/tinymce/plugins/image/plugin.min.js" as="script" />
<link rel="prefetch" href="/libs/tinymce/plugins/searchreplace/plugin.min.js" as="script" />
<link rel="prefetch" href="/libs/tinymce/plugins/fullscreen/plugin.min.js" as="script" />
<link rel="prefetch" href="/libs/tinymce/plugins/media/plugin.min.js" as="script" />
<link rel="prefetch" href="/libs/tinymce/plugins/table/plugin.min.js" as="script" />
<link rel="prefetch" href="/libs/tinymce/plugins/code/plugin.min.js" as="script" />
<link rel="prefetch" href="/libs/tinymce/plugins/codesample/plugin.min.js" as="script" />
<script> <script>
window.Prism = window.Prism || {}; window.Prism = window.Prism || {};
window.Prism.manual = true; window.Prism.manual = true;

225
ui/package-lock.json generated
View File

@ -10,6 +10,7 @@
"@codemirror/commands": "^6.0.0", "@codemirror/commands": "^6.0.0",
"@codemirror/lang-html": "^6.1.0", "@codemirror/lang-html": "^6.1.0",
"@codemirror/lang-javascript": "^6.0.2", "@codemirror/lang-javascript": "^6.0.2",
"@codemirror/lang-sql": "^6.4.0",
"@codemirror/language": "^6.0.0", "@codemirror/language": "^6.0.0",
"@codemirror/legacy-modes": "^6.0.0", "@codemirror/legacy-modes": "^6.0.0",
"@codemirror/search": "^6.0.0", "@codemirror/search": "^6.0.0",
@ -20,7 +21,7 @@
"chart.js": "^3.7.1", "chart.js": "^3.7.1",
"chartjs-adapter-luxon": "^1.2.0", "chartjs-adapter-luxon": "^1.2.0",
"luxon": "^2.3.2", "luxon": "^2.3.2",
"pocketbase": "^0.10.0", "pocketbase": "^0.10.2",
"prismjs": "^1.28.0", "prismjs": "^1.28.0",
"sass": "^1.45.0", "sass": "^1.45.0",
"svelte": "^3.44.0", "svelte": "^3.44.0",
@ -30,9 +31,9 @@
} }
}, },
"node_modules/@codemirror/autocomplete": { "node_modules/@codemirror/autocomplete": {
"version": "6.4.0", "version": "6.4.1",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.4.0.tgz", "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.4.1.tgz",
"integrity": "sha512-HLF2PnZAm1s4kGs30EiqKMgD7XsYaQ0XJnMR0rofEWQ5t5D60SfqpDIkIh1ze5tiEbyUWm8+VJ6W1/erVvBMIA==", "integrity": "sha512-06yAmj0FjPZzYOpNeugJtG28GNqU2/CPr34m91Q+fKSyTOR6+hDFiatkPcIkxOlU0K5yP7WH6KoLg3fTqIUgaw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@codemirror/language": "^6.0.0", "@codemirror/language": "^6.0.0",
@ -48,9 +49,9 @@
} }
}, },
"node_modules/@codemirror/commands": { "node_modules/@codemirror/commands": {
"version": "6.2.0", "version": "6.2.1",
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.2.0.tgz", "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.2.1.tgz",
"integrity": "sha512-+00smmZBradoGFEkRjliN7BjqPh/Hx0KCHWOEibUmflUqZz2RwBTU0MrVovEEHozhx3AUSGcO/rl3/5f9e9Biw==", "integrity": "sha512-FFiNKGuHA5O8uC6IJE5apI5rT9gyjlw4whqy4vlcX0wE/myxL6P1s0upwDhY4HtMWLOwzwsp0ap3bjdQhvfDOA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@codemirror/language": "^6.0.0", "@codemirror/language": "^6.0.0",
@ -72,9 +73,9 @@
} }
}, },
"node_modules/@codemirror/lang-html": { "node_modules/@codemirror/lang-html": {
"version": "6.4.1", "version": "6.4.2",
"resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.1.tgz", "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.2.tgz",
"integrity": "sha512-9NzhWKAkWEwjXC04DKM6yrHnxIPFTqZNLDhWfZiKLMxUiU++XoHz9n6D5EPp1igBmX0vXcpFb5Kud6XzIJhZ4A==", "integrity": "sha512-bqCBASkteKySwtIbiV/WCtGnn/khLRbbiV5TE+d9S9eQJD7BA4c5dTRm2b3bVmSpilff5EYxvB4PQaZzM/7cNw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@codemirror/autocomplete": "^6.0.0", "@codemirror/autocomplete": "^6.0.0",
@ -89,13 +90,13 @@
} }
}, },
"node_modules/@codemirror/lang-javascript": { "node_modules/@codemirror/lang-javascript": {
"version": "6.1.2", "version": "6.1.4",
"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.1.2.tgz", "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.1.4.tgz",
"integrity": "sha512-OcwLfZXdQ1OHrLiIcKCn7MqZ7nx205CMKlhe+vL88pe2ymhT9+2P+QhwkYGxMICj8TDHyp8HFKVwpiisUT7iEQ==", "integrity": "sha512-OxLf7OfOZBTMRMi6BO/F72MNGmgOd9B0vetOLvHsDACFXayBzW8fm8aWnDM0yuy68wTK03MBf4HbjSBNRG5q7A==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@codemirror/autocomplete": "^6.0.0", "@codemirror/autocomplete": "^6.0.0",
"@codemirror/language": "^6.0.0", "@codemirror/language": "^6.6.0",
"@codemirror/lint": "^6.0.0", "@codemirror/lint": "^6.0.0",
"@codemirror/state": "^6.0.0", "@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0", "@codemirror/view": "^6.0.0",
@ -103,10 +104,23 @@
"@lezer/javascript": "^1.0.0" "@lezer/javascript": "^1.0.0"
} }
}, },
"node_modules/@codemirror/language": { "node_modules/@codemirror/lang-sql": {
"version": "6.4.0", "version": "6.4.0",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.4.0.tgz", "resolved": "https://registry.npmjs.org/@codemirror/lang-sql/-/lang-sql-6.4.0.tgz",
"integrity": "sha512-Wzb7GnNj8vnEtbPWiOy9H0m1fBtE28kepQNGLXekU2EEZv43BF865VKITUn+NoV8OpW6gRtvm29YEhqm46927Q==", "integrity": "sha512-UWGK1+zc9+JtkiT+XxHByp4N6VLgLvC2x0tIudrJG26gyNtn0hWOVoB0A8kh/NABPWkKl3tLWDYf2qOBJS9Zdw==",
"dev": true,
"dependencies": {
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0"
}
},
"node_modules/@codemirror/language": {
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.6.0.tgz",
"integrity": "sha512-cwUd6lzt3MfNYOobdjf14ZkLbJcnv4WtndYaoBkbor/vF+rCNguMPK0IRtvZJG4dsWiaWPcK8x1VijhvSxnstg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@codemirror/state": "^6.0.0", "@codemirror/state": "^6.0.0",
@ -127,9 +141,9 @@
} }
}, },
"node_modules/@codemirror/lint": { "node_modules/@codemirror/lint": {
"version": "6.1.0", "version": "6.1.1",
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.1.0.tgz", "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.1.1.tgz",
"integrity": "sha512-mdvDQrjRmYPvQ3WrzF6Ewaao+NWERYtpthJvoQ3tK3t/44Ynhk8ZGjTSL9jMEv8CgSMogmt75X8ceOZRDSXHtQ==", "integrity": "sha512-e+M543x0NVHGayNHQzLP4XByJsvbu/ojY6+0VF2Y4Uu66Rt1nADuxNflZwECLf7gS009smIsptSUa6bUj/U/rw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@codemirror/state": "^6.0.0", "@codemirror/state": "^6.0.0",
@ -155,9 +169,9 @@
"dev": true "dev": true
}, },
"node_modules/@codemirror/view": { "node_modules/@codemirror/view": {
"version": "6.7.3", "version": "6.9.0",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.7.3.tgz", "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.9.0.tgz",
"integrity": "sha512-Lt+4POnhXrZFfHOdPzXEHxrzwdy7cjqYlMkOWvoFGi6/bAsjzlFfr0NY3B15B/PGx+cDFgM1hlc12wvYeZbGLw==", "integrity": "sha512-uFaqE6fBOp0Dj/tmWoe/TFlSSIPdpAzhvATgbq1eAKRkgq3hY79FioZ7nfdiT+24kz68AIWuSZ9hi3psKe36WQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@codemirror/state": "^6.1.4", "@codemirror/state": "^6.1.4",
@ -549,9 +563,9 @@
} }
}, },
"node_modules/@lezer/html": { "node_modules/@lezer/html": {
"version": "1.3.0", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.0.tgz", "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.2.tgz",
"integrity": "sha512-jU/ah8DEoiECLTMouU/X/ujIg6k9WQMIOFMaCLebzaXfrguyGaR3DpTgmk0tbljiuIJ7hlmVJPcJcxGzmCd0Mg==", "integrity": "sha512-LKGyDdqqDugXR/lKM9FwaKEfMerbZ/aqvhLf0P1FLLK/pVP7wKHXGcg6g3cJ7ckvFidn0tXA8jioG0irVsCBAQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@lezer/common": "^1.0.0", "@lezer/common": "^1.0.0",
@ -570,9 +584,9 @@
} }
}, },
"node_modules/@lezer/lr": { "node_modules/@lezer/lr": {
"version": "1.3.2", "version": "1.3.3",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.2.tgz", "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.3.tgz",
"integrity": "sha512-SDSvnHWMBH6WxoOt51AjuHOiQ0DMxxhfK5lNoyJXuv5POWz6MfXKGU9Fus9tK8NqrI1sSBNdKtG5cUXXZtGG5w==", "integrity": "sha512-JPQe3mwJlzEVqy67iQiiGozhcngbO8QBgpqZM6oL1Wj/dXckrEexpBLeFkq0edtW5IqnPRFxA24BHJni8Js69w==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@lezer/common": "^1.0.0" "@lezer/common": "^1.0.0"
@ -649,9 +663,9 @@
"dev": true "dev": true
}, },
"node_modules/chartjs-adapter-luxon": { "node_modules/chartjs-adapter-luxon": {
"version": "1.3.0", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/chartjs-adapter-luxon/-/chartjs-adapter-luxon-1.3.0.tgz", "resolved": "https://registry.npmjs.org/chartjs-adapter-luxon/-/chartjs-adapter-luxon-1.3.1.tgz",
"integrity": "sha512-TPqS8S7aw4a07LhFzG5DZU6Kduk1xFkaGTn8y/gfhBRvfyCkqnwFqfXqG9Gl+gmnj5DRXgPscApJUE6bsgzKjQ==", "integrity": "sha512-yxHov3X8y+reIibl1o+j18xzrcdddCLqsXhriV2+aQ4hCR66IYFchlRXUvrJVoxglJ380pgytU7YWtoqdIgqhg==",
"dev": true, "dev": true,
"peerDependencies": { "peerDependencies": {
"chart.js": ">=3.0.0", "chart.js": ">=3.0.0",
@ -817,9 +831,9 @@
} }
}, },
"node_modules/immutable": { "node_modules/immutable": {
"version": "4.2.2", "version": "4.2.4",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.2.tgz", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.4.tgz",
"integrity": "sha512-fTMKDwtbvO5tldky9QZ2fMX7slR0mYpY5nbnFWYp0fOzDhHqhgIw9KoYgxLWsoNTS9ZHGauHj18DTyEw6BK3Og==", "integrity": "sha512-WDxL3Hheb1JkRN3sQkyujNlL/xRjAo3rJtaU5xeufUauG66JdMr32bLj4gF+vWl84DIA3Zxw7tiAjneYzRRw+w==",
"dev": true "dev": true
}, },
"node_modules/is-binary-path": { "node_modules/is-binary-path": {
@ -958,9 +972,9 @@
} }
}, },
"node_modules/pocketbase": { "node_modules/pocketbase": {
"version": "0.10.1", "version": "0.10.2",
"resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.10.1.tgz", "resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.10.2.tgz",
"integrity": "sha512-FMBhF+9o2AmdKJYbfz2qunnk4Q5/efYMRcukdQF49UfhsIuaSYl39fSPN1l880bYI4XAvDDeySAjK1MlxrK37A==", "integrity": "sha512-728aVaPLhSwOyEqZ4+GJfqg1SHUAyokvErsOksxauvOXiRE/QSCn8s6bW1/KyU3NGqz248eBjUQY3aLwTV/CcA==",
"dev": true "dev": true
}, },
"node_modules/postcss": { "node_modules/postcss": {
@ -1035,9 +1049,9 @@
} }
}, },
"node_modules/rollup": { "node_modules/rollup": {
"version": "3.12.1", "version": "3.15.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.12.1.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.15.0.tgz",
"integrity": "sha512-t9elERrz2i4UU9z7AwISj3CQcXP39cWxgRWLdf4Tm6aKm1eYrqHIgjzXBgb67GNY1sZckTFFi0oMozh3/S++Ig==", "integrity": "sha512-F9hrCAhnp5/zx/7HYmftvsNBkMfLfk/dXUh73hPSM2E3CRgap65orDNJbLetoiUFwSAk6iHPLvBrZ5iHYvzqsg==",
"dev": true, "dev": true,
"bin": { "bin": {
"rollup": "dist/bin/rollup" "rollup": "dist/bin/rollup"
@ -1057,9 +1071,9 @@
"dev": true "dev": true
}, },
"node_modules/sass": { "node_modules/sass": {
"version": "1.58.0", "version": "1.58.1",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.58.0.tgz", "resolved": "https://registry.npmjs.org/sass/-/sass-1.58.1.tgz",
"integrity": "sha512-PiMJcP33DdKtZ/1jSjjqVIKihoDc6yWmYr9K/4r3fVVIEDAluD0q7XZiRKrNJcPK3qkLRF/79DND1H5q1LBjgg==", "integrity": "sha512-bnINi6nPXbP1XNRaranMFEBZWUfdW/AF16Ql5+ypRxfTvCRTTKrLsMIakyDcayUt2t/RZotmL4kgJwNH5xO+bg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"chokidar": ">=3.0.0 <4.0.0", "chokidar": ">=3.0.0 <4.0.0",
@ -1158,15 +1172,15 @@
} }
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "4.0.4", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.0.4.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-4.1.1.tgz",
"integrity": "sha512-xevPU7M8FU0i/80DMR+YhgrzR5KS2ORy1B4xcX/cXLsvnUWvfHuqMmVU6N0YiJ4JWGRJJsLCgjEzKjG9/GKoSw==", "integrity": "sha512-LM9WWea8vsxhr782r9ntg+bhSFS06FJgCvvB0+8hf8UWtvaiDagKYWXndjfX6kGl74keHJUcpzrQliDXZlF5yg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"esbuild": "^0.16.3", "esbuild": "^0.16.14",
"postcss": "^8.4.20", "postcss": "^8.4.21",
"resolve": "^1.22.1", "resolve": "^1.22.1",
"rollup": "^3.7.0" "rollup": "^3.10.0"
}, },
"bin": { "bin": {
"vite": "bin/vite.js" "vite": "bin/vite.js"
@ -1229,9 +1243,9 @@
}, },
"dependencies": { "dependencies": {
"@codemirror/autocomplete": { "@codemirror/autocomplete": {
"version": "6.4.0", "version": "6.4.1",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.4.0.tgz", "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.4.1.tgz",
"integrity": "sha512-HLF2PnZAm1s4kGs30EiqKMgD7XsYaQ0XJnMR0rofEWQ5t5D60SfqpDIkIh1ze5tiEbyUWm8+VJ6W1/erVvBMIA==", "integrity": "sha512-06yAmj0FjPZzYOpNeugJtG28GNqU2/CPr34m91Q+fKSyTOR6+hDFiatkPcIkxOlU0K5yP7WH6KoLg3fTqIUgaw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@codemirror/language": "^6.0.0", "@codemirror/language": "^6.0.0",
@ -1241,9 +1255,9 @@
} }
}, },
"@codemirror/commands": { "@codemirror/commands": {
"version": "6.2.0", "version": "6.2.1",
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.2.0.tgz", "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.2.1.tgz",
"integrity": "sha512-+00smmZBradoGFEkRjliN7BjqPh/Hx0KCHWOEibUmflUqZz2RwBTU0MrVovEEHozhx3AUSGcO/rl3/5f9e9Biw==", "integrity": "sha512-FFiNKGuHA5O8uC6IJE5apI5rT9gyjlw4whqy4vlcX0wE/myxL6P1s0upwDhY4HtMWLOwzwsp0ap3bjdQhvfDOA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@codemirror/language": "^6.0.0", "@codemirror/language": "^6.0.0",
@ -1265,9 +1279,9 @@
} }
}, },
"@codemirror/lang-html": { "@codemirror/lang-html": {
"version": "6.4.1", "version": "6.4.2",
"resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.1.tgz", "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.2.tgz",
"integrity": "sha512-9NzhWKAkWEwjXC04DKM6yrHnxIPFTqZNLDhWfZiKLMxUiU++XoHz9n6D5EPp1igBmX0vXcpFb5Kud6XzIJhZ4A==", "integrity": "sha512-bqCBASkteKySwtIbiV/WCtGnn/khLRbbiV5TE+d9S9eQJD7BA4c5dTRm2b3bVmSpilff5EYxvB4PQaZzM/7cNw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@codemirror/autocomplete": "^6.0.0", "@codemirror/autocomplete": "^6.0.0",
@ -1282,13 +1296,13 @@
} }
}, },
"@codemirror/lang-javascript": { "@codemirror/lang-javascript": {
"version": "6.1.2", "version": "6.1.4",
"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.1.2.tgz", "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.1.4.tgz",
"integrity": "sha512-OcwLfZXdQ1OHrLiIcKCn7MqZ7nx205CMKlhe+vL88pe2ymhT9+2P+QhwkYGxMICj8TDHyp8HFKVwpiisUT7iEQ==", "integrity": "sha512-OxLf7OfOZBTMRMi6BO/F72MNGmgOd9B0vetOLvHsDACFXayBzW8fm8aWnDM0yuy68wTK03MBf4HbjSBNRG5q7A==",
"dev": true, "dev": true,
"requires": { "requires": {
"@codemirror/autocomplete": "^6.0.0", "@codemirror/autocomplete": "^6.0.0",
"@codemirror/language": "^6.0.0", "@codemirror/language": "^6.6.0",
"@codemirror/lint": "^6.0.0", "@codemirror/lint": "^6.0.0",
"@codemirror/state": "^6.0.0", "@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0", "@codemirror/view": "^6.0.0",
@ -1296,10 +1310,23 @@
"@lezer/javascript": "^1.0.0" "@lezer/javascript": "^1.0.0"
} }
}, },
"@codemirror/language": { "@codemirror/lang-sql": {
"version": "6.4.0", "version": "6.4.0",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.4.0.tgz", "resolved": "https://registry.npmjs.org/@codemirror/lang-sql/-/lang-sql-6.4.0.tgz",
"integrity": "sha512-Wzb7GnNj8vnEtbPWiOy9H0m1fBtE28kepQNGLXekU2EEZv43BF865VKITUn+NoV8OpW6gRtvm29YEhqm46927Q==", "integrity": "sha512-UWGK1+zc9+JtkiT+XxHByp4N6VLgLvC2x0tIudrJG26gyNtn0hWOVoB0A8kh/NABPWkKl3tLWDYf2qOBJS9Zdw==",
"dev": true,
"requires": {
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0"
}
},
"@codemirror/language": {
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.6.0.tgz",
"integrity": "sha512-cwUd6lzt3MfNYOobdjf14ZkLbJcnv4WtndYaoBkbor/vF+rCNguMPK0IRtvZJG4dsWiaWPcK8x1VijhvSxnstg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@codemirror/state": "^6.0.0", "@codemirror/state": "^6.0.0",
@ -1320,9 +1347,9 @@
} }
}, },
"@codemirror/lint": { "@codemirror/lint": {
"version": "6.1.0", "version": "6.1.1",
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.1.0.tgz", "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.1.1.tgz",
"integrity": "sha512-mdvDQrjRmYPvQ3WrzF6Ewaao+NWERYtpthJvoQ3tK3t/44Ynhk8ZGjTSL9jMEv8CgSMogmt75X8ceOZRDSXHtQ==", "integrity": "sha512-e+M543x0NVHGayNHQzLP4XByJsvbu/ojY6+0VF2Y4Uu66Rt1nADuxNflZwECLf7gS009smIsptSUa6bUj/U/rw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@codemirror/state": "^6.0.0", "@codemirror/state": "^6.0.0",
@ -1348,9 +1375,9 @@
"dev": true "dev": true
}, },
"@codemirror/view": { "@codemirror/view": {
"version": "6.7.3", "version": "6.9.0",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.7.3.tgz", "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.9.0.tgz",
"integrity": "sha512-Lt+4POnhXrZFfHOdPzXEHxrzwdy7cjqYlMkOWvoFGi6/bAsjzlFfr0NY3B15B/PGx+cDFgM1hlc12wvYeZbGLw==", "integrity": "sha512-uFaqE6fBOp0Dj/tmWoe/TFlSSIPdpAzhvATgbq1eAKRkgq3hY79FioZ7nfdiT+24kz68AIWuSZ9hi3psKe36WQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@codemirror/state": "^6.1.4", "@codemirror/state": "^6.1.4",
@ -1544,9 +1571,9 @@
} }
}, },
"@lezer/html": { "@lezer/html": {
"version": "1.3.0", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.0.tgz", "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.2.tgz",
"integrity": "sha512-jU/ah8DEoiECLTMouU/X/ujIg6k9WQMIOFMaCLebzaXfrguyGaR3DpTgmk0tbljiuIJ7hlmVJPcJcxGzmCd0Mg==", "integrity": "sha512-LKGyDdqqDugXR/lKM9FwaKEfMerbZ/aqvhLf0P1FLLK/pVP7wKHXGcg6g3cJ7ckvFidn0tXA8jioG0irVsCBAQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@lezer/common": "^1.0.0", "@lezer/common": "^1.0.0",
@ -1565,9 +1592,9 @@
} }
}, },
"@lezer/lr": { "@lezer/lr": {
"version": "1.3.2", "version": "1.3.3",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.2.tgz", "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.3.tgz",
"integrity": "sha512-SDSvnHWMBH6WxoOt51AjuHOiQ0DMxxhfK5lNoyJXuv5POWz6MfXKGU9Fus9tK8NqrI1sSBNdKtG5cUXXZtGG5w==", "integrity": "sha512-JPQe3mwJlzEVqy67iQiiGozhcngbO8QBgpqZM6oL1Wj/dXckrEexpBLeFkq0edtW5IqnPRFxA24BHJni8Js69w==",
"dev": true, "dev": true,
"requires": { "requires": {
"@lezer/common": "^1.0.0" "@lezer/common": "^1.0.0"
@ -1628,9 +1655,9 @@
"dev": true "dev": true
}, },
"chartjs-adapter-luxon": { "chartjs-adapter-luxon": {
"version": "1.3.0", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/chartjs-adapter-luxon/-/chartjs-adapter-luxon-1.3.0.tgz", "resolved": "https://registry.npmjs.org/chartjs-adapter-luxon/-/chartjs-adapter-luxon-1.3.1.tgz",
"integrity": "sha512-TPqS8S7aw4a07LhFzG5DZU6Kduk1xFkaGTn8y/gfhBRvfyCkqnwFqfXqG9Gl+gmnj5DRXgPscApJUE6bsgzKjQ==", "integrity": "sha512-yxHov3X8y+reIibl1o+j18xzrcdddCLqsXhriV2+aQ4hCR66IYFchlRXUvrJVoxglJ380pgytU7YWtoqdIgqhg==",
"dev": true, "dev": true,
"requires": {} "requires": {}
}, },
@ -1748,9 +1775,9 @@
} }
}, },
"immutable": { "immutable": {
"version": "4.2.2", "version": "4.2.4",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.2.tgz", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.4.tgz",
"integrity": "sha512-fTMKDwtbvO5tldky9QZ2fMX7slR0mYpY5nbnFWYp0fOzDhHqhgIw9KoYgxLWsoNTS9ZHGauHj18DTyEw6BK3Og==", "integrity": "sha512-WDxL3Hheb1JkRN3sQkyujNlL/xRjAo3rJtaU5xeufUauG66JdMr32bLj4gF+vWl84DIA3Zxw7tiAjneYzRRw+w==",
"dev": true "dev": true
}, },
"is-binary-path": { "is-binary-path": {
@ -1850,9 +1877,9 @@
"dev": true "dev": true
}, },
"pocketbase": { "pocketbase": {
"version": "0.10.1", "version": "0.10.2",
"resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.10.1.tgz", "resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.10.2.tgz",
"integrity": "sha512-FMBhF+9o2AmdKJYbfz2qunnk4Q5/efYMRcukdQF49UfhsIuaSYl39fSPN1l880bYI4XAvDDeySAjK1MlxrK37A==", "integrity": "sha512-728aVaPLhSwOyEqZ4+GJfqg1SHUAyokvErsOksxauvOXiRE/QSCn8s6bW1/KyU3NGqz248eBjUQY3aLwTV/CcA==",
"dev": true "dev": true
}, },
"postcss": { "postcss": {
@ -1899,9 +1926,9 @@
} }
}, },
"rollup": { "rollup": {
"version": "3.12.1", "version": "3.15.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.12.1.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.15.0.tgz",
"integrity": "sha512-t9elERrz2i4UU9z7AwISj3CQcXP39cWxgRWLdf4Tm6aKm1eYrqHIgjzXBgb67GNY1sZckTFFi0oMozh3/S++Ig==", "integrity": "sha512-F9hrCAhnp5/zx/7HYmftvsNBkMfLfk/dXUh73hPSM2E3CRgap65orDNJbLetoiUFwSAk6iHPLvBrZ5iHYvzqsg==",
"dev": true, "dev": true,
"requires": { "requires": {
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
@ -1914,9 +1941,9 @@
"dev": true "dev": true
}, },
"sass": { "sass": {
"version": "1.58.0", "version": "1.58.1",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.58.0.tgz", "resolved": "https://registry.npmjs.org/sass/-/sass-1.58.1.tgz",
"integrity": "sha512-PiMJcP33DdKtZ/1jSjjqVIKihoDc6yWmYr9K/4r3fVVIEDAluD0q7XZiRKrNJcPK3qkLRF/79DND1H5q1LBjgg==", "integrity": "sha512-bnINi6nPXbP1XNRaranMFEBZWUfdW/AF16Ql5+ypRxfTvCRTTKrLsMIakyDcayUt2t/RZotmL4kgJwNH5xO+bg==",
"dev": true, "dev": true,
"requires": { "requires": {
"chokidar": ">=3.0.0 <4.0.0", "chokidar": ">=3.0.0 <4.0.0",
@ -1983,16 +2010,16 @@
} }
}, },
"vite": { "vite": {
"version": "4.0.4", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.0.4.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-4.1.1.tgz",
"integrity": "sha512-xevPU7M8FU0i/80DMR+YhgrzR5KS2ORy1B4xcX/cXLsvnUWvfHuqMmVU6N0YiJ4JWGRJJsLCgjEzKjG9/GKoSw==", "integrity": "sha512-LM9WWea8vsxhr782r9ntg+bhSFS06FJgCvvB0+8hf8UWtvaiDagKYWXndjfX6kGl74keHJUcpzrQliDXZlF5yg==",
"dev": true, "dev": true,
"requires": { "requires": {
"esbuild": "^0.16.3", "esbuild": "^0.16.14",
"fsevents": "~2.3.2", "fsevents": "~2.3.2",
"postcss": "^8.4.20", "postcss": "^8.4.21",
"resolve": "^1.22.1", "resolve": "^1.22.1",
"rollup": "^3.7.0" "rollup": "^3.10.0"
} }
}, },
"vitefu": { "vitefu": {

View File

@ -17,6 +17,7 @@
"@codemirror/commands": "^6.0.0", "@codemirror/commands": "^6.0.0",
"@codemirror/lang-html": "^6.1.0", "@codemirror/lang-html": "^6.1.0",
"@codemirror/lang-javascript": "^6.0.2", "@codemirror/lang-javascript": "^6.0.2",
"@codemirror/lang-sql": "^6.4.0",
"@codemirror/language": "^6.0.0", "@codemirror/language": "^6.0.0",
"@codemirror/legacy-modes": "^6.0.0", "@codemirror/legacy-modes": "^6.0.0",
"@codemirror/search": "^6.0.0", "@codemirror/search": "^6.0.0",
@ -27,7 +28,7 @@
"chart.js": "^3.7.1", "chart.js": "^3.7.1",
"chartjs-adapter-luxon": "^1.2.0", "chartjs-adapter-luxon": "^1.2.0",
"luxon": "^2.3.2", "luxon": "^2.3.2",
"pocketbase": "^0.10.0", "pocketbase": "^0.10.2",
"prismjs": "^1.28.0", "prismjs": "^1.28.0",
"sass": "^1.45.0", "sass": "^1.45.0",
"svelte": "^3.44.0", "svelte": "^3.44.0",

View File

@ -142,10 +142,10 @@
<form id={formId} class="grid" autocomplete="off" on:submit|preventDefault={save}> <form id={formId} class="grid" autocomplete="off" on:submit|preventDefault={save}>
{#if !admin.isNew} {#if !admin.isNew}
<Field class="form-field disabled" name="id" let:uniqueId> <Field class="form-field readonly" name="id" let:uniqueId>
<label for={uniqueId}> <label for={uniqueId}>
<i class={CommonHelper.getFieldTypeIcon("primary")} /> <i class={CommonHelper.getFieldTypeIcon("primary")} />
<span class="txt">ID</span> <span class="txt">id</span>
</label> </label>
<div class="form-field-addon"> <div class="form-field-addon">
<i <i
@ -156,7 +156,7 @@
}} }}
/> />
</div> </div>
<input type="text" id={uniqueId} value={admin.id} disabled /> <input type="text" id={uniqueId} value={admin.id} readonly />
</Field> </Field>
{/if} {/if}

View File

@ -53,13 +53,17 @@
closeBracketsKeymap, closeBracketsKeymap,
} from "@codemirror/autocomplete"; } from "@codemirror/autocomplete";
import { html as htmlLang } from "@codemirror/lang-html"; import { html as htmlLang } from "@codemirror/lang-html";
import { sql, SQLDialect } from "@codemirror/lang-sql";
import { javascript as javascriptLang } from "@codemirror/lang-javascript"; import { javascript as javascriptLang } from "@codemirror/lang-javascript";
// --- // ---
import CommonHelper from "@/utils/CommonHelper";
import { collections } from "@/stores/collections";
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
export let id = ""; export let id = "";
export let value = ""; export let value = "";
export let minHeight = null;
export let maxHeight = null; export let maxHeight = null;
export let disabled = false; export let disabled = false;
export let placeholder = ""; export let placeholder = "";
@ -123,6 +127,8 @@
bubbles: true, bubbles: true,
}) })
); );
dispatch("change", value);
} }
// Remove any attached label listeners. // Remove any attached label listeners.
@ -153,7 +159,33 @@
// Returns the current active editor language. // Returns the current active editor language.
function getEditorLang() { function getEditorLang() {
return language === "html" ? htmlLang() : javascriptLang(); switch (language) {
case "html":
return htmlLang();
case "sql":
let schema = {};
for (let collection of $collections) {
schema[collection.name] = CommonHelper.getAllCollectionIdentifiers(collection);
}
return sql({
// lightweight sql dialect with mostly SELECT statements keywords
dialect: SQLDialect.define({
keywords:
"select from where having group by order limit offset join left right inner with like not in match asc desc regexp isnull notnull glob " +
"count avg sum min max current random cast as int real text " +
"date time datetime unixepoch strftime coalesce lower upper substr " +
"case when then iif if else json_extract json_each json_tree json_array_length json_valid ",
operatorChars: "*+-%<>!=&|/~",
identifierQuotes: '`"',
specialVar: "@:?$",
}),
schema: schema,
upperCaseKeywords: true,
});
default:
return javascriptLang();
}
} }
onMount(() => { onMount(() => {
@ -222,4 +254,9 @@
}); });
</script> </script>
<div bind:this={container} class="code-editor" style:max-height={maxHeight ? maxHeight + "px" : "auto"} /> <div
bind:this={container}
class="code-editor"
style:min-height={minHeight ? minHeight + "px" : null}
style:max-height={maxHeight ? maxHeight + "px" : "auto"}
/>

View File

@ -217,25 +217,11 @@
return []; return [];
} }
let result = [ let result = CommonHelper.getAllCollectionIdentifiers(collection, prefix);
// base model fields
prefix + "id",
prefix + "created",
prefix + "updated",
];
if (collection.isAuth) {
result.push(prefix + "username");
result.push(prefix + "email");
result.push(prefix + "emailVisibility");
result.push(prefix + "verified");
}
for (const field of collection.schema) { for (const field of collection.schema) {
const key = prefix + field.name; const key = prefix + field.name;
result.push(key);
// add relation fields // add relation fields
if (field.type === "relation" && field.options?.collectionId) { if (field.type === "relation" && field.options?.collectionId) {
const subKeys = getCollectionFieldKeys(field.options.collectionId, key + ".", level + 1); const subKeys = getCollectionFieldKeys(field.options.collectionId, key + ".", level + 1);

View File

@ -93,6 +93,12 @@
if (!collection?.options.allowOAuth2Auth) { if (!collection?.options.allowOAuth2Auth) {
delete tabs["auth-with-oauth2"]; delete tabs["auth-with-oauth2"];
} }
} else if (collection.isView) {
tabs = Object.assign({}, baseTabs);
delete tabs.create;
delete tabs.update;
delete tabs.delete;
delete tabs.realtime;
} else { } else {
tabs = Object.assign({}, baseTabs); tabs = Object.assign({}, baseTabs);
} }

View File

@ -0,0 +1,105 @@
<script>
import { onMount } from "svelte";
import { Collection } from "pocketbase";
import { errors, removeError } from "@/stores/errors";
import CommonHelper from "@/utils/CommonHelper";
import Field from "@/components/base/Field.svelte";
export let collection = new Collection();
let codeEditorComponent;
let isCodeEditorComponentLoading = false;
let schemaErrors = [];
$: checkSchemaErrors($errors);
function checkSchemaErrors(errs) {
schemaErrors = [];
const raw = CommonHelper.getNestedVal(errs, "schema", null);
if (CommonHelper.isEmpty(raw)) {
return;
}
// generic schema error
// ---
if (raw?.message) {
schemaErrors.push(raw?.message);
return;
}
// schema fields error
// ---
const columns = CommonHelper.extractColumnsFromQuery(collection?.options?.query);
// remove base system fields
CommonHelper.removeByValue(columns, "id");
CommonHelper.removeByValue(columns, "created");
CommonHelper.removeByValue(columns, "updated");
for (let idx in raw) {
for (let key in raw[idx]) {
const message = raw[idx][key].message;
const fieldName = columns[idx] || idx;
schemaErrors.push(CommonHelper.sentenize(fieldName + ": " + message));
}
}
}
onMount(async () => {
isCodeEditorComponentLoading = true;
try {
codeEditorComponent = (await import("@/components/base/CodeEditor.svelte")).default;
} catch (err) {
console.warn(err);
}
isCodeEditorComponentLoading = false;
});
</script>
<Field class="form-field required {schemaErrors.length ? 'error' : ''}" name="options.query" let:uniqueId>
<label for={uniqueId}>Select query</label>
{#if isCodeEditorComponentLoading}
<textarea disabled rows="7" placeholder="Loading..." />
{:else}
<svelte:component
this={codeEditorComponent}
id={uniqueId}
placeholder="eg. SELECT id, name from posts"
language="sql"
minHeight="150"
on:change={() => {
if (schemaErrors.length) {
removeError("schema");
}
}}
bind:value={collection.options.query}
/>
{/if}
<div class="help-block">
<ul>
<li>Wildcard (<code>*</code>) columns are not supported.</li>
<li>
The query must have a unique <code>id</code> column.
<br />
If your query doesn't have a suitable one, you can use
<code>(ROW_NUMBER() OVER()) as id</code>.
</li>
</ul>
</div>
{#if schemaErrors.length}
<div class="help-block help-block-error">
<div class="content">
{#each schemaErrors as err}
<p>{err}</p>
{/each}
</div>
</div>
{/if}
</Field>

View File

@ -1,10 +1,13 @@
<script> <script>
import { slide } from "svelte/transition"; import { slide } from "svelte/transition";
import { Collection } from "pocketbase"; import { Collection } from "pocketbase";
import CommonHelper from "@/utils/CommonHelper";
import RuleField from "@/components/collections/RuleField.svelte"; import RuleField from "@/components/collections/RuleField.svelte";
export let collection = new Collection(); export let collection = new Collection();
$: fields = CommonHelper.getAllCollectionIdentifiers(collection);
let showFiltersInfo = false; let showFiltersInfo = false;
</script> </script>
@ -31,15 +34,8 @@
<div class="content"> <div class="content">
<p class="m-b-0">The following record fields are available:</p> <p class="m-b-0">The following record fields are available:</p>
<div class="inline-flex flex-gap-5"> <div class="inline-flex flex-gap-5">
<code>id</code> {#each fields as name}
<code>created</code> <code>{name}</code>
<code>updated</code>
{#each collection.schema as field}
{#if field.type === "relation" || field.type === "user"}
<code>{field.name}.*</code>
{:else}
<code>{field.name}</code>
{/if}
{/each} {/each}
</div> </div>
@ -84,14 +80,16 @@
<hr class="m-t-sm m-b-sm" /> <hr class="m-t-sm m-b-sm" />
<RuleField label="View action" formKey="viewRule" {collection} bind:rule={collection.viewRule} /> <RuleField label="View action" formKey="viewRule" {collection} bind:rule={collection.viewRule} />
<hr class="m-t-sm m-b-sm" /> {#if !collection?.isView}
<RuleField label="Create action" formKey="createRule" {collection} bind:rule={collection.createRule} /> <hr class="m-t-sm m-b-sm" />
<RuleField label="Create action" formKey="createRule" {collection} bind:rule={collection.createRule} />
<hr class="m-t-sm m-b-sm" /> <hr class="m-t-sm m-b-sm" />
<RuleField label="Update action" formKey="updateRule" {collection} bind:rule={collection.updateRule} /> <RuleField label="Update action" formKey="updateRule" {collection} bind:rule={collection.updateRule} />
<hr class="m-t-sm m-b-sm" /> <hr class="m-t-sm m-b-sm" />
<RuleField label="Delete action" formKey="deleteRule" {collection} bind:rule={collection.deleteRule} /> <RuleField label="Delete action" formKey="deleteRule" {collection} bind:rule={collection.deleteRule} />
{/if}
{#if collection?.isAuth} {#if collection?.isAuth}
<hr class="m-t-sm m-b-sm" /> <hr class="m-t-sm m-b-sm" />

View File

@ -16,6 +16,8 @@
$: deletedFields = collection?.schema.filter((field) => field.id && field.toDelete) || []; $: deletedFields = collection?.schema.filter((field) => field.id && field.toDelete) || [];
$: showChanges = isCollectionRenamed || !collection?.isView;
export async function show(collectionToCheck) { export async function show(collectionToCheck) {
collection = collectionToCheck; collection = collectionToCheck;
@ -50,8 +52,8 @@
</div> </div>
<div class="content txt-bold"> <div class="content txt-bold">
<p> <p>
If any of the following changes is part of another collection rule or filter, you'll have to If any of the collection changes is part of another collection rule, filter or view query,
update it manually! you'll have to update it manually!
</p> </p>
{#if deletedFields.length} {#if deletedFields.length}
<p>All data associated with the removed fields will be permanently deleted!</p> <p>All data associated with the removed fields will be permanently deleted!</p>
@ -59,36 +61,40 @@
</div> </div>
</div> </div>
<h6>Changes:</h6> {#if showChanges}
<ul class="changes-list"> <h6>Changes:</h6>
{#if isCollectionRenamed} <ul class="changes-list">
<li> {#if isCollectionRenamed}
<div class="inline-flex"> <li>
Renamed collection <div class="inline-flex">
<strong class="txt-strikethrough txt-hint">{collection.originalName}</strong> Renamed collection
<i class="ri-arrow-right-line txt-sm" /> <strong class="txt-strikethrough txt-hint">{collection.originalName}</strong>
<strong class="txt"> {collection.name}</strong> <i class="ri-arrow-right-line txt-sm" />
</div> <strong class="txt"> {collection.name}</strong>
</li> </div>
{/if} </li>
{/if}
{#each renamedFields as field} {#if !collection?.isView}
<li> {#each renamedFields as field}
<div class="inline-flex"> <li>
Renamed field <div class="inline-flex">
<strong class="txt-strikethrough txt-hint">{field.originalName}</strong> Renamed field
<i class="ri-arrow-right-line txt-sm" /> <strong class="txt-strikethrough txt-hint">{field.originalName}</strong>
<strong class="txt"> {field.name}</strong> <i class="ri-arrow-right-line txt-sm" />
</div> <strong class="txt"> {field.name}</strong>
</li> </div>
{/each} </li>
{/each}
{#each deletedFields as field} {#each deletedFields as field}
<li class="txt-danger"> <li class="txt-danger">
Removed field <span class="txt-bold">{field.name}</span> Removed field <span class="txt-bold">{field.name}</span>
</li> </li>
{/each} {/each}
</ul> {/if}
</ul>
{/if}
<svelte:fragment slot="footer"> <svelte:fragment slot="footer">
<!-- svelte-ignore a11y-autofocus --> <!-- svelte-ignore a11y-autofocus -->

View File

@ -1,7 +1,7 @@
<script> <script>
import { Collection } from "pocketbase";
import { createEventDispatcher, tick } from "svelte"; import { createEventDispatcher, tick } from "svelte";
import { scale } from "svelte/transition"; import { scale } from "svelte/transition";
import { Collection } from "pocketbase";
import CommonHelper from "@/utils/CommonHelper"; import CommonHelper from "@/utils/CommonHelper";
import ApiClient from "@/utils/ApiClient"; import ApiClient from "@/utils/ApiClient";
import { errors, setErrors, removeError } from "@/stores/errors"; import { errors, setErrors, removeError } from "@/stores/errors";
@ -14,18 +14,21 @@
import OverlayPanel from "@/components/base/OverlayPanel.svelte"; import OverlayPanel from "@/components/base/OverlayPanel.svelte";
import CollectionFieldsTab from "@/components/collections/CollectionFieldsTab.svelte"; import CollectionFieldsTab from "@/components/collections/CollectionFieldsTab.svelte";
import CollectionRulesTab from "@/components/collections/CollectionRulesTab.svelte"; import CollectionRulesTab from "@/components/collections/CollectionRulesTab.svelte";
import CollectionQueryTab from "@/components/collections/CollectionQueryTab.svelte";
import CollectionAuthOptionsTab from "@/components/collections/CollectionAuthOptionsTab.svelte"; import CollectionAuthOptionsTab from "@/components/collections/CollectionAuthOptionsTab.svelte";
import CollectionUpdateConfirm from "@/components/collections/CollectionUpdateConfirm.svelte"; import CollectionUpdateConfirm from "@/components/collections/CollectionUpdateConfirm.svelte";
const TAB_FIELDS = "fields"; const TAB_SCHEMA = "schema";
const TAB_RULES = "api_rules"; const TAB_RULES = "api_rules";
const TAB_OPTIONS = "options"; const TAB_OPTIONS = "options";
const TYPE_BASE = "base"; const TYPE_BASE = "base";
const TYPE_AUTH = "auth"; const TYPE_AUTH = "auth";
const TYPE_VIEW = "view";
const collectionTypes = {}; const collectionTypes = {};
collectionTypes[TYPE_BASE] = "Base"; collectionTypes[TYPE_BASE] = "Base";
collectionTypes[TYPE_VIEW] = "View";
collectionTypes[TYPE_AUTH] = "Auth"; collectionTypes[TYPE_AUTH] = "Auth";
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
@ -37,14 +40,16 @@
let collection = new Collection(); let collection = new Collection();
let isSaving = false; let isSaving = false;
let confirmClose = false; // prevent close recursion let confirmClose = false; // prevent close recursion
let activeTab = TAB_FIELDS; let activeTab = TAB_SCHEMA;
let initialFormHash = calculateFormHash(collection); let initialFormHash = calculateFormHash(collection);
let schemaTabError = "";
$: schemaTabError = $: if ($errors.schema || $errors.options?.query) {
// extract the direct schema field error, otherwise - return a generic message // extract the direct schema field error, otherwise - return a generic message
typeof CommonHelper.getNestedVal($errors, "schema.message", null) === "string" schemaTabError = CommonHelper.getNestedVal($errors, "schema.message") || "Has errors";
? CommonHelper.getNestedVal($errors, "schema.message") } else {
: "Has errors"; schemaTabError = "";
}
$: isSystemUpdate = !collection.isNew && collection.system; $: isSystemUpdate = !collection.isNew && collection.system;
@ -54,7 +59,7 @@
$: if (activeTab === TAB_OPTIONS && collection.type !== TYPE_AUTH) { $: if (activeTab === TAB_OPTIONS && collection.type !== TYPE_AUTH) {
// reset selected tab // reset selected tab
changeTab(TAB_FIELDS); changeTab(TAB_SCHEMA);
} }
export function changeTab(newTab) { export function changeTab(newTab) {
@ -66,7 +71,7 @@
confirmClose = true; confirmClose = true;
changeTab(TAB_FIELDS); changeTab(TAB_SCHEMA);
return collectionPanel?.show(); return collectionPanel?.show();
} }
@ -301,7 +306,7 @@
<button <button
type="button" type="button"
class="btn btn-sm p-r-10 p-l-10 {collection.isNew class="btn btn-sm p-r-10 p-l-10 {collection.isNew
? 'btn-secondary' ? 'btn-outline'
: 'btn-transparent'}" : 'btn-transparent'}"
disabled={!collection.isNew} disabled={!collection.isNew}
> >
@ -339,11 +344,11 @@
<button <button
type="button" type="button"
class="tab-item" class="tab-item"
class:active={activeTab === TAB_FIELDS} class:active={activeTab === TAB_SCHEMA}
on:click={() => changeTab(TAB_FIELDS)} on:click={() => changeTab(TAB_SCHEMA)}
> >
<span class="txt">Fields</span> <span class="txt">{collection?.isView ? "Query" : "Fields"}</span>
{#if !CommonHelper.isEmpty($errors?.schema)} {#if !CommonHelper.isEmpty(schemaTabError)}
<i <i
class="ri-error-warning-fill txt-danger" class="ri-error-warning-fill txt-danger"
transition:scale|local={{ duration: 150, start: 0.7 }} transition:scale|local={{ duration: 150, start: 0.7 }}
@ -390,8 +395,12 @@
<div class="tabs-content"> <div class="tabs-content">
<!-- avoid rerendering the fields tab --> <!-- avoid rerendering the fields tab -->
<div class="tab-item" class:active={activeTab === TAB_FIELDS}> <div class="tab-item" class:active={activeTab === TAB_SCHEMA}>
<CollectionFieldsTab bind:collection /> {#if collection.isView}
<CollectionQueryTab bind:collection />
{:else}
<CollectionFieldsTab bind:collection />
{/if}
</div> </div>
{#if activeTab === TAB_RULES} {#if activeTab === TAB_RULES}
@ -431,7 +440,7 @@
align-items: center; align-items: center;
min-height: var(--smBtnHeight); min-height: var(--smBtnHeight);
} }
.tabs-content { .tabs-content:focus-within {
z-index: 3; /* autocomplete dropdown overlay fix */ z-index: 9; /* autocomplete dropdown overlay fix */
} }
</style> </style>

View File

@ -8,21 +8,21 @@
let collectionPanel; let collectionPanel;
let searchTerm = ""; let searchTerm = "";
$: if ($collections) {
scrollIntoView();
}
$: normalizedSearch = searchTerm.replace(/\s+/g, "").toLowerCase(); $: normalizedSearch = searchTerm.replace(/\s+/g, "").toLowerCase();
$: hasSearch = searchTerm !== ""; $: hasSearch = searchTerm !== "";
$: filteredCollections = $collections.filter((collection) => { $: filtered = $collections.filter((collection) => {
return ( return (
collection.id == searchTerm || collection.id == searchTerm ||
collection.name.replace(/\s+/g, "").toLowerCase().includes(normalizedSearch) collection.name.replace(/\s+/g, "").toLowerCase().includes(normalizedSearch)
); );
}); });
$: if ($collections) {
scrollIntoView();
}
function selectCollection(collection) { function selectCollection(collection) {
$activeCollection = collection; $activeCollection = collection;
} }
@ -59,9 +59,9 @@
<div <div
class="sidebar-content" class="sidebar-content"
class:fade={$isCollectionsLoading} class:fade={$isCollectionsLoading}
class:sidebar-content-compact={filteredCollections.length > 20} class:sidebar-content-compact={filtered.length > 20}
> >
{#each filteredCollections as collection (collection.id)} {#each filtered as collection (collection.id)}
<a <a
href="/collections?collectionId={collection.id}" href="/collections?collectionId={collection.id}"
class="sidebar-list-item" class="sidebar-list-item"
@ -69,7 +69,6 @@
use:link use:link
> >
<i class={CommonHelper.getCollectionTypeIcon(collection.type)} /> <i class={CommonHelper.getCollectionTypeIcon(collection.type)} />
<span class="txt">{collection.name}</span> <span class="txt">{collection.name}</span>
</a> </a>
{:else} {:else}

View File

@ -206,7 +206,7 @@
<div class="grid"> <div class="grid">
<div class="col-sm-6"> <div class="col-sm-6">
<Field <Field
class="form-field required {field.id ? 'disabled' : ''}" class="form-field required {field.id ? 'readonly' : ''}"
name="schema.{key}.type" name="schema.{key}.type"
let:uniqueId let:uniqueId
> >

View File

@ -114,9 +114,9 @@
right: 0px; right: 0px;
top: 0px; top: 0px;
min-width: 135px; min-width: 135px;
padding: 10px 10px; padding: 10px;
border-top-left-radius: 0; border-top-left-radius: 0;
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
background: rgba(53, 71, 104, 0.1); background: rgba(53, 71, 104, 0.08);
} }
</style> </style>

View File

@ -24,7 +24,7 @@
<h4>Request log</h4> <h4>Request log</h4>
</svelte:fragment> </svelte:fragment>
<table class="table-compact table-border"> <table class="table-border">
<tbody> <tbody>
<tr> <tr>
<td class="min-width txt-hint txt-bold">ID</td> <td class="min-width txt-hint txt-bold">ID</td>

View File

@ -1,5 +1,6 @@
<script> <script>
import { replace, querystring } from "svelte-spa-router"; import { replace, querystring } from "svelte-spa-router";
import CommonHelper from "@/utils/CommonHelper";
import { import {
collections, collections,
activeCollection, activeCollection,
@ -16,16 +17,18 @@
import CollectionUpsertPanel from "@/components/collections/CollectionUpsertPanel.svelte"; import CollectionUpsertPanel from "@/components/collections/CollectionUpsertPanel.svelte";
import CollectionDocsPanel from "@/components/collections/CollectionDocsPanel.svelte"; import CollectionDocsPanel from "@/components/collections/CollectionDocsPanel.svelte";
import RecordUpsertPanel from "@/components/records/RecordUpsertPanel.svelte"; import RecordUpsertPanel from "@/components/records/RecordUpsertPanel.svelte";
import RecordPreviewPanel from "@/components/records/RecordPreviewPanel.svelte";
import RecordsList from "@/components/records/RecordsList.svelte"; import RecordsList from "@/components/records/RecordsList.svelte";
const queryParams = new URLSearchParams($querystring); const queryParams = new URLSearchParams($querystring);
let collectionUpsertPanel; let collectionUpsertPanel;
let collectionDocsPanel; let collectionDocsPanel;
let recordPanel; let recordUpsertPanel;
let recordPreviewPanel;
let recordsList; let recordsList;
let filter = queryParams.get("filter") || ""; let filter = queryParams.get("filter") || "";
let sort = queryParams.get("sort") || "-created"; let sort = queryParams.get("sort") || "";
let selectedCollectionId = queryParams.get("collectionId") || $activeCollection?.id; let selectedCollectionId = queryParams.get("collectionId") || $activeCollection?.id;
$: reactiveParams = new URLSearchParams($querystring); $: reactiveParams = new URLSearchParams($querystring);
@ -56,9 +59,17 @@
$: $pageTitle = $activeCollection?.name || "Collections"; $: $pageTitle = $activeCollection?.name || "Collections";
function reset() { function reset() {
selectedCollectionId = $activeCollection.id; selectedCollectionId = $activeCollection?.id;
sort = "-created";
filter = ""; filter = "";
sort = "-created";
// clear default sort if created field is not available
if (
$activeCollection?.isView &&
!CommonHelper.extractColumnsFromQuery($activeCollection.options.query).includes("created")
) {
sort = "";
}
} }
loadCollections(selectedCollectionId); loadCollections(selectedCollectionId);
@ -128,10 +139,12 @@
<span class="txt">API Preview</span> <span class="txt">API Preview</span>
</button> </button>
<button type="button" class="btn btn-expanded" on:click={() => recordPanel?.show()}> {#if !$activeCollection.isView}
<i class="ri-add-line" /> <button type="button" class="btn btn-expanded" on:click={() => recordUpsertPanel?.show()}>
<span class="txt">New record</span> <i class="ri-add-line" />
</button> <span class="txt">New record</span>
</button>
{/if}
</div> </div>
</header> </header>
@ -147,8 +160,12 @@
collection={$activeCollection} collection={$activeCollection}
bind:filter bind:filter
bind:sort bind:sort
on:select={(e) => recordPanel?.show(e?.detail)} on:select={(e) => {
on:new={() => recordPanel?.show()} $activeCollection.isView
? recordPreviewPanel.show(e?.detail)
: recordUpsertPanel?.show(e?.detail);
}}
on:new={() => recordUpsertPanel?.show()}
/> />
</PageWrapper> </PageWrapper>
{/if} {/if}
@ -158,8 +175,10 @@
<CollectionDocsPanel bind:this={collectionDocsPanel} /> <CollectionDocsPanel bind:this={collectionDocsPanel} />
<RecordUpsertPanel <RecordUpsertPanel
bind:this={recordPanel} bind:this={recordUpsertPanel}
collection={$activeCollection} collection={$activeCollection}
on:save={() => recordsList?.reloadLoadedPages()} on:save={() => recordsList?.reloadLoadedPages()}
on:delete={() => recordsList?.reloadLoadedPages()} on:delete={() => recordsList?.reloadLoadedPages()}
/> />
<RecordPreviewPanel bind:this={recordPreviewPanel} collection={$activeCollection} />

View File

@ -1,82 +0,0 @@
<script>
import CommonHelper from "@/utils/CommonHelper";
import tooltip from "@/actions/tooltip";
import FormattedDate from "@/components/base/FormattedDate.svelte";
import RecordFileThumb from "@/components/records/RecordFileThumb.svelte";
import RecordInfo from "@/components/records/RecordInfo.svelte";
export let record;
export let field;
</script>
<td class="col-type-{field.type} col-field-{field.name}">
{#if field.type === "json"}
<span class="txt txt-ellipsis">
{CommonHelper.truncate(JSON.stringify(record[field.name]))}
</span>
{:else if CommonHelper.isEmpty(record[field.name])}
<span class="txt-hint">N/A</span>
{:else if field.type === "bool"}
<span class="txt">{record[field.name] ? "True" : "False"}</span>
{:else if field.type === "number"}
<span class="txt">{record[field.name]}</span>
{:else if field.type === "url"}
<a
class="txt-ellipsis"
href={record[field.name]}
target="_blank"
rel="noopener noreferrer"
use:tooltip={"Open in new tab"}
on:click|stopPropagation
>
{CommonHelper.truncate(record[field.name])}
</a>
{:else if field.type === "editor"}
<span class="txt">
{CommonHelper.truncate(CommonHelper.plainText(record[field.name]), 300, true)}
</span>
{:else if field.type === "date"}
<FormattedDate date={record[field.name]} />
{:else if field.type === "select"}
<div class="inline-flex">
{#each CommonHelper.toArray(record[field.name]) as item, i (i + item)}
<span class="label">{item}</span>
{/each}
</div>
{:else if field.type === "relation" || field.type === "user"}
{@const relations = CommonHelper.toArray(record[field.name])}
{@const expanded = CommonHelper.toArray(record.expand[field.name])}
<div class="inline-flex">
{#if expanded.length}
{#each expanded.slice(0, 20) as item, i (i + item)}
<span class="label">
<RecordInfo record={item} displayFields={field.options?.displayFields} />
</span>
{/each}
{:else}
{#each relations.slice(0, 20) as id}
<span class="label">{id}</span>
{/each}
{/if}
{#if relations.length > 20}
...
{/if}
</div>
{:else if field.type === "file"}
<div class="inline-flex">
{#each CommonHelper.toArray(record[field.name]) as filename, i (i + filename)}
<RecordFileThumb {record} {filename} size="sm" />
{/each}
</div>
{:else}
<span class="txt txt-ellipsis" title={CommonHelper.truncate(record[field.name])}>
{CommonHelper.truncate(record[field.name])}
</span>
{/if}
</td>
<style>
.filename {
max-width: 200px;
}
</style>

View File

@ -0,0 +1,107 @@
<script>
import CommonHelper from "@/utils/CommonHelper";
import tooltip from "@/actions/tooltip";
import FormattedDate from "@/components/base/FormattedDate.svelte";
import RecordFileThumb from "@/components/records/RecordFileThumb.svelte";
import RecordInfo from "@/components/records/RecordInfo.svelte";
import TinyMCE from "@tinymce/tinymce-svelte";
export let record;
export let field;
export let short = false;
$: rawValue = record[field.name];
</script>
{#if field.type === "json"}
<span class="txt txt-ellipsis">
{short
? CommonHelper.truncate(JSON.stringify(rawValue))
: CommonHelper.truncate(JSON.stringify(rawValue, null, 2), 2000, true)}
</span>
{:else if CommonHelper.isEmpty(rawValue)}
<span class="txt-hint">N/A</span>
{:else if field.type === "bool"}
<span class="txt">{rawValue ? "True" : "False"}</span>
{:else if field.type === "number"}
<span class="txt">{rawValue}</span>
{:else if field.type === "url"}
<a
class="txt-ellipsis"
href={rawValue}
target="_blank"
rel="noopener noreferrer"
use:tooltip={"Open in new tab"}
on:click|stopPropagation
>
{CommonHelper.truncate(rawValue)}
</a>
{:else if field.type === "editor"}
{#if short}
<span class="txt">
{CommonHelper.truncate(CommonHelper.plainText(rawValue), 250)}
</span>
{:else}
<TinyMCE
scriptSrc="{import.meta.env.BASE_URL}libs/tinymce/tinymce.min.js"
cssClass="tinymce-preview"
conf={{
branding: false,
promotion: false,
menubar: false,
min_height: 30,
statusbar: false,
height: 59,
max_height: 500,
autoresize_bottom_margin: 5,
resize: false,
skin: "pocketbase",
content_style: "body { font-size: 14px }",
toolbar: "",
plugins: ["autoresize"],
}}
value={rawValue}
disabled
/>
{/if}
{:else if field.type === "date"}
<FormattedDate date={rawValue} />
{:else if field.type === "select"}
<div class="inline-flex">
{#each CommonHelper.toArray(rawValue) as item, i (i + item)}
<span class="label">{item}</span>
{/each}
</div>
{:else if field.type === "relation"}
{@const relations = CommonHelper.toArray(rawValue)}
{@const expanded = CommonHelper.toArray(record.expand[field.name])}
{@const relLimit = short ? 20 : 200}
<div class="inline-flex">
{#if expanded.length}
{#each expanded.slice(0, relLimit) as item, i (i + item)}
<span class="label">
<RecordInfo record={item} displayFields={field.options?.displayFields} />
</span>
{/each}
{:else}
{#each relations.slice(0, relLimit) as id}
<span class="label">{id}</span>
{/each}
{/if}
{#if relations.length > relLimit}
...
{/if}
</div>
{:else if field.type === "file"}
<div class="inline-flex">
{#each CommonHelper.toArray(rawValue) as filename, i (i + filename)}
<RecordFileThumb {record} {filename} size="sm" />
{/each}
</div>
{:else if short}
<span class="txt txt-ellipsis" title={CommonHelper.truncate(rawValue)}>
{CommonHelper.truncate(rawValue)}
</span>
{:else}
<span class="block txt-break">{CommonHelper.truncate(rawValue, 2000)}</span>
{/if}

View File

@ -1,11 +1,23 @@
<script> <script>
import CommonHelper from "@/utils/CommonHelper"; import CommonHelper from "@/utils/CommonHelper";
import tooltip from "@/actions/tooltip"; import tooltip from "@/actions/tooltip";
import { collections } from "@/stores/collections";
import RecordFileThumb from "@/components/records/RecordFileThumb.svelte";
export let record; export let record;
export let displayFields = []; export let displayFields = [];
$: displayValue = CommonHelper.displayValue(record, displayFields); $: collection = $collections?.find((item) => item.id == record?.collectionId);
$: fileDisplayFields =
displayFields?.filter((name) => {
return !!collection?.schema?.find((field) => field.name == name && field.type == "file");
}) || [];
$: textDisplayFields =
(!fileDisplayFields.length
? displayFields
: displayFields?.filter((name) => !fileDisplayFields.includes(name))) || [];
</script> </script>
<div class="record-info"> <div class="record-info">
@ -21,7 +33,16 @@
position: "left", position: "left",
}} }}
/> />
<span class="txt txt-ellipsis">{CommonHelper.truncate(displayValue, 150)}</span>
{#each fileDisplayFields as name}
{#if record[name]}
<RecordFileThumb {record} filename={record[name]} size="xs" />
{/if}
{/each}
<span class="txt txt-ellipsis">
{CommonHelper.truncate(CommonHelper.displayValue(record, textDisplayFields), 70)}
</span>
</div> </div>
<style lang="scss"> <style lang="scss">
@ -36,5 +57,8 @@
> * { > * {
line-height: inherit; line-height: inherit;
} }
:global(.thumb) {
box-shadow: none;
}
} }
</style> </style>

View File

@ -0,0 +1,78 @@
<script>
import { Record } from "pocketbase";
import OverlayPanel from "@/components/base/OverlayPanel.svelte";
import RecordFieldValue from "./RecordFieldValue.svelte";
import CopyIcon from "@/components/base/CopyIcon.svelte";
import FormattedDate from "@/components/base/FormattedDate.svelte";
export let collection;
let recordPanel;
let record = new Record();
$: hasEditorField = !!collection?.schema?.find((f) => f.type === "editor");
export function show(model) {
record = model;
return recordPanel?.show();
}
export function hide() {
return recordPanel?.hide();
}
</script>
<OverlayPanel
bind:this={recordPanel}
class="record-preview-panel {hasEditorField ? 'overlay-panel-xl' : 'overlay-panel-lg'}"
on:hide
on:show
>
<svelte:fragment slot="header">
<h4><strong>{collection?.name}</strong> record preview</h4>
</svelte:fragment>
<table class="table-border">
<tbody>
<tr>
<td class="min-width txt-hint txt-bold">id</td>
<td>
<div class="label">
<CopyIcon value={record.id} />
<span class="txt">{record.id}</span>
</div>
</td>
</tr>
{#each collection?.schema as field}
<tr>
<td class="min-width txt-hint txt-bold">{field.name}</td>
<td>
<RecordFieldValue {field} {record} />
</td>
</tr>
{/each}
{#if record.created}
<tr>
<td class="min-width txt-hint txt-bold">created</td>
<td><FormattedDate date={record.created} /></td>
</tr>
{/if}
{#if record.updated}
<tr>
<td class="min-width txt-hint txt-bold">updated</td>
<td><FormattedDate date={record.updated} /></td>
</tr>
{/if}
</tbody>
</table>
<svelte:fragment slot="footer">
<button type="button" class="btn btn-transparent" on:click={() => hide()}>
<span class="txt">Close</span>
</button>
</svelte:fragment>
</OverlayPanel>

View File

@ -355,7 +355,7 @@
on:submit|preventDefault={save} on:submit|preventDefault={save}
> >
{#if !record.isNew} {#if !record.isNew}
<Field class="form-field disabled" name="id" let:uniqueId> <Field class="form-field readonly" name="id" let:uniqueId>
<label for={uniqueId}> <label for={uniqueId}>
<i class={CommonHelper.getFieldTypeIcon("primary")} /> <i class={CommonHelper.getFieldTypeIcon("primary")} />
<span class="txt">id</span> <span class="txt">id</span>
@ -426,6 +426,7 @@
<button type="button" class="btn btn-transparent" disabled={isSaving} on:click={() => hide()}> <button type="button" class="btn btn-transparent" disabled={isSaving} on:click={() => hide()}>
<span class="txt">Cancel</span> <span class="txt">Cancel</span>
</button> </button>
<button <button
type="submit" type="submit"
form={formId} form={formId}

View File

@ -12,7 +12,7 @@
import CopyIcon from "@/components/base/CopyIcon.svelte"; import CopyIcon from "@/components/base/CopyIcon.svelte";
import FormattedDate from "@/components/base/FormattedDate.svelte"; import FormattedDate from "@/components/base/FormattedDate.svelte";
import HorizontalScroller from "@/components/base/HorizontalScroller.svelte"; import HorizontalScroller from "@/components/base/HorizontalScroller.svelte";
import RecordFieldCell from "@/components/records/RecordFieldCell.svelte"; import RecordFieldValue from "@/components/records/RecordFieldValue.svelte";
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
const sortRegex = /^([\+\-])?(\w+)$/; const sortRegex = /^([\+\-])?(\w+)$/;
@ -57,6 +57,10 @@
updateStoredHiddenColumns(); updateStoredHiddenColumns();
} }
$: hasCreated = !collection?.isView || (records.length > 0 && records[0].created != "");
$: hasUpdated = !collection?.isView || (records.length > 0 && records[0].updated != "");
$: collumnsToHide = [].concat( $: collumnsToHide = [].concat(
collection.isAuth collection.isAuth
? [ ? [
@ -67,10 +71,8 @@
fields.map((f) => { fields.map((f) => {
return { id: f.id, name: f.name }; return { id: f.id, name: f.name };
}), }),
[ hasCreated ? { id: "@created", name: "created" } : [],
{ id: "@created", name: "created" }, hasUpdated ? { id: "@updated", name: "updated" } : []
{ id: "@updated", name: "updated" },
]
); );
function updateStoredHiddenColumns() { function updateStoredHiddenColumns() {
@ -242,48 +244,55 @@
<HorizontalScroller class="table-wrapper"> <HorizontalScroller class="table-wrapper">
<svelte:fragment slot="before"> <svelte:fragment slot="before">
<Toggler class="dropdown dropdown-right dropdown-nowrap columns-dropdown" trigger={columnsTrigger}> {#if columnsTrigger}
<div class="txt-hint txt-sm p-5 m-b-5">Toggle columns</div> <Toggler
{#each collumnsToHide as column (column.id + column.name)} class="dropdown dropdown-right dropdown-nowrap columns-dropdown"
<Field class="form-field form-field-sm form-field-toggle m-0 p-5" let:uniqueId> trigger={columnsTrigger}
<input >
type="checkbox" <div class="txt-hint txt-sm p-5 m-b-5">Toggle columns</div>
id={uniqueId} {#each collumnsToHide as column (column.id + column.name)}
checked={!hiddenColumns.includes(column.id)} <Field class="form-field form-field-sm form-field-toggle m-0 p-5" let:uniqueId>
on:change={(e) => { <input
if (e.target.checked) { type="checkbox"
CommonHelper.removeByValue(hiddenColumns, column.id); id={uniqueId}
} else { checked={!hiddenColumns.includes(column.id)}
CommonHelper.pushUnique(hiddenColumns, column.id); on:change={(e) => {
} if (e.target.checked) {
hiddenColumns = hiddenColumns; CommonHelper.removeByValue(hiddenColumns, column.id);
}} } else {
/> CommonHelper.pushUnique(hiddenColumns, column.id);
<label for={uniqueId}>{column.name}</label> }
</Field> hiddenColumns = hiddenColumns;
{/each} }}
</Toggler> />
<label for={uniqueId}>{column.name}</label>
</Field>
{/each}
</Toggler>
{/if}
</svelte:fragment> </svelte:fragment>
<table class="table" class:table-loading={isLoading}> <table class="table" class:table-loading={isLoading}>
<thead> <thead>
<tr> <tr>
<th class="bulk-select-col min-width"> {#if !collection.isView}
{#if isLoading} <th class="bulk-select-col min-width">
<span class="loader loader-sm" /> {#if isLoading}
{:else} <span class="loader loader-sm" />
<div class="form-field"> {:else}
<input <div class="form-field">
type="checkbox" <input
id="checkbox_0" type="checkbox"
disabled={!records.length} id="checkbox_0"
checked={areAllRecordsSelected} disabled={!records.length}
on:change={() => toggleSelectAllRecords()} checked={areAllRecordsSelected}
/> on:change={() => toggleSelectAllRecords()}
<label for="checkbox_0" /> />
</div> <label for="checkbox_0" />
{/if} </div>
</th> {/if}
</th>
{/if}
{#if !hiddenColumns.includes("@id")} {#if !hiddenColumns.includes("@id")}
<SortHeader class="col-type-text col-field-id" name="id" bind:sort> <SortHeader class="col-type-text col-field-id" name="id" bind:sort>
@ -326,7 +335,7 @@
</SortHeader> </SortHeader>
{/each} {/each}
{#if !hiddenColumns.includes("@created")} {#if hasCreated && !hiddenColumns.includes("@created")}
<SortHeader class="col-type-date col-field-created" name="created" bind:sort> <SortHeader class="col-type-date col-field-created" name="created" bind:sort>
<div class="col-header-content"> <div class="col-header-content">
<i class={CommonHelper.getFieldTypeIcon("date")} /> <i class={CommonHelper.getFieldTypeIcon("date")} />
@ -335,7 +344,7 @@
</SortHeader> </SortHeader>
{/if} {/if}
{#if !hiddenColumns.includes("@updated")} {#if hasUpdated && !hiddenColumns.includes("@updated")}
<SortHeader class="col-type-date col-field-updated" name="updated" bind:sort> <SortHeader class="col-type-date col-field-updated" name="updated" bind:sort>
<div class="col-header-content"> <div class="col-header-content">
<i class={CommonHelper.getFieldTypeIcon("date")} /> <i class={CommonHelper.getFieldTypeIcon("date")} />
@ -345,19 +354,21 @@
{/if} {/if}
<th class="col-type-action min-width"> <th class="col-type-action min-width">
<button {#if collumnsToHide.length}
bind:this={columnsTrigger} <button
type="button" bind:this={columnsTrigger}
aria-label="Toggle columns" type="button"
class="btn btn-sm btn-transparent p-0" aria-label="Toggle columns"
> class="btn btn-sm btn-transparent p-0"
<i class="ri-more-line" /> >
</button> <i class="ri-more-line" />
</button>
{/if}
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{#each records as record (record.id)} {#each records as record (!collection.isView ? record.id : record)}
<tr <tr
tabindex="0" tabindex="0"
class="row-handle" class="row-handle"
@ -369,18 +380,20 @@
} }
}} }}
> >
<td class="bulk-select-col min-width"> {#if !collection.isView}
<!-- svelte-ignore a11y-click-events-have-key-events --> <td class="bulk-select-col min-width">
<div class="form-field" on:click|stopPropagation> <!-- svelte-ignore a11y-click-events-have-key-events -->
<input <div class="form-field" on:click|stopPropagation>
type="checkbox" <input
id="checkbox_{record.id}" type="checkbox"
checked={bulkSelected[record.id]} id="checkbox_{record.id}"
on:change={() => toggleSelectRecord(record)} checked={bulkSelected[record.id]}
/> on:change={() => toggleSelectRecord(record)}
<label for="checkbox_{record.id}" /> />
</div> <label for="checkbox_{record.id}" />
</td> </div>
</td>
{/if}
{#if !hiddenColumns.includes("@id")} {#if !hiddenColumns.includes("@id")}
<td class="col-type-text col-field-id"> <td class="col-type-text col-field-id">
@ -433,16 +446,18 @@
{/if} {/if}
{#each visibleFields as field (field.name)} {#each visibleFields as field (field.name)}
<RecordFieldCell {record} {field} /> <td class="col-type-{field.type} col-field-{field.name}">
<RecordFieldValue short {record} {field} />
</td>
{/each} {/each}
{#if !hiddenColumns.includes("@created")} {#if hasCreated && !hiddenColumns.includes("@created")}
<td class="col-type-date col-field-created"> <td class="col-type-date col-field-created">
<FormattedDate date={record.created} /> <FormattedDate date={record.created} />
</td> </td>
{/if} {/if}
{#if !hiddenColumns.includes("@updated")} {#if hasUpdated && !hiddenColumns.includes("@updated")}
<td class="col-type-date col-field-updated"> <td class="col-type-date col-field-updated">
<FormattedDate date={record.updated} /> <FormattedDate date={record.updated} />
</td> </td>

View File

@ -198,7 +198,7 @@
</div> </div>
<div class="col-lg-3"> <div class="col-lg-3">
<Field class="form-field required" name="smtp.tls" let:uniqueId> <Field class="form-field required" name="smtp.tls" let:uniqueId>
<label for={uniqueId}>TLS Encryption</label> <label for={uniqueId}>TLS encryption</label>
<ObjectSelect <ObjectSelect
id={uniqueId} id={uniqueId}
items={tlsOptions} items={tlsOptions}
@ -208,7 +208,7 @@
</div> </div>
<div class="col-lg-3"> <div class="col-lg-3">
<Field class="form-field" name="smtp.authMethod" let:uniqueId> <Field class="form-field" name="smtp.authMethod" let:uniqueId>
<label for={uniqueId}>AUTH Method</label> <label for={uniqueId}>AUTH method</label>
<ObjectSelect <ObjectSelect
id={uniqueId} id={uniqueId}
items={authMethods} items={authMethods}

View File

@ -123,7 +123,7 @@ code {
display: inline-block; display: inline-block;
font-family: var(--monospaceFontFamily); font-family: var(--monospaceFontFamily);
font-style: normal; font-style: normal;
font-size: var(--lgFontSize); font-size: 1em;
line-height: 1.379rem; line-height: 1.379rem;
padding: 0px 4px; padding: 0px 4px;
white-space: nowrap; white-space: nowrap;
@ -522,6 +522,10 @@ a,
border-radius: inherit; border-radius: inherit;
overflow: hidden; overflow: hidden;
} }
&.thumb-xs {
--thumbSize: 24px;
font-size: 0.85rem;
}
&.thumb-sm { &.thumb-sm {
--thumbSize: 32px; --thumbSize: 32px;
font-size: 0.92rem; font-size: 0.92rem;

View File

@ -1,10 +1,10 @@
/* remixicon */ /* remixicon */
@font-face { @font-face {
font-family: 'remixicon'; font-family: 'remixicon';
src: url('../fonts/remixicon/remixicon.woff2?v=1') format('woff2'), src: url('/fonts/remixicon/remixicon.woff2?v=1') format('woff2'),
url('../fonts/remixicon/remixicon.woff?v=1') format('woff'), url('/fonts/remixicon/remixicon.woff?v=1') format('woff'),
url('../fonts/remixicon/remixicon.ttf?v=1') format('truetype'), url('/fonts/remixicon/remixicon.ttf?v=1') format('truetype'),
url('../fonts/remixicon/remixicon.svg?v=1#remixicon') format('svg'); /* iOS 4.1- */ url('/fonts/remixicon/remixicon.svg?v=1#remixicon') format('svg'); /* iOS 4.1- */
font-display: swap; font-display: swap;
} }
@ -14,8 +14,8 @@
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
src: local(''), src: local(''),
url('../fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ url('/fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
url('../fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ url('/fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
} }
/* source-sans-pro-italic - latin_cyrillic */ /* source-sans-pro-italic - latin_cyrillic */
@ -24,8 +24,8 @@
font-style: italic; font-style: italic;
font-weight: 400; font-weight: 400;
src: local(''), src: local(''),
url('../fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ url('/fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
url('../fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ url('/fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
} }
/* source-sans-pro-600 - latin_cyrillic */ /* source-sans-pro-600 - latin_cyrillic */
@ -34,8 +34,8 @@
font-style: normal; font-style: normal;
font-weight: 600; font-weight: 600;
src: local(''), src: local(''),
url('../fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-600.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ url('/fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-600.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
url('../fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-600.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ url('/fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-600.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
} }
/* source-sans-pro-600italic - latin_cyrillic */ /* source-sans-pro-600italic - latin_cyrillic */
@ -44,8 +44,8 @@
font-style: italic; font-style: italic;
font-weight: 600; font-weight: 600;
src: local(''), src: local(''),
url('../fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-600italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ url('/fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-600italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
url('../fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-600italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ url('/fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-600italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
} }
/* source-sans-pro-700 - latin_cyrillic */ /* source-sans-pro-700 - latin_cyrillic */
@ -54,8 +54,8 @@
font-style: normal; font-style: normal;
font-weight: 700; font-weight: 700;
src: local(''), src: local(''),
url('../fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-700.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ url('/fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-700.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
url('../fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-700.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ url('/fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-700.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
} }
/* source-sans-pro-700italic - latin_cyrillic */ /* source-sans-pro-700italic - latin_cyrillic */
@ -64,8 +64,8 @@
font-style: italic; font-style: italic;
font-weight: 700; font-weight: 700;
src: local(''), src: local(''),
url('../fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-700italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ url('/fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-700italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
url('../fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-700italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ url('/fonts/source-sans-pro/source-sans-pro-v18-latin_cyrillic-700italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
} }
/* jetbrains-mono-regular - latin */ /* jetbrains-mono-regular - latin */
@ -74,8 +74,8 @@
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
src: local(''), src: local(''),
url('../fonts/jetbrains-mono/jetbrains-mono-v12-latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ url('/fonts/jetbrains-mono/jetbrains-mono-v12-latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
url('../fonts/jetbrains-mono/jetbrains-mono-v12-latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ url('/fonts/jetbrains-mono/jetbrains-mono-v12-latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
} }
/* jetbrains-mono-600 - latin */ /* jetbrains-mono-600 - latin */
@ -84,6 +84,6 @@
font-style: normal; font-style: normal;
font-weight: 600; font-weight: 600;
src: local(''), src: local(''),
url('../fonts/jetbrains-mono/jetbrains-mono-v12-latin-600.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ url('/fonts/jetbrains-mono/jetbrains-mono-v12-latin-600.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
url('../fonts/jetbrains-mono/jetbrains-mono-v12-latin-600.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ url('/fonts/jetbrains-mono/jetbrains-mono-v12-latin-600.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
} }

View File

@ -454,11 +454,10 @@ select {
user-select: none; user-select: none;
font-weight: 600; font-weight: 600;
color: var(--txtHintColor); color: var(--txtHintColor);
font-size: var(--xsFontSize); font-size: var(--smFontSize);
text-transform: uppercase;
line-height: 1; line-height: 1;
padding-top: 12px; padding-top: 12px;
padding-bottom: 2px; padding-bottom: 3px;
padding-left: var(--hPadding); padding-left: var(--hPadding);
padding-right: var(--hPadding); padding-right: var(--hPadding);
border: 0; border: 0;
@ -474,8 +473,11 @@ select {
i { i {
font-size: 0.96rem; font-size: 0.96rem;
line-height: 1; line-height: 1;
margin-top: -2px; margin-top: -1px;
margin-bottom: -2px; margin-bottom: -1px;
&:before {
margin: 0;
}
} }
} }
%input, label { %input, label {
@ -550,14 +552,23 @@ select {
margin-left: -2px; margin-left: -2px;
} }
} }
&.readonly,
&.disabled { &.disabled {
label, %input {
background: var(--baseAlt1Color);
}
> label { > label {
color: var(--txtDisabledColor); color: var(--txtHintColor);
} }
&.required > label:after { &.required > label:after {
opacity: 0.5; opacity: 0.5;
} }
} }
&.disabled {
> label {
color: var(--txtDisabledColor);
}
}
// checkbox/radio // checkbox/radio
input[type="radio"], input[type="radio"],
@ -1084,13 +1095,15 @@ select {
// codemirror field // codemirror field
.code-editor { .code-editor {
@extend %input; @extend %input;
display: flex;
flex-direction: column;
width: 100%;
.form-field label ~ & { .form-field label ~ & {
padding-bottom: 6px; padding-bottom: 6px;
padding-top: 4px; padding-top: 4px;
} }
.cm-editor { .cm-editor {
flex-grow: 1;
border: 0 !important; border: 0 !important;
outline: none !important; outline: none !important;
.cm-line { .cm-line {
@ -1122,6 +1135,7 @@ select {
} }
} }
.cm-scroller { .cm-scroller {
flex-grow: 1;
outline: 0 !important; outline: 0 !important;
font-family: var(--monospaceFontFamily); font-family: var(--monospaceFontFamily);
font-size: var(--baseFontSize); font-size: var(--baseFontSize);
@ -1145,6 +1159,9 @@ select {
background-color: rgba(50, 140, 130, 0.1); background-color: rgba(50, 140, 130, 0.1);
} }
} }
.ͼf {
color: var(--dangerColor);
}
} }
// tinymce field // tinymce field

View File

@ -17,7 +17,7 @@ table {
vertical-align: middle; vertical-align: middle;
position: relative; position: relative;
text-align: left; text-align: left;
padding: 5px 10px; padding: 10px;
border-bottom: 1px solid var(--baseAlt2Color); border-bottom: 1px solid var(--baseAlt2Color);
&:first-child { &:first-child {
padding-left: 20px; padding-left: 20px;
@ -191,16 +191,15 @@ table {
} }
// styles // styles
&.table-compact {
td, th {
height: auto;
}
}
&.table-border { &.table-border {
border: 1px solid var(--baseAlt2Color); border: 1px solid var(--baseAlt2Color);
border-radius: var(--baseRadius);
tr { tr {
background: var(--baseColor); background: var(--baseColor);
} }
td, th {
height: 45px;
}
th { th {
background: var(--baseAlt1Color); background: var(--baseAlt1Color);
} }
@ -209,6 +208,29 @@ table {
border-bottom: 0; border-bottom: 0;
} }
} }
> tr:first-child,
> :first-child > tr:first-child {
> :first-child {
border-top-left-radius: var(--baseRadius);
}
> :last-child {
border-top-right-radius: var(--baseRadius);
}
}
> tr:last-child,
> :last-child > tr:last-child {
> :first-child {
border-bottom-left-radius: var(--baseRadius);
}
> :last-child {
border-bottom-right-radius: var(--baseRadius);
}
}
}
&.table-compact {
td, th {
height: auto;
}
} }
// states // states

View File

@ -18,13 +18,13 @@
--baseAlt4Color: #a5b0c0; --baseAlt4Color: #a5b0c0;
--infoColor: #3da9fc; --infoColor: #3da9fc;
--infoAltColor: #d8eefe; --infoAltColor: #d2ecfe;
--successColor: #2cb67d; --successColor: #2aac76;
--successAltColor: #d6f5e8; --successAltColor: #d2f4e6;
--dangerColor: #e13756; --dangerColor: #e13756;
--dangerAltColor: #fcdee4; --dangerAltColor: #fcdee4;
--warningColor: #ff8e3c; --warningColor: #ff8e3c;
--warningAltColor: #ffe7d6; --warningAltColor: #ffeadb;
--overlayColor: rgba(53, 71, 104, 0.25); --overlayColor: rgba(53, 71, 104, 0.25);
--tooltipColor: rgba(0, 0, 0, 0.85); --tooltipColor: rgba(0, 0, 0, 0.85);

View File

@ -475,11 +475,12 @@ export default class CommonHelper {
/** /**
* Truncates the provided text to the specified max characters length. * Truncates the provided text to the specified max characters length.
* *
* @param {String} str * @param {String} str
* @param {Number} length * @param {Number} [length]
* @param {Boolean} [dots]
* @return {String} * @return {String}
*/ */
static truncate(str, length = 150, dots = false) { static truncate(str, length = 150, dots = true) {
str = str || ""; str = str || "";
if (str.length <= length) { if (str.length <= length) {
@ -979,8 +980,8 @@ export default class CommonHelper {
switch (type?.toLowerCase()) { switch (type?.toLowerCase()) {
case "auth": case "auth":
return "ri-group-line"; return "ri-group-line";
case "single": case "view":
return "ri-file-list-2-line"; return "ri-terminal-box-line";
default: default:
return "ri-folder-2-line"; return "ri-folder-2-line";
} }
@ -1157,7 +1158,7 @@ export default class CommonHelper {
for (const collection of collections) { for (const collection of collections) {
if (collection.type === 'auth') { if (collection.type === 'auth') {
authCollections.push(collection); authCollections.push(collection);
} else if (collection.type === 'single') { } else if (collection.type === 'base') {
singleCollections.push(collection); singleCollections.push(collection);
} else { } else {
baseCollections.push(collection); baseCollections.push(collection);
@ -1318,4 +1319,76 @@ export default class CommonHelper {
return missingValue; return missingValue;
} }
/**
* Rudimentary SELECT query columns extractor.
* Returns an array with the identifier aliases
* (expressions wrapped in parenthesis are skipped).
*
* @param {String} selectQuery
* @return {Array}
*/
static extractColumnsFromQuery(selectQuery) {
const groupReplacement = "__GROUP__";
selectQuery = (selectQuery || "").
// replace parenthesis/group expessions
replace(/\([\s\S]+?\)/gm, groupReplacement).
// replace multi-whitespace characters with single space
replace(/[\t\r\n]|(?:\s\s)+/g, " ");
const match = selectQuery.match(/select\s+([\s\S]+)\s+from/);
const expressions = match?.[1]?.split(",") || [];
const result = [];
for (let expr of expressions) {
const column = expr.trim().split(" ").pop(); // get only the alias
if (column != "" && column != groupReplacement) {
result.push(column);
}
}
return result;
}
/**
* Returns an array with all public collection identifiers (schema + type specific fields).
*
* @param {[type]} collection The collection to extract identifiers from.
* @param {String} prefix Optional prefix for each found identified.
* @return {Array}
*/
static getAllCollectionIdentifiers(collection, prefix = "") {
if (!collection) {
return;
}
let result = [prefix + "id"];
if (collection.isView) {
for (let col of CommonHelper.extractColumnsFromQuery(collection.options.query)) {
CommonHelper.pushUnique(result, prefix + col);
}
} else if (collection.isAuth) {
result.push(prefix + "username");
result.push(prefix + "email");
result.push(prefix + "emailVisibility");
result.push(prefix + "verified");
result.push(prefix + "created");
result.push(prefix + "updated");
} else {
result.push(prefix + "created");
result.push(prefix + "updated");
}
const schema = collection.schema || [];
for (const field of schema) {
CommonHelper.pushUnique(result, prefix + field.name);
}
return result;
}
} }