mirror of
https://github.com/pocketbase/pocketbase.git
synced 2025-02-03 09:57:24 +02:00
added view collection type
This commit is contained in:
parent
0052e2ab2a
commit
a07f67002f
@ -26,6 +26,10 @@
|
||||
|
||||
- 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
|
||||
|
||||
|
@ -45,7 +45,7 @@ func TestCollectionsList(t *testing.T) {
|
||||
ExpectedContent: []string{
|
||||
`"page":1`,
|
||||
`"perPage":30`,
|
||||
`"totalItems":8`,
|
||||
`"totalItems":10`,
|
||||
`"items":[{`,
|
||||
`"id":"_pb_users_auth_"`,
|
||||
`"id":"v851q4r790rhknl"`,
|
||||
@ -73,10 +73,10 @@ func TestCollectionsList(t *testing.T) {
|
||||
ExpectedContent: []string{
|
||||
`"page":2`,
|
||||
`"perPage":2`,
|
||||
`"totalItems":8`,
|
||||
`"totalItems":10`,
|
||||
`"items":[{`,
|
||||
`"id":"v851q4r790rhknl"`,
|
||||
`"id":"4d1blo5cuycfaca"`,
|
||||
`"id":"kpv709sk2lqbqk8"`,
|
||||
`"id":"9n89pl5vkct6330"`,
|
||||
},
|
||||
ExpectedEvents: map[string]int{
|
||||
"OnCollectionsListRequest": 1,
|
||||
@ -231,7 +231,7 @@ func TestCollectionDelete(t *testing.T) {
|
||||
{
|
||||
Name: "authorized as admin + using the collection name",
|
||||
Method: http.MethodDelete,
|
||||
Url: "/api/collections/demo1",
|
||||
Url: "/api/collections/demo5",
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8",
|
||||
},
|
||||
@ -244,13 +244,13 @@ func TestCollectionDelete(t *testing.T) {
|
||||
"OnCollectionAfterDeleteRequest": 1,
|
||||
},
|
||||
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",
|
||||
Method: http.MethodDelete,
|
||||
Url: "/api/collections/wsmn24bux7wo113",
|
||||
Url: "/api/collections/9n89pl5vkct6330",
|
||||
RequestHeaders: map[string]string{
|
||||
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8",
|
||||
},
|
||||
@ -263,7 +263,7 @@ func TestCollectionDelete(t *testing.T) {
|
||||
"OnCollectionAfterDeleteRequest": 1,
|
||||
},
|
||||
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,
|
||||
},
|
||||
},
|
||||
{
|
||||
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 {
|
||||
@ -520,6 +536,56 @@ func TestCollectionCreate(t *testing.T) {
|
||||
`"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 {
|
||||
@ -660,7 +726,7 @@ func TestCollectionUpdate(t *testing.T) {
|
||||
{
|
||||
Name: "updating base collection with reserved auth fields",
|
||||
Method: http.MethodPatch,
|
||||
Url: "/api/collections/demo1",
|
||||
Url: "/api/collections/demo4",
|
||||
Body: strings.NewReader(`{
|
||||
"schema":[
|
||||
{"type":"text","name":"email"},
|
||||
@ -681,7 +747,7 @@ func TestCollectionUpdate(t *testing.T) {
|
||||
},
|
||||
ExpectedStatus: 200,
|
||||
ExpectedContent: []string{
|
||||
`"name":"demo1"`,
|
||||
`"name":"demo4"`,
|
||||
`"type":"base"`,
|
||||
`"schema":[{`,
|
||||
`"email"`,
|
||||
@ -751,6 +817,7 @@ func TestCollectionUpdate(t *testing.T) {
|
||||
},
|
||||
|
||||
// rel field change displayFields propagation
|
||||
// -----------------------------------------------------------
|
||||
{
|
||||
Name: "renaming a display field should also update the referenced displayFields value",
|
||||
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 {
|
||||
@ -838,6 +959,8 @@ func TestCollectionUpdate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCollectionImport(t *testing.T) {
|
||||
totalCollections := 10
|
||||
|
||||
scenarios := []tests.ApiScenario{
|
||||
{
|
||||
Name: "unauthorized",
|
||||
@ -874,7 +997,7 @@ func TestCollectionImport(t *testing.T) {
|
||||
if err := app.Dao().CollectionQuery().All(&collections); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := 8
|
||||
expected := totalCollections
|
||||
if len(collections) != expected {
|
||||
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 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := 8
|
||||
expected := totalCollections
|
||||
if len(collections) != expected {
|
||||
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 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := 8
|
||||
expected := totalCollections
|
||||
if len(collections) != expected {
|
||||
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 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := 11
|
||||
expected := totalCollections + 3
|
||||
if len(collections) != expected {
|
||||
t.Fatalf("Expected %d collections, got %d", expected, len(collections))
|
||||
}
|
||||
@ -1084,8 +1207,8 @@ func TestCollectionImport(t *testing.T) {
|
||||
ExpectedEvents: map[string]int{
|
||||
"OnCollectionsAfterImportRequest": 1,
|
||||
"OnCollectionsBeforeImportRequest": 1,
|
||||
"OnModelBeforeDelete": 6,
|
||||
"OnModelAfterDelete": 6,
|
||||
"OnModelBeforeDelete": 8,
|
||||
"OnModelAfterDelete": 8,
|
||||
"OnModelBeforeUpdate": 2,
|
||||
"OnModelAfterUpdate": 2,
|
||||
"OnModelBeforeCreate": 1,
|
||||
|
18
apis/file.go
18
apis/file.go
@ -1,6 +1,8 @@
|
||||
package apis
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/labstack/echo/v5"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
@ -45,15 +47,27 @@ func (api *fileApi) download(c echo.Context) error {
|
||||
if fileField == nil {
|
||||
return NewNotFoundError("", nil)
|
||||
}
|
||||
|
||||
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()
|
||||
if err != nil {
|
||||
return NewBadRequestError("Filesystem initialization failure.", err)
|
||||
}
|
||||
defer fs.Close()
|
||||
|
||||
originalPath := record.BaseFilesPath() + "/" + filename
|
||||
originalPath := baseFilesPath + "/" + filename
|
||||
servedPath := originalPath
|
||||
servedName := filename
|
||||
|
||||
@ -70,7 +84,7 @@ func (api *fileApi) download(c echo.Context) error {
|
||||
if list.ExistInSlice(oAttrs.ContentType, imageContentTypes) {
|
||||
// add thumb size as file suffix
|
||||
servedName = thumbSize + "_" + filename
|
||||
servedPath = record.BaseFilesPath() + "/thumbs_" + filename + "/" + servedName
|
||||
servedPath = baseFilesPath + "/thumbs_" + filename + "/" + servedName
|
||||
|
||||
// check if the thumb exists:
|
||||
// - if doesn't exist - create a new thumb with the specified thumb size
|
||||
|
@ -59,9 +59,12 @@ func RequireGuestOnly() echo.MiddlewareFunc {
|
||||
// specifying their names.
|
||||
//
|
||||
// Example:
|
||||
// apis.RequireRecordAuth()
|
||||
//
|
||||
// apis.RequireRecordAuth()
|
||||
//
|
||||
// Or:
|
||||
// apis.RequireRecordAuth("users", "supervisors")
|
||||
//
|
||||
// apis.RequireRecordAuth("users", "supervisors")
|
||||
//
|
||||
// To restrict the auth record only to the loaded context collection,
|
||||
// use [apis.RequireSameContextRecordAuth()] instead.
|
||||
@ -83,7 +86,6 @@ func RequireRecordAuth(optCollectionNames ...string) echo.MiddlewareFunc {
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// RequireSameContextRecordAuth middleware requires a request to have
|
||||
// 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) {
|
||||
return NewBadRequestError("Invalid collection type.", nil)
|
||||
return NewBadRequestError("Unsupported collection type.", nil)
|
||||
}
|
||||
|
||||
c.Set(ContextCollectionKey, collection)
|
||||
|
@ -26,14 +26,13 @@ func bindRecordCrudApi(app core.App, rg *echo.Group) {
|
||||
subGroup := rg.Group(
|
||||
"/collections/:collection",
|
||||
ActivityLogger(app),
|
||||
LoadCollectionContext(app),
|
||||
)
|
||||
|
||||
subGroup.GET("/records", api.list)
|
||||
subGroup.POST("/records", api.create)
|
||||
subGroup.GET("/records/:id", api.view)
|
||||
subGroup.PATCH("/records/:id", api.update)
|
||||
subGroup.DELETE("/records/:id", api.delete)
|
||||
subGroup.GET("/records", api.list, LoadCollectionContext(app))
|
||||
subGroup.GET("/records/:id", api.view, LoadCollectionContext(app))
|
||||
subGroup.POST("/records", api.create, LoadCollectionContext(app, models.CollectionTypeBase, models.CollectionTypeAuth))
|
||||
subGroup.PATCH("/records/:id", api.update, LoadCollectionContext(app, models.CollectionTypeBase, models.CollectionTypeAuth))
|
||||
subGroup.DELETE("/records/:id", api.delete, LoadCollectionContext(app, models.CollectionTypeBase, models.CollectionTypeAuth))
|
||||
}
|
||||
|
||||
type recordApi struct {
|
||||
|
@ -256,7 +256,7 @@ func TestRecordCrudList(t *testing.T) {
|
||||
ExpectedEvents: map[string]int{"OnRecordsListRequest": 1},
|
||||
},
|
||||
|
||||
// auth collection checks
|
||||
// auth collection
|
||||
// -----------------------------------------------------------
|
||||
{
|
||||
Name: "check email visibility as guest",
|
||||
@ -403,6 +403,63 @@ func TestRecordCrudList(t *testing.T) {
|
||||
},
|
||||
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 {
|
||||
@ -531,7 +588,7 @@ func TestRecordCrudView(t *testing.T) {
|
||||
ExpectedEvents: map[string]int{"OnRecordViewRequest": 1},
|
||||
},
|
||||
|
||||
// auth collection checks
|
||||
// auth collection
|
||||
// -----------------------------------------------------------
|
||||
{
|
||||
Name: "check email visibility as guest",
|
||||
@ -629,6 +686,49 @@ func TestRecordCrudView(t *testing.T) {
|
||||
},
|
||||
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 {
|
||||
@ -690,6 +790,13 @@ func TestRecordCrudDelete(t *testing.T) {
|
||||
ExpectedStatus: 404,
|
||||
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",
|
||||
Method: http.MethodDelete,
|
||||
@ -885,6 +992,14 @@ func TestRecordCrudCreate(t *testing.T) {
|
||||
ExpectedStatus: 403,
|
||||
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",
|
||||
Method: http.MethodPost,
|
||||
@ -1428,6 +1543,14 @@ func TestRecordCrudUpdate(t *testing.T) {
|
||||
ExpectedStatus: 400,
|
||||
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",
|
||||
Method: http.MethodPatch,
|
||||
|
@ -472,7 +472,7 @@ func (app *BaseApp) NewMailClient() mailer.Mailer {
|
||||
// NB! Make sure to call `Close()` on the returned result
|
||||
// after you are done working with it.
|
||||
func (app *BaseApp) NewFilesystem() (*filesystem.System, error) {
|
||||
if app.settings.S3.Enabled {
|
||||
if app.settings != nil && app.settings.S3.Enabled {
|
||||
return filesystem.NewS3(
|
||||
app.settings.S3.Bucket,
|
||||
app.settings.S3.Region,
|
||||
|
@ -129,13 +129,23 @@ func (dao *Dao) DeleteCollection(collection *models.Collection) error {
|
||||
return err
|
||||
}
|
||||
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 {
|
||||
// delete the related records table
|
||||
if err := txDao.DeleteTable(collection.Name); err != nil {
|
||||
return err
|
||||
// delete the related view or records table
|
||||
if collection.IsView() {
|
||||
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)
|
||||
@ -163,13 +173,18 @@ func (dao *Dao) SaveCollection(collection *models.Collection) error {
|
||||
collection.Type = models.CollectionTypeBase
|
||||
}
|
||||
|
||||
// persist the collection model
|
||||
if err := txDao.Save(collection); err != nil {
|
||||
return err
|
||||
}
|
||||
switch collection.Type {
|
||||
case models.CollectionTypeView:
|
||||
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
|
||||
return txDao.SyncRecordTableSchema(collection, oldCollection)
|
||||
// sync the changes with the related records table
|
||||
return txDao.SyncRecordTableSchema(collection, oldCollection)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -274,8 +289,14 @@ func (dao *Dao) ImportCollections(
|
||||
continue // exist
|
||||
}
|
||||
|
||||
if err := txDao.DeleteTable(existing.Name); err != nil {
|
||||
return err
|
||||
if existing.IsView() {
|
||||
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
|
||||
for _, imported := range importedCollections {
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
// 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)
|
||||
})
|
||||
}
|
||||
|
@ -45,16 +45,16 @@ func TestFindCollectionsByType(t *testing.T) {
|
||||
|
||||
hasErr := err != nil
|
||||
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 {
|
||||
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 {
|
||||
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
|
||||
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) {
|
||||
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 {
|
||||
result := app.Dao().IsCollectionNameUnique(scenario.name, scenario.excludeId)
|
||||
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 {
|
||||
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()
|
||||
defer app.Cleanup()
|
||||
|
||||
c0 := &models.Collection{}
|
||||
c1, err := app.Dao().FindCollectionByNameOrId("clients")
|
||||
colEmpty := &models.Collection{}
|
||||
|
||||
colAuth, err := app.Dao().FindCollectionByNameOrId("clients")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c2, err := app.Dao().FindCollectionByNameOrId("demo2")
|
||||
|
||||
colReferenced, err := app.Dao().FindCollectionByNameOrId("demo2")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c3, err := app.Dao().FindCollectionByNameOrId("demo1")
|
||||
|
||||
colSystem, err := app.Dao().FindCollectionByNameOrId("demo3")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c3.System = true
|
||||
if err := app.Dao().Save(c3); err != nil {
|
||||
colSystem.System = true
|
||||
if err := app.Dao().Save(colSystem); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
colView, err := app.Dao().FindCollectionByNameOrId("view1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@ -187,18 +195,28 @@ func TestDeleteCollection(t *testing.T) {
|
||||
model *models.Collection
|
||||
expectError bool
|
||||
}{
|
||||
{c0, true},
|
||||
{c1, false},
|
||||
{c2, true}, // is part of a reference
|
||||
{c3, true}, // system
|
||||
{colEmpty, true},
|
||||
{colAuth, false},
|
||||
{colReferenced, true},
|
||||
{colSystem, true},
|
||||
{colView, false},
|
||||
}
|
||||
|
||||
for i, scenario := range scenarios {
|
||||
err := app.Dao().DeleteCollection(scenario.model)
|
||||
hasErr := err != nil
|
||||
for i, s := range scenarios {
|
||||
err := app.Dao().DeleteCollection(s.model)
|
||||
|
||||
if hasErr != scenario.expectError {
|
||||
t.Errorf("(%d) Expected hasErr %v, got %v", i, scenario.expectError, hasErr)
|
||||
hasErr := err != nil
|
||||
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 {
|
||||
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 {
|
||||
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) {
|
||||
totalCollections := 10
|
||||
|
||||
scenarios := []struct {
|
||||
name string
|
||||
jsonData string
|
||||
@ -302,7 +322,7 @@ func TestImportCollections(t *testing.T) {
|
||||
name: "empty collections",
|
||||
jsonData: `[]`,
|
||||
expectError: true,
|
||||
expectCollectionsCount: 8,
|
||||
expectCollectionsCount: totalCollections,
|
||||
},
|
||||
{
|
||||
name: "minimal collection import",
|
||||
@ -312,7 +332,7 @@ func TestImportCollections(t *testing.T) {
|
||||
]`,
|
||||
deleteMissing: false,
|
||||
expectError: false,
|
||||
expectCollectionsCount: 10,
|
||||
expectCollectionsCount: totalCollections + 2,
|
||||
},
|
||||
{
|
||||
name: "minimal collection import + failed beforeRecordsSync",
|
||||
@ -324,7 +344,7 @@ func TestImportCollections(t *testing.T) {
|
||||
},
|
||||
deleteMissing: false,
|
||||
expectError: true,
|
||||
expectCollectionsCount: 8,
|
||||
expectCollectionsCount: totalCollections,
|
||||
},
|
||||
{
|
||||
name: "minimal collection import + successful beforeRecordsSync",
|
||||
@ -336,7 +356,7 @@ func TestImportCollections(t *testing.T) {
|
||||
},
|
||||
deleteMissing: false,
|
||||
expectError: false,
|
||||
expectCollectionsCount: 9,
|
||||
expectCollectionsCount: totalCollections + 1,
|
||||
},
|
||||
{
|
||||
name: "new + update + delete system collection",
|
||||
@ -372,7 +392,7 @@ func TestImportCollections(t *testing.T) {
|
||||
]`,
|
||||
deleteMissing: true,
|
||||
expectError: true,
|
||||
expectCollectionsCount: 8,
|
||||
expectCollectionsCount: totalCollections,
|
||||
},
|
||||
{
|
||||
name: "new + update + delete non-system collection",
|
||||
@ -442,11 +462,19 @@ func TestImportCollections(t *testing.T) {
|
||||
"type":"bool"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "test_new_view",
|
||||
"name": "new_view",
|
||||
"type": "view",
|
||||
"options": {
|
||||
"query": "select id from demo2"
|
||||
}
|
||||
}
|
||||
]`,
|
||||
deleteMissing: true,
|
||||
expectError: false,
|
||||
expectCollectionsCount: 3,
|
||||
expectCollectionsCount: 4,
|
||||
},
|
||||
{
|
||||
name: "test with deleteMissing: false",
|
||||
@ -501,7 +529,7 @@ func TestImportCollections(t *testing.T) {
|
||||
]`,
|
||||
deleteMissing: false,
|
||||
expectError: false,
|
||||
expectCollectionsCount: 9,
|
||||
expectCollectionsCount: totalCollections + 1,
|
||||
afterTestFunc: func(testApp *tests.TestApp, resultCollections []*models.Collection) {
|
||||
expectedCollectionFields := map[string]int{
|
||||
"nologin": 1,
|
||||
@ -509,7 +537,7 @@ func TestImportCollections(t *testing.T) {
|
||||
"demo2": 2,
|
||||
"demo3": 2,
|
||||
"demo4": 11,
|
||||
"demo5": 5,
|
||||
"demo5": 6,
|
||||
"new_import": 1,
|
||||
}
|
||||
for name, expectedCount := range expectedCollectionFields {
|
||||
|
@ -128,7 +128,11 @@ func (dao *Dao) FindRecordsByExpr(collectionNameOrId string, exprs ...dbx.Expres
|
||||
|
||||
// FindFirstRecordByData returns the first found record matching
|
||||
// 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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -396,11 +400,15 @@ func (dao *Dao) cascadeRecordDelete(mainRecord *models.Record, refs map[*models.
|
||||
uniqueJsonEachAlias := "__je__" + security.PseudorandomString(4)
|
||||
|
||||
for refCollection, fields := range refs {
|
||||
if refCollection.IsView() {
|
||||
continue // skip view collections
|
||||
}
|
||||
|
||||
for _, field := range fields {
|
||||
recordTableName := inflector.Columnify(refCollection.Name)
|
||||
prefixedFieldName := recordTableName + "." + inflector.Columnify(field.Name)
|
||||
|
||||
// @todo optimize single relation lookup in v0.12+
|
||||
// @todo optimize single relation lookup
|
||||
query := dao.RecordQuery(refCollection).
|
||||
Distinct(true).
|
||||
InnerJoin(fmt.Sprintf(
|
||||
|
@ -1,7 +1,10 @@
|
||||
package daos
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// 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 {
|
||||
_, err := dao.DB().DropTable(tableName).Execute()
|
||||
_, err := dao.DB().NewQuery(fmt.Sprintf(
|
||||
"DROP TABLE IF EXISTS {{%s}}",
|
||||
tableName,
|
||||
)).Execute()
|
||||
|
||||
return err
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package daos_test
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -28,7 +29,7 @@ func TestHasTable(t *testing.T) {
|
||||
for i, scenario := range scenarios {
|
||||
result := app.Dao().HasTable(scenario.tableName)
|
||||
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"}},
|
||||
}
|
||||
|
||||
for i, scenario := range scenarios {
|
||||
columns, _ := app.Dao().GetTableColumns(scenario.tableName)
|
||||
for i, s := range scenarios {
|
||||
columns, _ := app.Dao().GetTableColumns(s.tableName)
|
||||
|
||||
if len(columns) != len(scenario.expected) {
|
||||
t.Errorf("(%d) Expected columns %v, got %v", i, scenario.expected, columns)
|
||||
if len(columns) != len(s.expected) {
|
||||
t.Errorf("[%d] Expected columns %v, got %v", i, s.expected, columns)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, c := range columns {
|
||||
if !list.ExistInSlice(c, scenario.expected) {
|
||||
t.Errorf("(%d) Didn't expect column %s", i, c)
|
||||
if !list.ExistInSlice(c, s.expected) {
|
||||
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) {
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
@ -69,16 +99,17 @@ func TestDeleteTable(t *testing.T) {
|
||||
expectError bool
|
||||
}{
|
||||
{"", true},
|
||||
{"test", true},
|
||||
{"test", false}, // missing tables are ignored
|
||||
{"_admins", false},
|
||||
{"demo3", false},
|
||||
}
|
||||
|
||||
for i, scenario := range scenarios {
|
||||
err := app.Dao().DeleteTable(scenario.tableName)
|
||||
for i, s := range scenarios {
|
||||
err := app.Dao().DeleteTable(s.tableName)
|
||||
|
||||
hasErr := err != nil
|
||||
if hasErr != scenario.expectError {
|
||||
t.Errorf("(%d) Expected hasErr %v, got %v", i, scenario.expectError, hasErr)
|
||||
if hasErr != s.expectError {
|
||||
t.Errorf("[%d] Expected hasErr %v, got %v", i, s.expectError, hasErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
583
daos/view.go
Normal file
583
daos/view.go
Normal 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
539
daos/view_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
@ -69,7 +69,7 @@ func NewCollectionUpsert(app core.App, collection *models.Collection) *Collectio
|
||||
}
|
||||
|
||||
clone, _ := form.collection.Schema.Clone()
|
||||
if clone != nil {
|
||||
if clone != nil && form.Type != models.CollectionTypeView {
|
||||
form.Schema = *clone
|
||||
} else {
|
||||
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.
|
||||
func (form *CollectionUpsert) Validate() error {
|
||||
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,
|
||||
validation.Field(
|
||||
@ -104,7 +114,11 @@ func (form *CollectionUpsert) Validate() error {
|
||||
validation.Field(
|
||||
&form.Type,
|
||||
validation.Required,
|
||||
validation.In(models.CollectionTypeAuth, models.CollectionTypeBase),
|
||||
validation.In(
|
||||
models.CollectionTypeBase,
|
||||
models.CollectionTypeAuth,
|
||||
models.CollectionTypeView,
|
||||
),
|
||||
validation.By(form.ensureNoTypeChange),
|
||||
),
|
||||
validation.Field(
|
||||
@ -115,23 +129,32 @@ func (form *CollectionUpsert) Validate() error {
|
||||
validation.By(form.ensureNoSystemNameChange),
|
||||
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(
|
||||
&form.Schema,
|
||||
validation.By(form.checkMinSchemaFields),
|
||||
validation.By(form.ensureNoSystemFieldsChange),
|
||||
validation.By(form.ensureNoFieldsTypeChange),
|
||||
validation.By(form.checkRelationFields),
|
||||
validation.When(
|
||||
isAuth,
|
||||
validation.By(form.ensureNoAuthFieldName),
|
||||
),
|
||||
validation.When(isAuth, validation.By(form.ensureNoAuthFieldName)),
|
||||
),
|
||||
validation.Field(&form.ListRule, validation.By(form.checkRule)),
|
||||
validation.Field(&form.ViewRule, validation.By(form.checkRule)),
|
||||
validation.Field(&form.CreateRule, validation.By(form.checkRule)),
|
||||
validation.Field(&form.UpdateRule, validation.By(form.checkRule)),
|
||||
validation.Field(&form.DeleteRule, validation.By(form.checkRule)),
|
||||
validation.Field(
|
||||
&form.CreateRule,
|
||||
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)),
|
||||
)
|
||||
}
|
||||
@ -288,13 +311,15 @@ func (form *CollectionUpsert) ensureNoAuthFieldName(value any) error {
|
||||
}
|
||||
|
||||
func (form *CollectionUpsert) checkMinSchemaFields(value any) error {
|
||||
if form.Type == models.CollectionTypeAuth {
|
||||
return nil // auth collections doesn't require having additional schema fields
|
||||
}
|
||||
v, _ := value.(schema.Schema)
|
||||
|
||||
v, ok := value.(schema.Schema)
|
||||
if !ok || len(v.Fields()) == 0 {
|
||||
return validation.ErrRequired
|
||||
switch form.Type {
|
||||
case models.CollectionTypeAuth, models.CollectionTypeView:
|
||||
return nil // no schema fields constraint
|
||||
default:
|
||||
if len(v.Fields()) == 0 {
|
||||
return validation.ErrRequired
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -343,15 +368,11 @@ func (form *CollectionUpsert) checkRule(value any) error {
|
||||
func (form *CollectionUpsert) checkOptions(value any) error {
|
||||
v, _ := value.(types.JsonMap)
|
||||
|
||||
if form.Type == models.CollectionTypeAuth {
|
||||
raw, err := v.MarshalJSON()
|
||||
if err != nil {
|
||||
return validation.NewError("validation_invalid_options", "Invalid options.")
|
||||
}
|
||||
|
||||
switch form.Type {
|
||||
case models.CollectionTypeAuth:
|
||||
options := models.CollectionAuthOptions{}
|
||||
if err := json.Unmarshal(raw, &options); err != nil {
|
||||
return validation.NewError("validation_invalid_options", "Invalid options.")
|
||||
if err := decodeOptions(v, &options); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check the generic validations
|
||||
@ -363,6 +384,39 @@ func (form *CollectionUpsert) checkOptions(value any) error {
|
||||
if err := form.checkRule(options.ManageRule); err != nil {
|
||||
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
|
||||
@ -398,7 +452,11 @@ func (form *CollectionUpsert) Submit(interceptors ...InterceptorFunc[*models.Col
|
||||
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.ViewRule = form.ViewRule
|
||||
form.collection.CreateRule = form.CreateRule
|
||||
|
@ -22,7 +22,7 @@ func TestNewCollectionUpsert(t *testing.T) {
|
||||
collection.Name = "test_name"
|
||||
collection.Type = "test_type"
|
||||
collection.System = true
|
||||
listRule := "testview"
|
||||
listRule := "test_list"
|
||||
collection.ListRule = &listRule
|
||||
viewRule := "test_view"
|
||||
collection.ViewRule = &viewRule
|
||||
@ -98,6 +98,7 @@ func TestCollectionUpsertValidateAndSubmit(t *testing.T) {
|
||||
}{
|
||||
{"empty create (base)", "", "{}", []string{"name", "schema"}},
|
||||
{"empty create (auth)", "", `{"type":"auth"}`, []string{"name"}},
|
||||
{"empty create (view)", "", `{"type":"view"}`, []string{"name", "options"}},
|
||||
{"empty update", "demo2", "{}", []string{}},
|
||||
{
|
||||
"create failure",
|
||||
@ -188,7 +189,7 @@ func TestCollectionUpsertValidateAndSubmit(t *testing.T) {
|
||||
[]string{"schema"},
|
||||
},
|
||||
{
|
||||
"create failure - check type options validators",
|
||||
"create failure - check auth options validators",
|
||||
"",
|
||||
`{
|
||||
"name": "test_new",
|
||||
@ -200,6 +201,16 @@ func TestCollectionUpsertValidateAndSubmit(t *testing.T) {
|
||||
}`,
|
||||
[]string{"options"},
|
||||
},
|
||||
{
|
||||
"create failure - check view options validators",
|
||||
"",
|
||||
`{
|
||||
"name": "test_new",
|
||||
"type": "view",
|
||||
"options": { "query": "invalid query" }
|
||||
}`,
|
||||
[]string{"options"},
|
||||
},
|
||||
{
|
||||
"create success",
|
||||
"",
|
||||
@ -356,6 +367,99 @@ func TestCollectionUpsertValidateAndSubmit(t *testing.T) {
|
||||
}`,
|
||||
[]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 {
|
||||
@ -454,10 +558,19 @@ func TestCollectionUpsertValidateAndSubmit(t *testing.T) {
|
||||
t.Errorf("[%s] Expected DeleteRule %v, got %v", s.testName, collection.DeleteRule, form.DeleteRule)
|
||||
}
|
||||
|
||||
formSchema, _ := form.Schema.MarshalJSON()
|
||||
collectionSchema, _ := collection.Schema.MarshalJSON()
|
||||
if string(formSchema) != string(collectionSchema) {
|
||||
t.Errorf("[%s] Expected Schema %v, got %v", s.testName, string(collectionSchema), string(formSchema))
|
||||
rawFormSchema, _ := form.Schema.MarshalJSON()
|
||||
rawCollectionSchema, _ := collection.Schema.MarshalJSON()
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +38,8 @@ func TestCollectionsImportValidate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCollectionsImportSubmit(t *testing.T) {
|
||||
totalCollections := 10
|
||||
|
||||
scenarios := []struct {
|
||||
name string
|
||||
jsonData string
|
||||
@ -52,7 +54,7 @@ func TestCollectionsImportSubmit(t *testing.T) {
|
||||
"collections": []
|
||||
}`,
|
||||
expectError: true,
|
||||
expectCollectionsCount: 8,
|
||||
expectCollectionsCount: totalCollections,
|
||||
expectEvents: nil,
|
||||
},
|
||||
{
|
||||
@ -82,7 +84,7 @@ func TestCollectionsImportSubmit(t *testing.T) {
|
||||
]
|
||||
}`,
|
||||
expectError: true,
|
||||
expectCollectionsCount: 8,
|
||||
expectCollectionsCount: totalCollections,
|
||||
expectEvents: map[string]int{
|
||||
"OnModelBeforeCreate": 2,
|
||||
},
|
||||
@ -101,7 +103,7 @@ func TestCollectionsImportSubmit(t *testing.T) {
|
||||
]
|
||||
}`,
|
||||
expectError: true,
|
||||
expectCollectionsCount: 8,
|
||||
expectCollectionsCount: totalCollections,
|
||||
expectEvents: map[string]int{
|
||||
"OnModelBeforeCreate": 2,
|
||||
},
|
||||
@ -137,7 +139,7 @@ func TestCollectionsImportSubmit(t *testing.T) {
|
||||
]
|
||||
}`,
|
||||
expectError: false,
|
||||
expectCollectionsCount: 11,
|
||||
expectCollectionsCount: totalCollections + 3,
|
||||
expectEvents: map[string]int{
|
||||
"OnModelBeforeCreate": 3,
|
||||
"OnModelAfterCreate": 3,
|
||||
@ -160,7 +162,7 @@ func TestCollectionsImportSubmit(t *testing.T) {
|
||||
]
|
||||
}`,
|
||||
expectError: true,
|
||||
expectCollectionsCount: 8,
|
||||
expectCollectionsCount: totalCollections,
|
||||
expectEvents: map[string]int{
|
||||
"OnModelBeforeCreate": 1,
|
||||
},
|
||||
@ -202,7 +204,7 @@ func TestCollectionsImportSubmit(t *testing.T) {
|
||||
]
|
||||
}`,
|
||||
expectError: true,
|
||||
expectCollectionsCount: 8,
|
||||
expectCollectionsCount: totalCollections,
|
||||
expectEvents: map[string]int{
|
||||
"OnModelBeforeDelete": 5,
|
||||
},
|
||||
@ -253,7 +255,7 @@ func TestCollectionsImportSubmit(t *testing.T) {
|
||||
]
|
||||
}`,
|
||||
expectError: false,
|
||||
expectCollectionsCount: 10,
|
||||
expectCollectionsCount: totalCollections + 2,
|
||||
expectEvents: map[string]int{
|
||||
"OnModelBeforeUpdate": 1,
|
||||
"OnModelAfterUpdate": 1,
|
||||
@ -341,8 +343,8 @@ func TestCollectionsImportSubmit(t *testing.T) {
|
||||
"OnModelAfterUpdate": 2,
|
||||
"OnModelBeforeCreate": 1,
|
||||
"OnModelAfterCreate": 1,
|
||||
"OnModelBeforeDelete": 6,
|
||||
"OnModelAfterDelete": 6,
|
||||
"OnModelBeforeDelete": totalCollections - 2,
|
||||
"OnModelAfterDelete": totalCollections - 2,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ var (
|
||||
const (
|
||||
CollectionTypeBase = "base"
|
||||
CollectionTypeAuth = "auth"
|
||||
CollectionTypeView = "view"
|
||||
)
|
||||
|
||||
type Collection struct {
|
||||
@ -52,11 +53,16 @@ func (m *Collection) IsBase() bool {
|
||||
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 {
|
||||
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.
|
||||
func (m Collection) MarshalJSON() ([]byte, error) {
|
||||
type alias Collection // prevent recursion
|
||||
@ -82,6 +88,14 @@ func (m *Collection) AuthOptions() CollectionAuthOptions {
|
||||
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
|
||||
// new normalized state based on the collection type.
|
||||
func (m *Collection) NormalizeOptions() error {
|
||||
@ -89,6 +103,8 @@ func (m *Collection) NormalizeOptions() error {
|
||||
switch m.Type {
|
||||
case CollectionTypeAuth:
|
||||
typedOptions = m.AuthOptions()
|
||||
case CollectionTypeView:
|
||||
typedOptions = m.ViewOptions()
|
||||
default:
|
||||
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 {
|
||||
}
|
||||
|
||||
@ -152,6 +168,8 @@ func (o CollectionBaseOptions) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
// CollectionAuthOptions defines the "auth" Collection.Options fields.
|
||||
type CollectionAuthOptions struct {
|
||||
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),
|
||||
)
|
||||
}
|
||||
|
@ -97,11 +97,11 @@ func TestCollectionMarshalJSON(t *testing.T) {
|
||||
for _, s := range scenarios {
|
||||
result, err := s.collection.MarshalJSON()
|
||||
if err != nil {
|
||||
t.Errorf("(%s) Unexpected error %v", s.name, err)
|
||||
t.Errorf("[%s] Unexpected error %v", s.name, err)
|
||||
continue
|
||||
}
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
@ -228,7 +273,7 @@ func TestNormalizeOptions(t *testing.T) {
|
||||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
@ -296,7 +341,7 @@ func TestSetOptions(t *testing.T) {
|
||||
}
|
||||
|
||||
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
|
||||
errs, ok := result.(validation.Errors)
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
for key := range errs {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -59,6 +59,9 @@ func nullStringMapValue(data dbx.NullStringMap, key string) any {
|
||||
|
||||
// NewRecordFromNullStringMap initializes a single new Record model
|
||||
// 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 {
|
||||
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
|
||||
// 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 {
|
||||
result := make([]*Record, len(rows))
|
||||
|
||||
@ -469,8 +475,12 @@ func (m *Record) PublicExport() map[string]any {
|
||||
|
||||
// export base model fields
|
||||
result[schema.FieldNameId] = m.GetId()
|
||||
result[schema.FieldNameCreated] = m.GetCreated()
|
||||
result[schema.FieldNameUpdated] = m.GetUpdated()
|
||||
if created := m.GetCreated(); !m.Collection().IsView() || !created.IsZero() {
|
||||
result[schema.FieldNameCreated] = created
|
||||
}
|
||||
if updated := m.GetUpdated(); !m.Collection().IsView() || !updated.IsZero() {
|
||||
result[schema.FieldNameUpdated] = updated
|
||||
}
|
||||
|
||||
// add helper collection reference fields
|
||||
result[schema.FieldNameCollectionId] = m.collection.Id
|
||||
|
@ -139,7 +139,7 @@ type SchemaField struct {
|
||||
func (f *SchemaField) ColDefinition() string {
|
||||
switch f.Type {
|
||||
case FieldTypeNumber:
|
||||
return "REAL DEFAULT 0"
|
||||
return "NUMERIC DEFAULT 0"
|
||||
case FieldTypeBool:
|
||||
return "BOOLEAN DEFAULT FALSE"
|
||||
case FieldTypeJson:
|
||||
|
@ -67,7 +67,7 @@ func TestSchemaFieldColDefinition(t *testing.T) {
|
||||
},
|
||||
{
|
||||
schema.SchemaField{Type: schema.FieldTypeNumber, Name: "test"},
|
||||
"REAL DEFAULT 0",
|
||||
"NUMERIC DEFAULT 0",
|
||||
},
|
||||
{
|
||||
schema.SchemaField{Type: schema.FieldTypeBool, Name: "test"},
|
||||
|
15
models/table_info.go
Normal file
15
models/table_info.go
Normal 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 |
@ -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=="}
|
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
@ -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=="}
|
@ -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 |
@ -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=="}
|
@ -34,8 +34,9 @@ func NewFromBytes(b []byte) *Tokenizer {
|
||||
// New creates new Tokenizer from the provided reader with DefaultSeparators.
|
||||
func New(r io.Reader) *Tokenizer {
|
||||
return &Tokenizer{
|
||||
r: bufio.NewReader(r),
|
||||
separators: DefaultSeparators,
|
||||
r: bufio.NewReader(r),
|
||||
separators: DefaultSeparators,
|
||||
keepSeparator: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,14 +45,21 @@ func New(r io.Reader) *Tokenizer {
|
||||
type Tokenizer struct {
|
||||
r *bufio.Reader
|
||||
|
||||
separators []rune
|
||||
separators []rune
|
||||
keepSeparator bool
|
||||
}
|
||||
|
||||
// SetSeparators specifies the provided separatos of the current Tokenizer.
|
||||
func (s *Tokenizer) SetSeparators(separators ...rune) {
|
||||
// Separators defines the provided separatos of the current Tokenizer.
|
||||
func (s *Tokenizer) Separators(separators ...rune) {
|
||||
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).
|
||||
//
|
||||
// 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.keepSeparator {
|
||||
buf.WriteRune(ch)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
|
@ -34,6 +34,10 @@ func TestFactories(t *testing.T) {
|
||||
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) {
|
||||
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) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
content string
|
||||
separators []rune
|
||||
expectError bool
|
||||
expectTokens []string
|
||||
name string
|
||||
content string
|
||||
separators []rune
|
||||
keepSeparator bool
|
||||
expectError bool
|
||||
expectTokens []string
|
||||
}{
|
||||
{
|
||||
"empty string",
|
||||
"",
|
||||
DefaultSeparators,
|
||||
false,
|
||||
false,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"unbalanced parenthesis",
|
||||
`(a,b() c`,
|
||||
DefaultSeparators,
|
||||
false,
|
||||
true,
|
||||
[]string{},
|
||||
},
|
||||
@ -105,6 +112,7 @@ func TestScanAll(t *testing.T) {
|
||||
"unmatching quotes",
|
||||
`'asd"`,
|
||||
DefaultSeparators,
|
||||
false,
|
||||
true,
|
||||
[]string{},
|
||||
},
|
||||
@ -113,6 +121,7 @@ func TestScanAll(t *testing.T) {
|
||||
`a, b, c, d, e 123, "abc"`,
|
||||
nil,
|
||||
false,
|
||||
false,
|
||||
[]string{
|
||||
`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)`,
|
||||
DefaultSeparators,
|
||||
false,
|
||||
false,
|
||||
[]string{
|
||||
"a",
|
||||
"b",
|
||||
@ -131,6 +141,21 @@ func TestScanAll(t *testing.T) {
|
||||
`(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",
|
||||
` a , 123.456, b, c d, (
|
||||
@ -138,6 +163,7 @@ func TestScanAll(t *testing.T) {
|
||||
),"(abc d", "abc) d", "(abc) d \" " 'abc "'`,
|
||||
[]rune{',', ' ', '\t', '\n'},
|
||||
false,
|
||||
false,
|
||||
[]string{
|
||||
"a",
|
||||
"123.456",
|
||||
@ -151,12 +177,34 @@ func TestScanAll(t *testing.T) {
|
||||
`'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 {
|
||||
tk := NewFromString(s.content)
|
||||
|
||||
tk.SetSeparators(s.separators...)
|
||||
tk.Separators(s.separators...)
|
||||
tk.KeepSeparator(s.keepSeparator)
|
||||
|
||||
tokens, err := tk.ScanAll()
|
||||
|
||||
|
2
ui/.env
2
ui/.env
@ -8,4 +8,4 @@ PB_DOCS_URL = "https://pocketbase.io/docs/"
|
||||
PB_JS_SDK_URL = "https://github.com/pocketbase/js-sdk"
|
||||
PB_DART_SDK_URL = "https://github.com/pocketbase/dart-sdk"
|
||||
PB_RELEASES = "https://github.com/pocketbase/pocketbase/releases"
|
||||
PB_VERSION = "v0.12.3"
|
||||
PB_VERSION = "v0.13.0"
|
||||
|
@ -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';
|
||||
|
||||
const pb = new PocketBase('${a[3]}');
|
@ -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';
|
||||
|
||||
const pb = new PocketBase('${r[3]}');
|
@ -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';
|
||||
|
||||
const pb = new PocketBase('${i[3]}');
|
@ -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&<(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)&<(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&<(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)&<(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';
|
||||
|
||||
const pb = new PocketBase('${n[6]}');
|
14
ui/dist/assets/CodeEditor-4caff52a.js
vendored
Normal file
14
ui/dist/assets/CodeEditor-4caff52a.js
vendored
Normal file
File diff suppressed because one or more lines are too long
13
ui/dist/assets/CodeEditor-a45f274f.js
vendored
13
ui/dist/assets/CodeEditor-a45f274f.js
vendored
File diff suppressed because one or more lines are too long
@ -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';
|
||||
|
||||
const pb = new PocketBase('${o[3]}');
|
@ -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';
|
||||
|
||||
const pb = new PocketBase('${o[3]}');
|
@ -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';
|
||||
|
||||
const pb = new PocketBase('${i[3]}');
|
@ -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>
|
||||
<td><span class="label">String</span></td>
|
||||
<td>The username of the auth record.
|
@ -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';
|
||||
|
||||
const pb = new PocketBase('${o[3]}');
|
File diff suppressed because one or more lines are too long
1
ui/dist/assets/FilterAutocompleteInput-80716b22.js
vendored
Normal file
1
ui/dist/assets/FilterAutocompleteInput-80716b22.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -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>
|
||||
<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
|
@ -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';
|
||||
|
||||
const pb = new PocketBase('${a[3]}');
|
@ -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};
|
@ -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 we’ll 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};
|
@ -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>
|
||||
<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};
|
@ -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>
|
||||
<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};
|
@ -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>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};
|
@ -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';
|
||||
|
||||
const pb = new PocketBase('${o[1]}');
|
@ -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';
|
||||
|
||||
const pb = new PocketBase('${o[3]}');
|
@ -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';
|
||||
|
||||
const pb = new PocketBase('${a[3]}');
|
@ -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';
|
||||
|
||||
const pb = new PocketBase('${a[3]}');
|
@ -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};
|
@ -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';
|
||||
|
||||
const pb = new PocketBase('${n[3]}');
|
@ -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>
|
||||
<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>
|
@ -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';
|
||||
|
||||
const pb = new PocketBase('${i[3]}');
|
200
ui/dist/assets/index-3e0f12d8.js
vendored
200
ui/dist/assets/index-3e0f12d8.js
vendored
File diff suppressed because one or more lines are too long
13
ui/dist/assets/index-4934dad7.js
vendored
13
ui/dist/assets/index-4934dad7.js
vendored
File diff suppressed because one or more lines are too long
1
ui/dist/assets/index-7f786dc0.css
vendored
1
ui/dist/assets/index-7f786dc0.css
vendored
File diff suppressed because one or more lines are too long
13
ui/dist/assets/index-a6ccb683.js
vendored
Normal file
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
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
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
26
ui/dist/index.html
vendored
@ -17,15 +17,35 @@
|
||||
<link rel="mask-icon" href="./images/favicon/safari-pinned-tab.svg" color="#000000">
|
||||
<link rel="shortcut icon" href="./images/favicon/favicon.ico">
|
||||
<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">
|
||||
|
||||
<!-- 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>
|
||||
window.Prism = window.Prism || {};
|
||||
window.Prism.manual = true;
|
||||
</script>
|
||||
<script type="module" crossorigin src="./assets/index-3e0f12d8.js"></script>
|
||||
<link rel="stylesheet" href="./assets/index-7f786dc0.css">
|
||||
<script type="module" crossorigin src="./assets/index-ffbb9561.js"></script>
|
||||
<link rel="stylesheet" href="./assets/index-b8b82b53.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
@ -10,16 +10,36 @@
|
||||
|
||||
<title>PocketBase</title>
|
||||
|
||||
<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="16x16" href="./images/favicon/favicon-16x16.png">
|
||||
<link rel="manifest" href="./images/favicon/site.webmanifest">
|
||||
<link rel="mask-icon" href="./images/favicon/safari-pinned-tab.svg" color="#000000">
|
||||
<link rel="shortcut icon" href="./images/favicon/favicon.ico">
|
||||
<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="16x16" href="/images/favicon/favicon-16x16.png">
|
||||
<link rel="manifest" href="/images/favicon/site.webmanifest">
|
||||
<link rel="mask-icon" href="/images/favicon/safari-pinned-tab.svg" color="#000000">
|
||||
<link rel="shortcut icon" href="/images/favicon/favicon.ico">
|
||||
<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">
|
||||
|
||||
<!-- 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>
|
||||
window.Prism = window.Prism || {};
|
||||
window.Prism.manual = true;
|
||||
|
225
ui/package-lock.json
generated
225
ui/package-lock.json
generated
@ -10,6 +10,7 @@
|
||||
"@codemirror/commands": "^6.0.0",
|
||||
"@codemirror/lang-html": "^6.1.0",
|
||||
"@codemirror/lang-javascript": "^6.0.2",
|
||||
"@codemirror/lang-sql": "^6.4.0",
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/legacy-modes": "^6.0.0",
|
||||
"@codemirror/search": "^6.0.0",
|
||||
@ -20,7 +21,7 @@
|
||||
"chart.js": "^3.7.1",
|
||||
"chartjs-adapter-luxon": "^1.2.0",
|
||||
"luxon": "^2.3.2",
|
||||
"pocketbase": "^0.10.0",
|
||||
"pocketbase": "^0.10.2",
|
||||
"prismjs": "^1.28.0",
|
||||
"sass": "^1.45.0",
|
||||
"svelte": "^3.44.0",
|
||||
@ -30,9 +31,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/autocomplete": {
|
||||
"version": "6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.4.0.tgz",
|
||||
"integrity": "sha512-HLF2PnZAm1s4kGs30EiqKMgD7XsYaQ0XJnMR0rofEWQ5t5D60SfqpDIkIh1ze5tiEbyUWm8+VJ6W1/erVvBMIA==",
|
||||
"version": "6.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.4.1.tgz",
|
||||
"integrity": "sha512-06yAmj0FjPZzYOpNeugJtG28GNqU2/CPr34m91Q+fKSyTOR6+hDFiatkPcIkxOlU0K5yP7WH6KoLg3fTqIUgaw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
@ -48,9 +49,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/commands": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.2.0.tgz",
|
||||
"integrity": "sha512-+00smmZBradoGFEkRjliN7BjqPh/Hx0KCHWOEibUmflUqZz2RwBTU0MrVovEEHozhx3AUSGcO/rl3/5f9e9Biw==",
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.2.1.tgz",
|
||||
"integrity": "sha512-FFiNKGuHA5O8uC6IJE5apI5rT9gyjlw4whqy4vlcX0wE/myxL6P1s0upwDhY4HtMWLOwzwsp0ap3bjdQhvfDOA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
@ -72,9 +73,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-html": {
|
||||
"version": "6.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.1.tgz",
|
||||
"integrity": "sha512-9NzhWKAkWEwjXC04DKM6yrHnxIPFTqZNLDhWfZiKLMxUiU++XoHz9n6D5EPp1igBmX0vXcpFb5Kud6XzIJhZ4A==",
|
||||
"version": "6.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.2.tgz",
|
||||
"integrity": "sha512-bqCBASkteKySwtIbiV/WCtGnn/khLRbbiV5TE+d9S9eQJD7BA4c5dTRm2b3bVmSpilff5EYxvB4PQaZzM/7cNw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
@ -89,13 +90,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-javascript": {
|
||||
"version": "6.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.1.2.tgz",
|
||||
"integrity": "sha512-OcwLfZXdQ1OHrLiIcKCn7MqZ7nx205CMKlhe+vL88pe2ymhT9+2P+QhwkYGxMICj8TDHyp8HFKVwpiisUT7iEQ==",
|
||||
"version": "6.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.1.4.tgz",
|
||||
"integrity": "sha512-OxLf7OfOZBTMRMi6BO/F72MNGmgOd9B0vetOLvHsDACFXayBzW8fm8aWnDM0yuy68wTK03MBf4HbjSBNRG5q7A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/language": "^6.6.0",
|
||||
"@codemirror/lint": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
@ -103,10 +104,23 @@
|
||||
"@lezer/javascript": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/language": {
|
||||
"node_modules/@codemirror/lang-sql": {
|
||||
"version": "6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.4.0.tgz",
|
||||
"integrity": "sha512-Wzb7GnNj8vnEtbPWiOy9H0m1fBtE28kepQNGLXekU2EEZv43BF865VKITUn+NoV8OpW6gRtvm29YEhqm46927Q==",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-sql/-/lang-sql-6.4.0.tgz",
|
||||
"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,
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
@ -127,9 +141,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lint": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.1.0.tgz",
|
||||
"integrity": "sha512-mdvDQrjRmYPvQ3WrzF6Ewaao+NWERYtpthJvoQ3tK3t/44Ynhk8ZGjTSL9jMEv8CgSMogmt75X8ceOZRDSXHtQ==",
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.1.1.tgz",
|
||||
"integrity": "sha512-e+M543x0NVHGayNHQzLP4XByJsvbu/ojY6+0VF2Y4Uu66Rt1nADuxNflZwECLf7gS009smIsptSUa6bUj/U/rw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
@ -155,9 +169,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@codemirror/view": {
|
||||
"version": "6.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.7.3.tgz",
|
||||
"integrity": "sha512-Lt+4POnhXrZFfHOdPzXEHxrzwdy7cjqYlMkOWvoFGi6/bAsjzlFfr0NY3B15B/PGx+cDFgM1hlc12wvYeZbGLw==",
|
||||
"version": "6.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.9.0.tgz",
|
||||
"integrity": "sha512-uFaqE6fBOp0Dj/tmWoe/TFlSSIPdpAzhvATgbq1eAKRkgq3hY79FioZ7nfdiT+24kz68AIWuSZ9hi3psKe36WQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.1.4",
|
||||
@ -549,9 +563,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/html": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.0.tgz",
|
||||
"integrity": "sha512-jU/ah8DEoiECLTMouU/X/ujIg6k9WQMIOFMaCLebzaXfrguyGaR3DpTgmk0tbljiuIJ7hlmVJPcJcxGzmCd0Mg==",
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.2.tgz",
|
||||
"integrity": "sha512-LKGyDdqqDugXR/lKM9FwaKEfMerbZ/aqvhLf0P1FLLK/pVP7wKHXGcg6g3cJ7ckvFidn0tXA8jioG0irVsCBAQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.0.0",
|
||||
@ -570,9 +584,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/lr": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.2.tgz",
|
||||
"integrity": "sha512-SDSvnHWMBH6WxoOt51AjuHOiQ0DMxxhfK5lNoyJXuv5POWz6MfXKGU9Fus9tK8NqrI1sSBNdKtG5cUXXZtGG5w==",
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.3.tgz",
|
||||
"integrity": "sha512-JPQe3mwJlzEVqy67iQiiGozhcngbO8QBgpqZM6oL1Wj/dXckrEexpBLeFkq0edtW5IqnPRFxA24BHJni8Js69w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.0.0"
|
||||
@ -649,9 +663,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/chartjs-adapter-luxon": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/chartjs-adapter-luxon/-/chartjs-adapter-luxon-1.3.0.tgz",
|
||||
"integrity": "sha512-TPqS8S7aw4a07LhFzG5DZU6Kduk1xFkaGTn8y/gfhBRvfyCkqnwFqfXqG9Gl+gmnj5DRXgPscApJUE6bsgzKjQ==",
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/chartjs-adapter-luxon/-/chartjs-adapter-luxon-1.3.1.tgz",
|
||||
"integrity": "sha512-yxHov3X8y+reIibl1o+j18xzrcdddCLqsXhriV2+aQ4hCR66IYFchlRXUvrJVoxglJ380pgytU7YWtoqdIgqhg==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"chart.js": ">=3.0.0",
|
||||
@ -817,9 +831,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/immutable": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.2.tgz",
|
||||
"integrity": "sha512-fTMKDwtbvO5tldky9QZ2fMX7slR0mYpY5nbnFWYp0fOzDhHqhgIw9KoYgxLWsoNTS9ZHGauHj18DTyEw6BK3Og==",
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.4.tgz",
|
||||
"integrity": "sha512-WDxL3Hheb1JkRN3sQkyujNlL/xRjAo3rJtaU5xeufUauG66JdMr32bLj4gF+vWl84DIA3Zxw7tiAjneYzRRw+w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/is-binary-path": {
|
||||
@ -958,9 +972,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/pocketbase": {
|
||||
"version": "0.10.1",
|
||||
"resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.10.1.tgz",
|
||||
"integrity": "sha512-FMBhF+9o2AmdKJYbfz2qunnk4Q5/efYMRcukdQF49UfhsIuaSYl39fSPN1l880bYI4XAvDDeySAjK1MlxrK37A==",
|
||||
"version": "0.10.2",
|
||||
"resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.10.2.tgz",
|
||||
"integrity": "sha512-728aVaPLhSwOyEqZ4+GJfqg1SHUAyokvErsOksxauvOXiRE/QSCn8s6bW1/KyU3NGqz248eBjUQY3aLwTV/CcA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
@ -1035,9 +1049,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "3.12.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.12.1.tgz",
|
||||
"integrity": "sha512-t9elERrz2i4UU9z7AwISj3CQcXP39cWxgRWLdf4Tm6aKm1eYrqHIgjzXBgb67GNY1sZckTFFi0oMozh3/S++Ig==",
|
||||
"version": "3.15.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.15.0.tgz",
|
||||
"integrity": "sha512-F9hrCAhnp5/zx/7HYmftvsNBkMfLfk/dXUh73hPSM2E3CRgap65orDNJbLetoiUFwSAk6iHPLvBrZ5iHYvzqsg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
@ -1057,9 +1071,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/sass": {
|
||||
"version": "1.58.0",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.58.0.tgz",
|
||||
"integrity": "sha512-PiMJcP33DdKtZ/1jSjjqVIKihoDc6yWmYr9K/4r3fVVIEDAluD0q7XZiRKrNJcPK3qkLRF/79DND1H5q1LBjgg==",
|
||||
"version": "1.58.1",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.58.1.tgz",
|
||||
"integrity": "sha512-bnINi6nPXbP1XNRaranMFEBZWUfdW/AF16Ql5+ypRxfTvCRTTKrLsMIakyDcayUt2t/RZotmL4kgJwNH5xO+bg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chokidar": ">=3.0.0 <4.0.0",
|
||||
@ -1158,15 +1172,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.0.4.tgz",
|
||||
"integrity": "sha512-xevPU7M8FU0i/80DMR+YhgrzR5KS2ORy1B4xcX/cXLsvnUWvfHuqMmVU6N0YiJ4JWGRJJsLCgjEzKjG9/GKoSw==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.1.1.tgz",
|
||||
"integrity": "sha512-LM9WWea8vsxhr782r9ntg+bhSFS06FJgCvvB0+8hf8UWtvaiDagKYWXndjfX6kGl74keHJUcpzrQliDXZlF5yg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.16.3",
|
||||
"postcss": "^8.4.20",
|
||||
"esbuild": "^0.16.14",
|
||||
"postcss": "^8.4.21",
|
||||
"resolve": "^1.22.1",
|
||||
"rollup": "^3.7.0"
|
||||
"rollup": "^3.10.0"
|
||||
},
|
||||
"bin": {
|
||||
"vite": "bin/vite.js"
|
||||
@ -1229,9 +1243,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": {
|
||||
"version": "6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.4.0.tgz",
|
||||
"integrity": "sha512-HLF2PnZAm1s4kGs30EiqKMgD7XsYaQ0XJnMR0rofEWQ5t5D60SfqpDIkIh1ze5tiEbyUWm8+VJ6W1/erVvBMIA==",
|
||||
"version": "6.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.4.1.tgz",
|
||||
"integrity": "sha512-06yAmj0FjPZzYOpNeugJtG28GNqU2/CPr34m91Q+fKSyTOR6+hDFiatkPcIkxOlU0K5yP7WH6KoLg3fTqIUgaw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
@ -1241,9 +1255,9 @@
|
||||
}
|
||||
},
|
||||
"@codemirror/commands": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.2.0.tgz",
|
||||
"integrity": "sha512-+00smmZBradoGFEkRjliN7BjqPh/Hx0KCHWOEibUmflUqZz2RwBTU0MrVovEEHozhx3AUSGcO/rl3/5f9e9Biw==",
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.2.1.tgz",
|
||||
"integrity": "sha512-FFiNKGuHA5O8uC6IJE5apI5rT9gyjlw4whqy4vlcX0wE/myxL6P1s0upwDhY4HtMWLOwzwsp0ap3bjdQhvfDOA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
@ -1265,9 +1279,9 @@
|
||||
}
|
||||
},
|
||||
"@codemirror/lang-html": {
|
||||
"version": "6.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.1.tgz",
|
||||
"integrity": "sha512-9NzhWKAkWEwjXC04DKM6yrHnxIPFTqZNLDhWfZiKLMxUiU++XoHz9n6D5EPp1igBmX0vXcpFb5Kud6XzIJhZ4A==",
|
||||
"version": "6.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.2.tgz",
|
||||
"integrity": "sha512-bqCBASkteKySwtIbiV/WCtGnn/khLRbbiV5TE+d9S9eQJD7BA4c5dTRm2b3bVmSpilff5EYxvB4PQaZzM/7cNw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
@ -1282,13 +1296,13 @@
|
||||
}
|
||||
},
|
||||
"@codemirror/lang-javascript": {
|
||||
"version": "6.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.1.2.tgz",
|
||||
"integrity": "sha512-OcwLfZXdQ1OHrLiIcKCn7MqZ7nx205CMKlhe+vL88pe2ymhT9+2P+QhwkYGxMICj8TDHyp8HFKVwpiisUT7iEQ==",
|
||||
"version": "6.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.1.4.tgz",
|
||||
"integrity": "sha512-OxLf7OfOZBTMRMi6BO/F72MNGmgOd9B0vetOLvHsDACFXayBzW8fm8aWnDM0yuy68wTK03MBf4HbjSBNRG5q7A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/language": "^6.6.0",
|
||||
"@codemirror/lint": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
@ -1296,10 +1310,23 @@
|
||||
"@lezer/javascript": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"@codemirror/language": {
|
||||
"@codemirror/lang-sql": {
|
||||
"version": "6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.4.0.tgz",
|
||||
"integrity": "sha512-Wzb7GnNj8vnEtbPWiOy9H0m1fBtE28kepQNGLXekU2EEZv43BF865VKITUn+NoV8OpW6gRtvm29YEhqm46927Q==",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-sql/-/lang-sql-6.4.0.tgz",
|
||||
"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,
|
||||
"requires": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
@ -1320,9 +1347,9 @@
|
||||
}
|
||||
},
|
||||
"@codemirror/lint": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.1.0.tgz",
|
||||
"integrity": "sha512-mdvDQrjRmYPvQ3WrzF6Ewaao+NWERYtpthJvoQ3tK3t/44Ynhk8ZGjTSL9jMEv8CgSMogmt75X8ceOZRDSXHtQ==",
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.1.1.tgz",
|
||||
"integrity": "sha512-e+M543x0NVHGayNHQzLP4XByJsvbu/ojY6+0VF2Y4Uu66Rt1nADuxNflZwECLf7gS009smIsptSUa6bUj/U/rw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
@ -1348,9 +1375,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@codemirror/view": {
|
||||
"version": "6.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.7.3.tgz",
|
||||
"integrity": "sha512-Lt+4POnhXrZFfHOdPzXEHxrzwdy7cjqYlMkOWvoFGi6/bAsjzlFfr0NY3B15B/PGx+cDFgM1hlc12wvYeZbGLw==",
|
||||
"version": "6.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.9.0.tgz",
|
||||
"integrity": "sha512-uFaqE6fBOp0Dj/tmWoe/TFlSSIPdpAzhvATgbq1eAKRkgq3hY79FioZ7nfdiT+24kz68AIWuSZ9hi3psKe36WQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@codemirror/state": "^6.1.4",
|
||||
@ -1544,9 +1571,9 @@
|
||||
}
|
||||
},
|
||||
"@lezer/html": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.0.tgz",
|
||||
"integrity": "sha512-jU/ah8DEoiECLTMouU/X/ujIg6k9WQMIOFMaCLebzaXfrguyGaR3DpTgmk0tbljiuIJ7hlmVJPcJcxGzmCd0Mg==",
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.2.tgz",
|
||||
"integrity": "sha512-LKGyDdqqDugXR/lKM9FwaKEfMerbZ/aqvhLf0P1FLLK/pVP7wKHXGcg6g3cJ7ckvFidn0tXA8jioG0irVsCBAQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@lezer/common": "^1.0.0",
|
||||
@ -1565,9 +1592,9 @@
|
||||
}
|
||||
},
|
||||
"@lezer/lr": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.2.tgz",
|
||||
"integrity": "sha512-SDSvnHWMBH6WxoOt51AjuHOiQ0DMxxhfK5lNoyJXuv5POWz6MfXKGU9Fus9tK8NqrI1sSBNdKtG5cUXXZtGG5w==",
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.3.tgz",
|
||||
"integrity": "sha512-JPQe3mwJlzEVqy67iQiiGozhcngbO8QBgpqZM6oL1Wj/dXckrEexpBLeFkq0edtW5IqnPRFxA24BHJni8Js69w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@lezer/common": "^1.0.0"
|
||||
@ -1628,9 +1655,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"chartjs-adapter-luxon": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/chartjs-adapter-luxon/-/chartjs-adapter-luxon-1.3.0.tgz",
|
||||
"integrity": "sha512-TPqS8S7aw4a07LhFzG5DZU6Kduk1xFkaGTn8y/gfhBRvfyCkqnwFqfXqG9Gl+gmnj5DRXgPscApJUE6bsgzKjQ==",
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/chartjs-adapter-luxon/-/chartjs-adapter-luxon-1.3.1.tgz",
|
||||
"integrity": "sha512-yxHov3X8y+reIibl1o+j18xzrcdddCLqsXhriV2+aQ4hCR66IYFchlRXUvrJVoxglJ380pgytU7YWtoqdIgqhg==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
@ -1748,9 +1775,9 @@
|
||||
}
|
||||
},
|
||||
"immutable": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.2.tgz",
|
||||
"integrity": "sha512-fTMKDwtbvO5tldky9QZ2fMX7slR0mYpY5nbnFWYp0fOzDhHqhgIw9KoYgxLWsoNTS9ZHGauHj18DTyEw6BK3Og==",
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.4.tgz",
|
||||
"integrity": "sha512-WDxL3Hheb1JkRN3sQkyujNlL/xRjAo3rJtaU5xeufUauG66JdMr32bLj4gF+vWl84DIA3Zxw7tiAjneYzRRw+w==",
|
||||
"dev": true
|
||||
},
|
||||
"is-binary-path": {
|
||||
@ -1850,9 +1877,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"pocketbase": {
|
||||
"version": "0.10.1",
|
||||
"resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.10.1.tgz",
|
||||
"integrity": "sha512-FMBhF+9o2AmdKJYbfz2qunnk4Q5/efYMRcukdQF49UfhsIuaSYl39fSPN1l880bYI4XAvDDeySAjK1MlxrK37A==",
|
||||
"version": "0.10.2",
|
||||
"resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.10.2.tgz",
|
||||
"integrity": "sha512-728aVaPLhSwOyEqZ4+GJfqg1SHUAyokvErsOksxauvOXiRE/QSCn8s6bW1/KyU3NGqz248eBjUQY3aLwTV/CcA==",
|
||||
"dev": true
|
||||
},
|
||||
"postcss": {
|
||||
@ -1899,9 +1926,9 @@
|
||||
}
|
||||
},
|
||||
"rollup": {
|
||||
"version": "3.12.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.12.1.tgz",
|
||||
"integrity": "sha512-t9elERrz2i4UU9z7AwISj3CQcXP39cWxgRWLdf4Tm6aKm1eYrqHIgjzXBgb67GNY1sZckTFFi0oMozh3/S++Ig==",
|
||||
"version": "3.15.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.15.0.tgz",
|
||||
"integrity": "sha512-F9hrCAhnp5/zx/7HYmftvsNBkMfLfk/dXUh73hPSM2E3CRgap65orDNJbLetoiUFwSAk6iHPLvBrZ5iHYvzqsg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fsevents": "~2.3.2"
|
||||
@ -1914,9 +1941,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"sass": {
|
||||
"version": "1.58.0",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.58.0.tgz",
|
||||
"integrity": "sha512-PiMJcP33DdKtZ/1jSjjqVIKihoDc6yWmYr9K/4r3fVVIEDAluD0q7XZiRKrNJcPK3qkLRF/79DND1H5q1LBjgg==",
|
||||
"version": "1.58.1",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.58.1.tgz",
|
||||
"integrity": "sha512-bnINi6nPXbP1XNRaranMFEBZWUfdW/AF16Ql5+ypRxfTvCRTTKrLsMIakyDcayUt2t/RZotmL4kgJwNH5xO+bg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chokidar": ">=3.0.0 <4.0.0",
|
||||
@ -1983,16 +2010,16 @@
|
||||
}
|
||||
},
|
||||
"vite": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.0.4.tgz",
|
||||
"integrity": "sha512-xevPU7M8FU0i/80DMR+YhgrzR5KS2ORy1B4xcX/cXLsvnUWvfHuqMmVU6N0YiJ4JWGRJJsLCgjEzKjG9/GKoSw==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.1.1.tgz",
|
||||
"integrity": "sha512-LM9WWea8vsxhr782r9ntg+bhSFS06FJgCvvB0+8hf8UWtvaiDagKYWXndjfX6kGl74keHJUcpzrQliDXZlF5yg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esbuild": "^0.16.3",
|
||||
"esbuild": "^0.16.14",
|
||||
"fsevents": "~2.3.2",
|
||||
"postcss": "^8.4.20",
|
||||
"postcss": "^8.4.21",
|
||||
"resolve": "^1.22.1",
|
||||
"rollup": "^3.7.0"
|
||||
"rollup": "^3.10.0"
|
||||
}
|
||||
},
|
||||
"vitefu": {
|
||||
|
@ -17,6 +17,7 @@
|
||||
"@codemirror/commands": "^6.0.0",
|
||||
"@codemirror/lang-html": "^6.1.0",
|
||||
"@codemirror/lang-javascript": "^6.0.2",
|
||||
"@codemirror/lang-sql": "^6.4.0",
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/legacy-modes": "^6.0.0",
|
||||
"@codemirror/search": "^6.0.0",
|
||||
@ -27,7 +28,7 @@
|
||||
"chart.js": "^3.7.1",
|
||||
"chartjs-adapter-luxon": "^1.2.0",
|
||||
"luxon": "^2.3.2",
|
||||
"pocketbase": "^0.10.0",
|
||||
"pocketbase": "^0.10.2",
|
||||
"prismjs": "^1.28.0",
|
||||
"sass": "^1.45.0",
|
||||
"svelte": "^3.44.0",
|
||||
|
@ -142,10 +142,10 @@
|
||||
|
||||
<form id={formId} class="grid" autocomplete="off" on:submit|preventDefault={save}>
|
||||
{#if !admin.isNew}
|
||||
<Field class="form-field disabled" name="id" let:uniqueId>
|
||||
<Field class="form-field readonly" name="id" let:uniqueId>
|
||||
<label for={uniqueId}>
|
||||
<i class={CommonHelper.getFieldTypeIcon("primary")} />
|
||||
<span class="txt">ID</span>
|
||||
<span class="txt">id</span>
|
||||
</label>
|
||||
<div class="form-field-addon">
|
||||
<i
|
||||
@ -156,7 +156,7 @@
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<input type="text" id={uniqueId} value={admin.id} disabled />
|
||||
<input type="text" id={uniqueId} value={admin.id} readonly />
|
||||
</Field>
|
||||
{/if}
|
||||
|
||||
|
@ -53,13 +53,17 @@
|
||||
closeBracketsKeymap,
|
||||
} from "@codemirror/autocomplete";
|
||||
import { html as htmlLang } from "@codemirror/lang-html";
|
||||
import { sql, SQLDialect } from "@codemirror/lang-sql";
|
||||
import { javascript as javascriptLang } from "@codemirror/lang-javascript";
|
||||
// ---
|
||||
import CommonHelper from "@/utils/CommonHelper";
|
||||
import { collections } from "@/stores/collections";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let id = "";
|
||||
export let value = "";
|
||||
export let minHeight = null;
|
||||
export let maxHeight = null;
|
||||
export let disabled = false;
|
||||
export let placeholder = "";
|
||||
@ -123,6 +127,8 @@
|
||||
bubbles: true,
|
||||
})
|
||||
);
|
||||
|
||||
dispatch("change", value);
|
||||
}
|
||||
|
||||
// Remove any attached label listeners.
|
||||
@ -153,7 +159,33 @@
|
||||
|
||||
// Returns the current active editor language.
|
||||
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(() => {
|
||||
@ -222,4 +254,9 @@
|
||||
});
|
||||
</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"}
|
||||
/>
|
||||
|
@ -217,25 +217,11 @@
|
||||
return [];
|
||||
}
|
||||
|
||||
let result = [
|
||||
// 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");
|
||||
}
|
||||
let result = CommonHelper.getAllCollectionIdentifiers(collection, prefix);
|
||||
|
||||
for (const field of collection.schema) {
|
||||
const key = prefix + field.name;
|
||||
|
||||
result.push(key);
|
||||
|
||||
// add relation fields
|
||||
if (field.type === "relation" && field.options?.collectionId) {
|
||||
const subKeys = getCollectionFieldKeys(field.options.collectionId, key + ".", level + 1);
|
||||
|
@ -93,6 +93,12 @@
|
||||
if (!collection?.options.allowOAuth2Auth) {
|
||||
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 {
|
||||
tabs = Object.assign({}, baseTabs);
|
||||
}
|
||||
|
105
ui/src/components/collections/CollectionQueryTab.svelte
Normal file
105
ui/src/components/collections/CollectionQueryTab.svelte
Normal 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>
|
@ -1,10 +1,13 @@
|
||||
<script>
|
||||
import { slide } from "svelte/transition";
|
||||
import { Collection } from "pocketbase";
|
||||
import CommonHelper from "@/utils/CommonHelper";
|
||||
import RuleField from "@/components/collections/RuleField.svelte";
|
||||
|
||||
export let collection = new Collection();
|
||||
|
||||
$: fields = CommonHelper.getAllCollectionIdentifiers(collection);
|
||||
|
||||
let showFiltersInfo = false;
|
||||
</script>
|
||||
|
||||
@ -31,15 +34,8 @@
|
||||
<div class="content">
|
||||
<p class="m-b-0">The following record fields are available:</p>
|
||||
<div class="inline-flex flex-gap-5">
|
||||
<code>id</code>
|
||||
<code>created</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 fields as name}
|
||||
<code>{name}</code>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@ -84,14 +80,16 @@
|
||||
<hr class="m-t-sm m-b-sm" />
|
||||
<RuleField label="View action" formKey="viewRule" {collection} bind:rule={collection.viewRule} />
|
||||
|
||||
<hr class="m-t-sm m-b-sm" />
|
||||
<RuleField label="Create action" formKey="createRule" {collection} bind:rule={collection.createRule} />
|
||||
{#if !collection?.isView}
|
||||
<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" />
|
||||
<RuleField label="Update action" formKey="updateRule" {collection} bind:rule={collection.updateRule} />
|
||||
<hr class="m-t-sm m-b-sm" />
|
||||
<RuleField label="Update action" formKey="updateRule" {collection} bind:rule={collection.updateRule} />
|
||||
|
||||
<hr class="m-t-sm m-b-sm" />
|
||||
<RuleField label="Delete action" formKey="deleteRule" {collection} bind:rule={collection.deleteRule} />
|
||||
<hr class="m-t-sm m-b-sm" />
|
||||
<RuleField label="Delete action" formKey="deleteRule" {collection} bind:rule={collection.deleteRule} />
|
||||
{/if}
|
||||
|
||||
{#if collection?.isAuth}
|
||||
<hr class="m-t-sm m-b-sm" />
|
||||
|
@ -16,6 +16,8 @@
|
||||
|
||||
$: deletedFields = collection?.schema.filter((field) => field.id && field.toDelete) || [];
|
||||
|
||||
$: showChanges = isCollectionRenamed || !collection?.isView;
|
||||
|
||||
export async function show(collectionToCheck) {
|
||||
collection = collectionToCheck;
|
||||
|
||||
@ -50,8 +52,8 @@
|
||||
</div>
|
||||
<div class="content txt-bold">
|
||||
<p>
|
||||
If any of the following changes is part of another collection rule or filter, you'll have to
|
||||
update it manually!
|
||||
If any of the collection changes is part of another collection rule, filter or view query,
|
||||
you'll have to update it manually!
|
||||
</p>
|
||||
{#if deletedFields.length}
|
||||
<p>All data associated with the removed fields will be permanently deleted!</p>
|
||||
@ -59,36 +61,40 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6>Changes:</h6>
|
||||
<ul class="changes-list">
|
||||
{#if isCollectionRenamed}
|
||||
<li>
|
||||
<div class="inline-flex">
|
||||
Renamed collection
|
||||
<strong class="txt-strikethrough txt-hint">{collection.originalName}</strong>
|
||||
<i class="ri-arrow-right-line txt-sm" />
|
||||
<strong class="txt"> {collection.name}</strong>
|
||||
</div>
|
||||
</li>
|
||||
{/if}
|
||||
{#if showChanges}
|
||||
<h6>Changes:</h6>
|
||||
<ul class="changes-list">
|
||||
{#if isCollectionRenamed}
|
||||
<li>
|
||||
<div class="inline-flex">
|
||||
Renamed collection
|
||||
<strong class="txt-strikethrough txt-hint">{collection.originalName}</strong>
|
||||
<i class="ri-arrow-right-line txt-sm" />
|
||||
<strong class="txt"> {collection.name}</strong>
|
||||
</div>
|
||||
</li>
|
||||
{/if}
|
||||
|
||||
{#each renamedFields as field}
|
||||
<li>
|
||||
<div class="inline-flex">
|
||||
Renamed field
|
||||
<strong class="txt-strikethrough txt-hint">{field.originalName}</strong>
|
||||
<i class="ri-arrow-right-line txt-sm" />
|
||||
<strong class="txt"> {field.name}</strong>
|
||||
</div>
|
||||
</li>
|
||||
{/each}
|
||||
{#if !collection?.isView}
|
||||
{#each renamedFields as field}
|
||||
<li>
|
||||
<div class="inline-flex">
|
||||
Renamed field
|
||||
<strong class="txt-strikethrough txt-hint">{field.originalName}</strong>
|
||||
<i class="ri-arrow-right-line txt-sm" />
|
||||
<strong class="txt"> {field.name}</strong>
|
||||
</div>
|
||||
</li>
|
||||
{/each}
|
||||
|
||||
{#each deletedFields as field}
|
||||
<li class="txt-danger">
|
||||
Removed field <span class="txt-bold">{field.name}</span>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{#each deletedFields as field}
|
||||
<li class="txt-danger">
|
||||
Removed field <span class="txt-bold">{field.name}</span>
|
||||
</li>
|
||||
{/each}
|
||||
{/if}
|
||||
</ul>
|
||||
{/if}
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<!-- svelte-ignore a11y-autofocus -->
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script>
|
||||
import { Collection } from "pocketbase";
|
||||
import { createEventDispatcher, tick } from "svelte";
|
||||
import { scale } from "svelte/transition";
|
||||
import { Collection } from "pocketbase";
|
||||
import CommonHelper from "@/utils/CommonHelper";
|
||||
import ApiClient from "@/utils/ApiClient";
|
||||
import { errors, setErrors, removeError } from "@/stores/errors";
|
||||
@ -14,18 +14,21 @@
|
||||
import OverlayPanel from "@/components/base/OverlayPanel.svelte";
|
||||
import CollectionFieldsTab from "@/components/collections/CollectionFieldsTab.svelte";
|
||||
import CollectionRulesTab from "@/components/collections/CollectionRulesTab.svelte";
|
||||
import CollectionQueryTab from "@/components/collections/CollectionQueryTab.svelte";
|
||||
import CollectionAuthOptionsTab from "@/components/collections/CollectionAuthOptionsTab.svelte";
|
||||
import CollectionUpdateConfirm from "@/components/collections/CollectionUpdateConfirm.svelte";
|
||||
|
||||
const TAB_FIELDS = "fields";
|
||||
const TAB_SCHEMA = "schema";
|
||||
const TAB_RULES = "api_rules";
|
||||
const TAB_OPTIONS = "options";
|
||||
|
||||
const TYPE_BASE = "base";
|
||||
const TYPE_AUTH = "auth";
|
||||
const TYPE_VIEW = "view";
|
||||
|
||||
const collectionTypes = {};
|
||||
collectionTypes[TYPE_BASE] = "Base";
|
||||
collectionTypes[TYPE_VIEW] = "View";
|
||||
collectionTypes[TYPE_AUTH] = "Auth";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
@ -37,14 +40,16 @@
|
||||
let collection = new Collection();
|
||||
let isSaving = false;
|
||||
let confirmClose = false; // prevent close recursion
|
||||
let activeTab = TAB_FIELDS;
|
||||
let activeTab = TAB_SCHEMA;
|
||||
let initialFormHash = calculateFormHash(collection);
|
||||
let schemaTabError = "";
|
||||
|
||||
$: schemaTabError =
|
||||
$: if ($errors.schema || $errors.options?.query) {
|
||||
// extract the direct schema field error, otherwise - return a generic message
|
||||
typeof CommonHelper.getNestedVal($errors, "schema.message", null) === "string"
|
||||
? CommonHelper.getNestedVal($errors, "schema.message")
|
||||
: "Has errors";
|
||||
schemaTabError = CommonHelper.getNestedVal($errors, "schema.message") || "Has errors";
|
||||
} else {
|
||||
schemaTabError = "";
|
||||
}
|
||||
|
||||
$: isSystemUpdate = !collection.isNew && collection.system;
|
||||
|
||||
@ -54,7 +59,7 @@
|
||||
|
||||
$: if (activeTab === TAB_OPTIONS && collection.type !== TYPE_AUTH) {
|
||||
// reset selected tab
|
||||
changeTab(TAB_FIELDS);
|
||||
changeTab(TAB_SCHEMA);
|
||||
}
|
||||
|
||||
export function changeTab(newTab) {
|
||||
@ -66,7 +71,7 @@
|
||||
|
||||
confirmClose = true;
|
||||
|
||||
changeTab(TAB_FIELDS);
|
||||
changeTab(TAB_SCHEMA);
|
||||
|
||||
return collectionPanel?.show();
|
||||
}
|
||||
@ -301,7 +306,7 @@
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm p-r-10 p-l-10 {collection.isNew
|
||||
? 'btn-secondary'
|
||||
? 'btn-outline'
|
||||
: 'btn-transparent'}"
|
||||
disabled={!collection.isNew}
|
||||
>
|
||||
@ -339,11 +344,11 @@
|
||||
<button
|
||||
type="button"
|
||||
class="tab-item"
|
||||
class:active={activeTab === TAB_FIELDS}
|
||||
on:click={() => changeTab(TAB_FIELDS)}
|
||||
class:active={activeTab === TAB_SCHEMA}
|
||||
on:click={() => changeTab(TAB_SCHEMA)}
|
||||
>
|
||||
<span class="txt">Fields</span>
|
||||
{#if !CommonHelper.isEmpty($errors?.schema)}
|
||||
<span class="txt">{collection?.isView ? "Query" : "Fields"}</span>
|
||||
{#if !CommonHelper.isEmpty(schemaTabError)}
|
||||
<i
|
||||
class="ri-error-warning-fill txt-danger"
|
||||
transition:scale|local={{ duration: 150, start: 0.7 }}
|
||||
@ -390,8 +395,12 @@
|
||||
|
||||
<div class="tabs-content">
|
||||
<!-- avoid rerendering the fields tab -->
|
||||
<div class="tab-item" class:active={activeTab === TAB_FIELDS}>
|
||||
<CollectionFieldsTab bind:collection />
|
||||
<div class="tab-item" class:active={activeTab === TAB_SCHEMA}>
|
||||
{#if collection.isView}
|
||||
<CollectionQueryTab bind:collection />
|
||||
{:else}
|
||||
<CollectionFieldsTab bind:collection />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if activeTab === TAB_RULES}
|
||||
@ -431,7 +440,7 @@
|
||||
align-items: center;
|
||||
min-height: var(--smBtnHeight);
|
||||
}
|
||||
.tabs-content {
|
||||
z-index: 3; /* autocomplete dropdown overlay fix */
|
||||
.tabs-content:focus-within {
|
||||
z-index: 9; /* autocomplete dropdown overlay fix */
|
||||
}
|
||||
</style>
|
||||
|
@ -8,21 +8,21 @@
|
||||
let collectionPanel;
|
||||
let searchTerm = "";
|
||||
|
||||
$: if ($collections) {
|
||||
scrollIntoView();
|
||||
}
|
||||
|
||||
$: normalizedSearch = searchTerm.replace(/\s+/g, "").toLowerCase();
|
||||
|
||||
$: hasSearch = searchTerm !== "";
|
||||
|
||||
$: filteredCollections = $collections.filter((collection) => {
|
||||
$: filtered = $collections.filter((collection) => {
|
||||
return (
|
||||
collection.id == searchTerm ||
|
||||
collection.name.replace(/\s+/g, "").toLowerCase().includes(normalizedSearch)
|
||||
);
|
||||
});
|
||||
|
||||
$: if ($collections) {
|
||||
scrollIntoView();
|
||||
}
|
||||
|
||||
function selectCollection(collection) {
|
||||
$activeCollection = collection;
|
||||
}
|
||||
@ -59,9 +59,9 @@
|
||||
<div
|
||||
class="sidebar-content"
|
||||
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
|
||||
href="/collections?collectionId={collection.id}"
|
||||
class="sidebar-list-item"
|
||||
@ -69,7 +69,6 @@
|
||||
use:link
|
||||
>
|
||||
<i class={CommonHelper.getCollectionTypeIcon(collection.type)} />
|
||||
|
||||
<span class="txt">{collection.name}</span>
|
||||
</a>
|
||||
{:else}
|
||||
|
@ -206,7 +206,7 @@
|
||||
<div class="grid">
|
||||
<div class="col-sm-6">
|
||||
<Field
|
||||
class="form-field required {field.id ? 'disabled' : ''}"
|
||||
class="form-field required {field.id ? 'readonly' : ''}"
|
||||
name="schema.{key}.type"
|
||||
let:uniqueId
|
||||
>
|
||||
|
@ -114,9 +114,9 @@
|
||||
right: 0px;
|
||||
top: 0px;
|
||||
min-width: 135px;
|
||||
padding: 10px 10px;
|
||||
padding: 10px;
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
background: rgba(53, 71, 104, 0.1);
|
||||
background: rgba(53, 71, 104, 0.08);
|
||||
}
|
||||
</style>
|
||||
|
@ -24,7 +24,7 @@
|
||||
<h4>Request log</h4>
|
||||
</svelte:fragment>
|
||||
|
||||
<table class="table-compact table-border">
|
||||
<table class="table-border">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="min-width txt-hint txt-bold">ID</td>
|
||||
|
@ -1,5 +1,6 @@
|
||||
<script>
|
||||
import { replace, querystring } from "svelte-spa-router";
|
||||
import CommonHelper from "@/utils/CommonHelper";
|
||||
import {
|
||||
collections,
|
||||
activeCollection,
|
||||
@ -16,16 +17,18 @@
|
||||
import CollectionUpsertPanel from "@/components/collections/CollectionUpsertPanel.svelte";
|
||||
import CollectionDocsPanel from "@/components/collections/CollectionDocsPanel.svelte";
|
||||
import RecordUpsertPanel from "@/components/records/RecordUpsertPanel.svelte";
|
||||
import RecordPreviewPanel from "@/components/records/RecordPreviewPanel.svelte";
|
||||
import RecordsList from "@/components/records/RecordsList.svelte";
|
||||
|
||||
const queryParams = new URLSearchParams($querystring);
|
||||
|
||||
let collectionUpsertPanel;
|
||||
let collectionDocsPanel;
|
||||
let recordPanel;
|
||||
let recordUpsertPanel;
|
||||
let recordPreviewPanel;
|
||||
let recordsList;
|
||||
let filter = queryParams.get("filter") || "";
|
||||
let sort = queryParams.get("sort") || "-created";
|
||||
let sort = queryParams.get("sort") || "";
|
||||
let selectedCollectionId = queryParams.get("collectionId") || $activeCollection?.id;
|
||||
|
||||
$: reactiveParams = new URLSearchParams($querystring);
|
||||
@ -56,9 +59,17 @@
|
||||
$: $pageTitle = $activeCollection?.name || "Collections";
|
||||
|
||||
function reset() {
|
||||
selectedCollectionId = $activeCollection.id;
|
||||
sort = "-created";
|
||||
selectedCollectionId = $activeCollection?.id;
|
||||
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);
|
||||
@ -128,10 +139,12 @@
|
||||
<span class="txt">API Preview</span>
|
||||
</button>
|
||||
|
||||
<button type="button" class="btn btn-expanded" on:click={() => recordPanel?.show()}>
|
||||
<i class="ri-add-line" />
|
||||
<span class="txt">New record</span>
|
||||
</button>
|
||||
{#if !$activeCollection.isView}
|
||||
<button type="button" class="btn btn-expanded" on:click={() => recordUpsertPanel?.show()}>
|
||||
<i class="ri-add-line" />
|
||||
<span class="txt">New record</span>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@ -147,8 +160,12 @@
|
||||
collection={$activeCollection}
|
||||
bind:filter
|
||||
bind:sort
|
||||
on:select={(e) => recordPanel?.show(e?.detail)}
|
||||
on:new={() => recordPanel?.show()}
|
||||
on:select={(e) => {
|
||||
$activeCollection.isView
|
||||
? recordPreviewPanel.show(e?.detail)
|
||||
: recordUpsertPanel?.show(e?.detail);
|
||||
}}
|
||||
on:new={() => recordUpsertPanel?.show()}
|
||||
/>
|
||||
</PageWrapper>
|
||||
{/if}
|
||||
@ -158,8 +175,10 @@
|
||||
<CollectionDocsPanel bind:this={collectionDocsPanel} />
|
||||
|
||||
<RecordUpsertPanel
|
||||
bind:this={recordPanel}
|
||||
bind:this={recordUpsertPanel}
|
||||
collection={$activeCollection}
|
||||
on:save={() => recordsList?.reloadLoadedPages()}
|
||||
on:delete={() => recordsList?.reloadLoadedPages()}
|
||||
/>
|
||||
|
||||
<RecordPreviewPanel bind:this={recordPreviewPanel} collection={$activeCollection} />
|
||||
|
@ -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>
|
107
ui/src/components/records/RecordFieldValue.svelte
Normal file
107
ui/src/components/records/RecordFieldValue.svelte
Normal 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}
|
@ -1,11 +1,23 @@
|
||||
<script>
|
||||
import CommonHelper from "@/utils/CommonHelper";
|
||||
import tooltip from "@/actions/tooltip";
|
||||
import { collections } from "@/stores/collections";
|
||||
import RecordFileThumb from "@/components/records/RecordFileThumb.svelte";
|
||||
|
||||
export let record;
|
||||
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>
|
||||
|
||||
<div class="record-info">
|
||||
@ -21,7 +33,16 @@
|
||||
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>
|
||||
|
||||
<style lang="scss">
|
||||
@ -36,5 +57,8 @@
|
||||
> * {
|
||||
line-height: inherit;
|
||||
}
|
||||
:global(.thumb) {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
78
ui/src/components/records/RecordPreviewPanel.svelte
Normal file
78
ui/src/components/records/RecordPreviewPanel.svelte
Normal 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>
|
@ -355,7 +355,7 @@
|
||||
on:submit|preventDefault={save}
|
||||
>
|
||||
{#if !record.isNew}
|
||||
<Field class="form-field disabled" name="id" let:uniqueId>
|
||||
<Field class="form-field readonly" name="id" let:uniqueId>
|
||||
<label for={uniqueId}>
|
||||
<i class={CommonHelper.getFieldTypeIcon("primary")} />
|
||||
<span class="txt">id</span>
|
||||
@ -426,6 +426,7 @@
|
||||
<button type="button" class="btn btn-transparent" disabled={isSaving} on:click={() => hide()}>
|
||||
<span class="txt">Cancel</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
form={formId}
|
||||
|
@ -12,7 +12,7 @@
|
||||
import CopyIcon from "@/components/base/CopyIcon.svelte";
|
||||
import FormattedDate from "@/components/base/FormattedDate.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 sortRegex = /^([\+\-])?(\w+)$/;
|
||||
@ -57,6 +57,10 @@
|
||||
updateStoredHiddenColumns();
|
||||
}
|
||||
|
||||
$: hasCreated = !collection?.isView || (records.length > 0 && records[0].created != "");
|
||||
|
||||
$: hasUpdated = !collection?.isView || (records.length > 0 && records[0].updated != "");
|
||||
|
||||
$: collumnsToHide = [].concat(
|
||||
collection.isAuth
|
||||
? [
|
||||
@ -67,10 +71,8 @@
|
||||
fields.map((f) => {
|
||||
return { id: f.id, name: f.name };
|
||||
}),
|
||||
[
|
||||
{ id: "@created", name: "created" },
|
||||
{ id: "@updated", name: "updated" },
|
||||
]
|
||||
hasCreated ? { id: "@created", name: "created" } : [],
|
||||
hasUpdated ? { id: "@updated", name: "updated" } : []
|
||||
);
|
||||
|
||||
function updateStoredHiddenColumns() {
|
||||
@ -242,48 +244,55 @@
|
||||
|
||||
<HorizontalScroller class="table-wrapper">
|
||||
<svelte:fragment slot="before">
|
||||
<Toggler class="dropdown dropdown-right dropdown-nowrap columns-dropdown" trigger={columnsTrigger}>
|
||||
<div class="txt-hint txt-sm p-5 m-b-5">Toggle columns</div>
|
||||
{#each collumnsToHide as column (column.id + column.name)}
|
||||
<Field class="form-field form-field-sm form-field-toggle m-0 p-5" let:uniqueId>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={uniqueId}
|
||||
checked={!hiddenColumns.includes(column.id)}
|
||||
on:change={(e) => {
|
||||
if (e.target.checked) {
|
||||
CommonHelper.removeByValue(hiddenColumns, column.id);
|
||||
} else {
|
||||
CommonHelper.pushUnique(hiddenColumns, column.id);
|
||||
}
|
||||
hiddenColumns = hiddenColumns;
|
||||
}}
|
||||
/>
|
||||
<label for={uniqueId}>{column.name}</label>
|
||||
</Field>
|
||||
{/each}
|
||||
</Toggler>
|
||||
{#if columnsTrigger}
|
||||
<Toggler
|
||||
class="dropdown dropdown-right dropdown-nowrap columns-dropdown"
|
||||
trigger={columnsTrigger}
|
||||
>
|
||||
<div class="txt-hint txt-sm p-5 m-b-5">Toggle columns</div>
|
||||
{#each collumnsToHide as column (column.id + column.name)}
|
||||
<Field class="form-field form-field-sm form-field-toggle m-0 p-5" let:uniqueId>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={uniqueId}
|
||||
checked={!hiddenColumns.includes(column.id)}
|
||||
on:change={(e) => {
|
||||
if (e.target.checked) {
|
||||
CommonHelper.removeByValue(hiddenColumns, column.id);
|
||||
} else {
|
||||
CommonHelper.pushUnique(hiddenColumns, column.id);
|
||||
}
|
||||
hiddenColumns = hiddenColumns;
|
||||
}}
|
||||
/>
|
||||
<label for={uniqueId}>{column.name}</label>
|
||||
</Field>
|
||||
{/each}
|
||||
</Toggler>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
|
||||
<table class="table" class:table-loading={isLoading}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="bulk-select-col min-width">
|
||||
{#if isLoading}
|
||||
<span class="loader loader-sm" />
|
||||
{:else}
|
||||
<div class="form-field">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="checkbox_0"
|
||||
disabled={!records.length}
|
||||
checked={areAllRecordsSelected}
|
||||
on:change={() => toggleSelectAllRecords()}
|
||||
/>
|
||||
<label for="checkbox_0" />
|
||||
</div>
|
||||
{/if}
|
||||
</th>
|
||||
{#if !collection.isView}
|
||||
<th class="bulk-select-col min-width">
|
||||
{#if isLoading}
|
||||
<span class="loader loader-sm" />
|
||||
{:else}
|
||||
<div class="form-field">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="checkbox_0"
|
||||
disabled={!records.length}
|
||||
checked={areAllRecordsSelected}
|
||||
on:change={() => toggleSelectAllRecords()}
|
||||
/>
|
||||
<label for="checkbox_0" />
|
||||
</div>
|
||||
{/if}
|
||||
</th>
|
||||
{/if}
|
||||
|
||||
{#if !hiddenColumns.includes("@id")}
|
||||
<SortHeader class="col-type-text col-field-id" name="id" bind:sort>
|
||||
@ -326,7 +335,7 @@
|
||||
</SortHeader>
|
||||
{/each}
|
||||
|
||||
{#if !hiddenColumns.includes("@created")}
|
||||
{#if hasCreated && !hiddenColumns.includes("@created")}
|
||||
<SortHeader class="col-type-date col-field-created" name="created" bind:sort>
|
||||
<div class="col-header-content">
|
||||
<i class={CommonHelper.getFieldTypeIcon("date")} />
|
||||
@ -335,7 +344,7 @@
|
||||
</SortHeader>
|
||||
{/if}
|
||||
|
||||
{#if !hiddenColumns.includes("@updated")}
|
||||
{#if hasUpdated && !hiddenColumns.includes("@updated")}
|
||||
<SortHeader class="col-type-date col-field-updated" name="updated" bind:sort>
|
||||
<div class="col-header-content">
|
||||
<i class={CommonHelper.getFieldTypeIcon("date")} />
|
||||
@ -345,19 +354,21 @@
|
||||
{/if}
|
||||
|
||||
<th class="col-type-action min-width">
|
||||
<button
|
||||
bind:this={columnsTrigger}
|
||||
type="button"
|
||||
aria-label="Toggle columns"
|
||||
class="btn btn-sm btn-transparent p-0"
|
||||
>
|
||||
<i class="ri-more-line" />
|
||||
</button>
|
||||
{#if collumnsToHide.length}
|
||||
<button
|
||||
bind:this={columnsTrigger}
|
||||
type="button"
|
||||
aria-label="Toggle columns"
|
||||
class="btn btn-sm btn-transparent p-0"
|
||||
>
|
||||
<i class="ri-more-line" />
|
||||
</button>
|
||||
{/if}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each records as record (record.id)}
|
||||
{#each records as record (!collection.isView ? record.id : record)}
|
||||
<tr
|
||||
tabindex="0"
|
||||
class="row-handle"
|
||||
@ -369,18 +380,20 @@
|
||||
}
|
||||
}}
|
||||
>
|
||||
<td class="bulk-select-col min-width">
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div class="form-field" on:click|stopPropagation>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="checkbox_{record.id}"
|
||||
checked={bulkSelected[record.id]}
|
||||
on:change={() => toggleSelectRecord(record)}
|
||||
/>
|
||||
<label for="checkbox_{record.id}" />
|
||||
</div>
|
||||
</td>
|
||||
{#if !collection.isView}
|
||||
<td class="bulk-select-col min-width">
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div class="form-field" on:click|stopPropagation>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="checkbox_{record.id}"
|
||||
checked={bulkSelected[record.id]}
|
||||
on:change={() => toggleSelectRecord(record)}
|
||||
/>
|
||||
<label for="checkbox_{record.id}" />
|
||||
</div>
|
||||
</td>
|
||||
{/if}
|
||||
|
||||
{#if !hiddenColumns.includes("@id")}
|
||||
<td class="col-type-text col-field-id">
|
||||
@ -433,16 +446,18 @@
|
||||
{/if}
|
||||
|
||||
{#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}
|
||||
|
||||
{#if !hiddenColumns.includes("@created")}
|
||||
{#if hasCreated && !hiddenColumns.includes("@created")}
|
||||
<td class="col-type-date col-field-created">
|
||||
<FormattedDate date={record.created} />
|
||||
</td>
|
||||
{/if}
|
||||
|
||||
{#if !hiddenColumns.includes("@updated")}
|
||||
{#if hasUpdated && !hiddenColumns.includes("@updated")}
|
||||
<td class="col-type-date col-field-updated">
|
||||
<FormattedDate date={record.updated} />
|
||||
</td>
|
||||
|
@ -198,7 +198,7 @@
|
||||
</div>
|
||||
<div class="col-lg-3">
|
||||
<Field class="form-field required" name="smtp.tls" let:uniqueId>
|
||||
<label for={uniqueId}>TLS Encryption</label>
|
||||
<label for={uniqueId}>TLS encryption</label>
|
||||
<ObjectSelect
|
||||
id={uniqueId}
|
||||
items={tlsOptions}
|
||||
@ -208,7 +208,7 @@
|
||||
</div>
|
||||
<div class="col-lg-3">
|
||||
<Field class="form-field" name="smtp.authMethod" let:uniqueId>
|
||||
<label for={uniqueId}>AUTH Method</label>
|
||||
<label for={uniqueId}>AUTH method</label>
|
||||
<ObjectSelect
|
||||
id={uniqueId}
|
||||
items={authMethods}
|
||||
|
@ -123,7 +123,7 @@ code {
|
||||
display: inline-block;
|
||||
font-family: var(--monospaceFontFamily);
|
||||
font-style: normal;
|
||||
font-size: var(--lgFontSize);
|
||||
font-size: 1em;
|
||||
line-height: 1.379rem;
|
||||
padding: 0px 4px;
|
||||
white-space: nowrap;
|
||||
@ -522,6 +522,10 @@ a,
|
||||
border-radius: inherit;
|
||||
overflow: hidden;
|
||||
}
|
||||
&.thumb-xs {
|
||||
--thumbSize: 24px;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
&.thumb-sm {
|
||||
--thumbSize: 32px;
|
||||
font-size: 0.92rem;
|
||||
|
@ -1,10 +1,10 @@
|
||||
/* remixicon */
|
||||
@font-face {
|
||||
font-family: 'remixicon';
|
||||
src: url('../fonts/remixicon/remixicon.woff2?v=1') format('woff2'),
|
||||
url('../fonts/remixicon/remixicon.woff?v=1') format('woff'),
|
||||
url('../fonts/remixicon/remixicon.ttf?v=1') format('truetype'),
|
||||
url('../fonts/remixicon/remixicon.svg?v=1#remixicon') format('svg'); /* iOS 4.1- */
|
||||
src: url('/fonts/remixicon/remixicon.woff2?v=1') format('woff2'),
|
||||
url('/fonts/remixicon/remixicon.woff?v=1') format('woff'),
|
||||
url('/fonts/remixicon/remixicon.ttf?v=1') format('truetype'),
|
||||
url('/fonts/remixicon/remixicon.svg?v=1#remixicon') format('svg'); /* iOS 4.1- */
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@ -14,8 +14,8 @@
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
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.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.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+ */
|
||||
}
|
||||
|
||||
/* source-sans-pro-italic - latin_cyrillic */
|
||||
@ -24,8 +24,8 @@
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
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.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.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+ */
|
||||
}
|
||||
|
||||
/* source-sans-pro-600 - latin_cyrillic */
|
||||
@ -34,8 +34,8 @@
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
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.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.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+ */
|
||||
}
|
||||
|
||||
/* source-sans-pro-600italic - latin_cyrillic */
|
||||
@ -44,8 +44,8 @@
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
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.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.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+ */
|
||||
}
|
||||
|
||||
/* source-sans-pro-700 - latin_cyrillic */
|
||||
@ -54,8 +54,8 @@
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
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.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.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+ */
|
||||
}
|
||||
|
||||
/* source-sans-pro-700italic - latin_cyrillic */
|
||||
@ -64,8 +64,8 @@
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
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.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.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+ */
|
||||
}
|
||||
|
||||
/* jetbrains-mono-regular - latin */
|
||||
@ -74,8 +74,8 @@
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
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.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||
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+ */
|
||||
}
|
||||
|
||||
/* jetbrains-mono-600 - latin */
|
||||
@ -84,6 +84,6 @@
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
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.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||
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+ */
|
||||
}
|
||||
|
@ -454,11 +454,10 @@ select {
|
||||
user-select: none;
|
||||
font-weight: 600;
|
||||
color: var(--txtHintColor);
|
||||
font-size: var(--xsFontSize);
|
||||
text-transform: uppercase;
|
||||
font-size: var(--smFontSize);
|
||||
line-height: 1;
|
||||
padding-top: 12px;
|
||||
padding-bottom: 2px;
|
||||
padding-bottom: 3px;
|
||||
padding-left: var(--hPadding);
|
||||
padding-right: var(--hPadding);
|
||||
border: 0;
|
||||
@ -474,8 +473,11 @@ select {
|
||||
i {
|
||||
font-size: 0.96rem;
|
||||
line-height: 1;
|
||||
margin-top: -2px;
|
||||
margin-bottom: -2px;
|
||||
margin-top: -1px;
|
||||
margin-bottom: -1px;
|
||||
&:before {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
%input, label {
|
||||
@ -550,14 +552,23 @@ select {
|
||||
margin-left: -2px;
|
||||
}
|
||||
}
|
||||
&.readonly,
|
||||
&.disabled {
|
||||
label, %input {
|
||||
background: var(--baseAlt1Color);
|
||||
}
|
||||
> label {
|
||||
color: var(--txtDisabledColor);
|
||||
color: var(--txtHintColor);
|
||||
}
|
||||
&.required > label:after {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
&.disabled {
|
||||
> label {
|
||||
color: var(--txtDisabledColor);
|
||||
}
|
||||
}
|
||||
|
||||
// checkbox/radio
|
||||
input[type="radio"],
|
||||
@ -1084,13 +1095,15 @@ select {
|
||||
// codemirror field
|
||||
.code-editor {
|
||||
@extend %input;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
.form-field label ~ & {
|
||||
padding-bottom: 6px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.cm-editor {
|
||||
flex-grow: 1;
|
||||
border: 0 !important;
|
||||
outline: none !important;
|
||||
.cm-line {
|
||||
@ -1122,6 +1135,7 @@ select {
|
||||
}
|
||||
}
|
||||
.cm-scroller {
|
||||
flex-grow: 1;
|
||||
outline: 0 !important;
|
||||
font-family: var(--monospaceFontFamily);
|
||||
font-size: var(--baseFontSize);
|
||||
@ -1145,6 +1159,9 @@ select {
|
||||
background-color: rgba(50, 140, 130, 0.1);
|
||||
}
|
||||
}
|
||||
.ͼf {
|
||||
color: var(--dangerColor);
|
||||
}
|
||||
}
|
||||
|
||||
// tinymce field
|
||||
|
@ -17,7 +17,7 @@ table {
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
padding: 5px 10px;
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid var(--baseAlt2Color);
|
||||
&:first-child {
|
||||
padding-left: 20px;
|
||||
@ -191,16 +191,15 @@ table {
|
||||
}
|
||||
|
||||
// styles
|
||||
&.table-compact {
|
||||
td, th {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
&.table-border {
|
||||
border: 1px solid var(--baseAlt2Color);
|
||||
border-radius: var(--baseRadius);
|
||||
tr {
|
||||
background: var(--baseColor);
|
||||
}
|
||||
td, th {
|
||||
height: 45px;
|
||||
}
|
||||
th {
|
||||
background: var(--baseAlt1Color);
|
||||
}
|
||||
@ -209,6 +208,29 @@ table {
|
||||
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
|
||||
|
@ -18,13 +18,13 @@
|
||||
--baseAlt4Color: #a5b0c0;
|
||||
|
||||
--infoColor: #3da9fc;
|
||||
--infoAltColor: #d8eefe;
|
||||
--successColor: #2cb67d;
|
||||
--successAltColor: #d6f5e8;
|
||||
--infoAltColor: #d2ecfe;
|
||||
--successColor: #2aac76;
|
||||
--successAltColor: #d2f4e6;
|
||||
--dangerColor: #e13756;
|
||||
--dangerAltColor: #fcdee4;
|
||||
--warningColor: #ff8e3c;
|
||||
--warningAltColor: #ffe7d6;
|
||||
--warningAltColor: #ffeadb;
|
||||
|
||||
--overlayColor: rgba(53, 71, 104, 0.25);
|
||||
--tooltipColor: rgba(0, 0, 0, 0.85);
|
||||
|
@ -475,11 +475,12 @@ export default class CommonHelper {
|
||||
/**
|
||||
* Truncates the provided text to the specified max characters length.
|
||||
*
|
||||
* @param {String} str
|
||||
* @param {Number} length
|
||||
* @param {String} str
|
||||
* @param {Number} [length]
|
||||
* @param {Boolean} [dots]
|
||||
* @return {String}
|
||||
*/
|
||||
static truncate(str, length = 150, dots = false) {
|
||||
static truncate(str, length = 150, dots = true) {
|
||||
str = str || "";
|
||||
|
||||
if (str.length <= length) {
|
||||
@ -979,8 +980,8 @@ export default class CommonHelper {
|
||||
switch (type?.toLowerCase()) {
|
||||
case "auth":
|
||||
return "ri-group-line";
|
||||
case "single":
|
||||
return "ri-file-list-2-line";
|
||||
case "view":
|
||||
return "ri-terminal-box-line";
|
||||
default:
|
||||
return "ri-folder-2-line";
|
||||
}
|
||||
@ -1157,7 +1158,7 @@ export default class CommonHelper {
|
||||
for (const collection of collections) {
|
||||
if (collection.type === 'auth') {
|
||||
authCollections.push(collection);
|
||||
} else if (collection.type === 'single') {
|
||||
} else if (collection.type === 'base') {
|
||||
singleCollections.push(collection);
|
||||
} else {
|
||||
baseCollections.push(collection);
|
||||
@ -1318,4 +1319,76 @@ export default class CommonHelper {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user