1
0
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:
Gani Georgiev 2023-02-18 19:33:42 +02:00
parent 0052e2ab2a
commit a07f67002f
98 changed files with 3259 additions and 829 deletions

View File

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

View File

@ -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,

View File

@ -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

View File

@ -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)

View File

@ -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 {

View File

@ -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,

View File

@ -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,

View File

@ -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)
})
}

View File

@ -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 {

View File

@ -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(

View File

@ -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
}

View File

@ -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
View File

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

539
daos/view_test.go Normal file
View File

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

View File

@ -69,7 +69,7 @@ func NewCollectionUpsert(app core.App, collection *models.Collection) *Collectio
}
clone, _ := form.collection.Schema.Clone()
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

View File

@ -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
}
}
}
}

View File

@ -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,
},
},
}

View File

@ -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),
)
}

View File

@ -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)
}
}
}

View File

@ -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

View File

@ -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:

View File

@ -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
View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

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

View File

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

View File

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

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

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

View File

@ -34,8 +34,9 @@ func NewFromBytes(b []byte) *Tokenizer {
// New creates new Tokenizer from the provided reader with DefaultSeparators.
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
}

View File

@ -34,6 +34,10 @@ func TestFactories(t *testing.T) {
t.Fatalf("[%s] Expected reader with content %q, got %q", s.name, expectedContent, content)
}
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()

View File

@ -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"

View File

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

View File

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

View File

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

View File

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

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
import{S as z,i as G,s as I,F as J,c as S,m as L,t as v,a as y,d as R,C as M,E as N,g as _,k as W,n as Y,o as b,G as j,H as A,p as B,q as D,e as m,w as C,b as h,f as d,r as H,h as k,u as P,v as K,y as E,x as O,z as T}from"./index-3e0f12d8.js";function Q(r){let e,t,l,s,n,o,c,a,i,u,g,$,p=r[3]&&F(r);return o=new D({props:{class:"form-field required",name:"password",$$slots:{default:[V,({uniqueId:f})=>({8:f}),({uniqueId:f})=>f?256:0]},$$scope:{ctx:r}}}),{c(){e=m("form"),t=m("div"),l=m("h5"),s=C(`Type your password to confirm changing your email address
import{S as z,i as G,s as I,F as J,c as S,m as L,t as v,a as y,d as R,C as M,E as N,g as _,k as W,n as Y,o as b,G as j,H as A,p as B,q as D,e as m,w as C,b as h,f as d,r as H,h as k,u as P,v as K,y as E,x as O,z as T}from"./index-ffbb9561.js";function Q(r){let e,t,l,s,n,o,c,a,i,u,g,$,p=r[3]&&F(r);return o=new D({props:{class:"form-field required",name:"password",$$slots:{default:[V,({uniqueId:f})=>({8:f}),({uniqueId:f})=>f?256:0]},$$scope:{ctx:r}}}),{c(){e=m("form"),t=m("div"),l=m("h5"),s=C(`Type your password to confirm changing your email address
`),p&&p.c(),n=h(),S(o.$$.fragment),c=h(),a=m("button"),i=m("span"),i.textContent="Confirm new email",d(t,"class","content txt-center m-b-base"),d(i,"class","txt"),d(a,"type","submit"),d(a,"class","btn btn-lg btn-block"),a.disabled=r[1],H(a,"btn-loading",r[1])},m(f,w){_(f,e,w),k(e,t),k(t,l),k(l,s),p&&p.m(l,null),k(e,n),L(o,e,null),k(e,c),k(e,a),k(a,i),u=!0,g||($=P(e,"submit",K(r[4])),g=!0)},p(f,w){f[3]?p?p.p(f,w):(p=F(f),p.c(),p.m(l,null)):p&&(p.d(1),p=null);const q={};w&769&&(q.$$scope={dirty:w,ctx:f}),o.$set(q),(!u||w&2)&&(a.disabled=f[1]),(!u||w&2)&&H(a,"btn-loading",f[1])},i(f){u||(v(o.$$.fragment,f),u=!0)},o(f){y(o.$$.fragment,f),u=!1},d(f){f&&b(e),p&&p.d(),R(o),g=!1,$()}}}function U(r){let e,t,l,s,n;return{c(){e=m("div"),e.innerHTML=`<div class="icon"><i class="ri-checkbox-circle-line"></i></div>
<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};

View File

@ -1,4 +1,4 @@
import{S as J,i as M,s as W,F as Y,c as F,m as N,t as y,a as q,d as T,C as j,E as A,g as _,k as B,n as D,o as m,G as K,H as O,p as Q,q as E,e as b,w as R,b as P,f as p,r as G,h as w,u as H,v as U,y as S,x as V,z as h}from"./index-3e0f12d8.js";function X(r){let e,l,s,n,t,o,c,a,i,u,v,k,g,C,d=r[4]&&I(r);return o=new E({props:{class:"form-field required",name:"password",$$slots:{default:[x,({uniqueId:f})=>({10:f}),({uniqueId:f})=>f?1024:0]},$$scope:{ctx:r}}}),a=new E({props:{class:"form-field required",name:"passwordConfirm",$$slots:{default:[ee,({uniqueId:f})=>({10:f}),({uniqueId:f})=>f?1024:0]},$$scope:{ctx:r}}}),{c(){e=b("form"),l=b("div"),s=b("h5"),n=R(`Reset your user password
import{S as J,i as M,s as W,F as Y,c as F,m as N,t as y,a as q,d as T,C as j,E as A,g as _,k as B,n as D,o as m,G as K,H as O,p as Q,q as E,e as b,w as R,b as P,f as p,r as G,h as w,u as H,v as U,y as S,x as V,z as h}from"./index-ffbb9561.js";function X(r){let e,l,s,n,t,o,c,a,i,u,v,k,g,C,d=r[4]&&I(r);return o=new E({props:{class:"form-field required",name:"password",$$slots:{default:[x,({uniqueId:f})=>({10:f}),({uniqueId:f})=>f?1024:0]},$$scope:{ctx:r}}}),a=new E({props:{class:"form-field required",name:"passwordConfirm",$$slots:{default:[ee,({uniqueId:f})=>({10:f}),({uniqueId:f})=>f?1024:0]},$$scope:{ctx:r}}}),{c(){e=b("form"),l=b("div"),s=b("h5"),n=R(`Reset your user password
`),d&&d.c(),t=P(),F(o.$$.fragment),c=P(),F(a.$$.fragment),i=P(),u=b("button"),v=b("span"),v.textContent="Set new password",p(l,"class","content txt-center m-b-base"),p(v,"class","txt"),p(u,"type","submit"),p(u,"class","btn btn-lg btn-block"),u.disabled=r[2],G(u,"btn-loading",r[2])},m(f,$){_(f,e,$),w(e,l),w(l,s),w(s,n),d&&d.m(s,null),w(e,t),N(o,e,null),w(e,c),N(a,e,null),w(e,i),w(e,u),w(u,v),k=!0,g||(C=H(e,"submit",U(r[5])),g=!0)},p(f,$){f[4]?d?d.p(f,$):(d=I(f),d.c(),d.m(s,null)):d&&(d.d(1),d=null);const L={};$&3073&&(L.$$scope={dirty:$,ctx:f}),o.$set(L);const z={};$&3074&&(z.$$scope={dirty:$,ctx:f}),a.$set(z),(!k||$&4)&&(u.disabled=f[2]),(!k||$&4)&&G(u,"btn-loading",f[2])},i(f){k||(y(o.$$.fragment,f),y(a.$$.fragment,f),k=!0)},o(f){q(o.$$.fragment,f),q(a.$$.fragment,f),k=!1},d(f){f&&m(e),d&&d.d(),T(o),T(a),g=!1,C()}}}function Z(r){let e,l,s,n,t;return{c(){e=b("div"),e.innerHTML=`<div class="icon"><i class="ri-checkbox-circle-line"></i></div>
<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};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

26
ui/dist/index.html vendored
View File

@ -17,15 +17,35 @@
<link rel="mask-icon" href="./images/favicon/safari-pinned-tab.svg" color="#000000">
<link rel="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>

View File

@ -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
View File

@ -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": {

View File

@ -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",

View File

@ -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}

View File

@ -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"}
/>

View File

@ -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);

View File

@ -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);
}

View File

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

View File

@ -1,10 +1,13 @@
<script>
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" />

View File

@ -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 -->

View File

@ -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>

View File

@ -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}

View File

@ -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
>

View File

@ -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>

View File

@ -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>

View File

@ -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} />

View File

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

View File

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

View File

@ -1,11 +1,23 @@
<script>
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>

View File

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

View File

@ -355,7 +355,7 @@
on:submit|preventDefault={save}
>
{#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}

View File

@ -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>

View File

@ -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}

View File

@ -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;

View File

@ -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+ */
}

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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;
}
}