mirror of
https://github.com/pocketbase/pocketbase.git
synced 2026-09-20 13:30:50 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8213ccffb4 | ||
|
|
e16a9f8b21 | ||
|
|
0206f6135c | ||
|
|
e417a26a05 | ||
|
|
ac4c30ccbc | ||
|
|
fed5ab9a13 | ||
|
|
6d9ca9bf9d | ||
|
|
56f1d1dfdd | ||
|
|
1b3edbbf5c | ||
|
|
522f9e0ab0 | ||
|
|
3c9427667d | ||
|
|
fd5b9e5f75 | ||
|
|
d0b441bc24 | ||
|
|
229240b374 | ||
|
|
f694893d31 | ||
|
|
8cb486f24a |
@@ -28,7 +28,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version: '>=1.27.0'
|
||||
go-version: '>=1.27.1'
|
||||
|
||||
# This step usually is not needed because the /ui/dist is pregenerated locally
|
||||
# but its here to ensure that each release embeds the latest admin ui artifacts.
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
## v0.40.2
|
||||
|
||||
- Return an error when filter params fallback fails to json serialize and optimized params replacement to execute in a single pass.
|
||||
|
||||
- Fixed collection index parsing error for indexes with missing name.
|
||||
|
||||
- Minor UI autocomplete optimizations _(prefix match, autocomplete debounce, etc.)_.
|
||||
|
||||
- Fixed linter warnings and comment typos.
|
||||
|
||||
- Bumped goja and its related dependencies _(regex unescaped dash error fix and base64 optimizations)_.
|
||||
|
||||
- Bumped the min Go GitHub action version to 1.27.1 as it includes some [minor `database/sql` and `enconding/json/v2` bug fixes](https://github.com/golang/go/issues?q=milestone%3AGo1.27.1).
|
||||
|
||||
|
||||
## v0.40.1
|
||||
|
||||
- Fixes for some reported regressions related to the `encoding/json/v2` update:
|
||||
|
||||
@@ -2,6 +2,15 @@
|
||||
> For the most recent versions, please refer to [CHANGELOG.md](./CHANGELOG.md)
|
||||
---
|
||||
|
||||
## v0.22.54
|
||||
|
||||
- (_Backported from v0.40.2_) Bumped goja and its related dependencies _(regex unescaped dash error fix and base64 optimizations)_.
|
||||
|
||||
- (_Backported from v0.40.2_) Return an error when filter params fallback fails to json serialize and optimized params replacement to execute in a single pass.
|
||||
|
||||
- (_Backported from v0.40.2_) Bumped the min Go GitHub action version to 1.27.1 as it includes some [minor `database/sql` and `enconding/json/v2` bug fixes](https://github.com/golang/go/issues?q=milestone%3AGo1.27.1).
|
||||
|
||||
|
||||
## v0.22.53
|
||||
|
||||
- Workaround `encoding/json` infinite recursion when used with Go 1.27 ([#7808](https://github.com/pocketbase/pocketbase/issues/7808)).
|
||||
|
||||
@@ -5,7 +5,7 @@ go 1.27
|
||||
require (
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/domodwyer/mailyak/v3 v3.6.2
|
||||
github.com/dop251/goja v0.0.0-20260806115107-493f22071ef6
|
||||
github.com/dop251/goja v0.0.0-20260901132549-43234fa61381
|
||||
github.com/dop251/goja_nodejs v0.0.0-20260212111938-1f56ff5bcf14
|
||||
github.com/fatih/color v1.19.0
|
||||
github.com/fsnotify/fsnotify v1.10.1
|
||||
@@ -31,7 +31,7 @@ require (
|
||||
github.com/dop251/base64dec v0.0.0-20231022112746-c6c9f9a96217 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect
|
||||
github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 // indirect
|
||||
github.com/google/pprof v0.0.0-20260902005441-ca85771921e4 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.15 // indirect
|
||||
|
||||
@@ -15,8 +15,8 @@ github.com/domodwyer/mailyak/v3 v3.6.2 h1:x3tGMsyFhTCaxp6ycgR0FE/bu5QiNp+hetUuCO
|
||||
github.com/domodwyer/mailyak/v3 v3.6.2/go.mod h1:lOm/u9CyCVWHeaAmHIdF4RiKVxKUT/H5XX10lIKAL6c=
|
||||
github.com/dop251/base64dec v0.0.0-20231022112746-c6c9f9a96217 h1:16iT9CBDOniJwFGPI41MbUDfEk74hFaKTqudrX8kenY=
|
||||
github.com/dop251/base64dec v0.0.0-20231022112746-c6c9f9a96217/go.mod h1:eIb+f24U+eWQCIsj9D/ah+MD9UP+wdxuqzsdLD+mhGM=
|
||||
github.com/dop251/goja v0.0.0-20260806115107-493f22071ef6 h1:Oh2rRG1un7tLlC3/NJDzKppZ4CeZGkVFJCUOTRwLpfw=
|
||||
github.com/dop251/goja v0.0.0-20260806115107-493f22071ef6/go.mod h1:LiIEzozrcvNXorsG/3+ypGqdTUAqZryhzSsqi0oU/Qg=
|
||||
github.com/dop251/goja v0.0.0-20260901132549-43234fa61381 h1:vzmJ+R9qxuaksCAB+Kn6wtGgQ01cIfKzUGwWWcX5zXI=
|
||||
github.com/dop251/goja v0.0.0-20260901132549-43234fa61381/go.mod h1:u8yZRUavu+N4EnFFy6J5fVtjE7lEcZ2YyV2GcBXY9c8=
|
||||
github.com/dop251/goja_nodejs v0.0.0-20260212111938-1f56ff5bcf14 h1:3U8dTgyNBhEQ/GVw0jZW5q+93Zw2gAZPRWhJ9TwV3rM=
|
||||
github.com/dop251/goja_nodejs v0.0.0-20260212111938-1f56ff5bcf14/go.mod h1:Tb7Xxye4LX7cT3i8YLvmPMGCV92IOi4CDZvm/V8ylc0=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
@@ -42,8 +42,8 @@ github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArs
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 h1:LMLX+LgTNWpfvCBdFebv6EsYotImrt/Ppc5cXIriCSo=
|
||||
github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk=
|
||||
github.com/google/pprof v0.0.0-20260902005441-ca85771921e4 h1:/6mPXfWmhv8eKck12I0YNIcIjwHtxP3YRIMKiEgTjWg=
|
||||
github.com/google/pprof v0.0.0-20260902005441-ca85771921e4/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
|
||||
+3251
-3248
File diff suppressed because it is too large
Load Diff
@@ -148,11 +148,12 @@ func ParseIndex(createIndexExpr string) Index {
|
||||
nameTk.Separators('.')
|
||||
|
||||
nameParts, _ := nameTk.ScanAll()
|
||||
if len(nameParts) == 2 {
|
||||
switch len(nameParts) {
|
||||
case 1:
|
||||
result.IndexName = strings.Trim(nameParts[0], trimChars)
|
||||
case 2:
|
||||
result.SchemaName = strings.Trim(nameParts[0], trimChars)
|
||||
result.IndexName = strings.Trim(nameParts[1], trimChars)
|
||||
} else {
|
||||
result.IndexName = strings.Trim(nameParts[0], trimChars)
|
||||
}
|
||||
|
||||
// TableName
|
||||
|
||||
@@ -20,6 +20,16 @@ func TestParseIndex(t *testing.T) {
|
||||
`invalid`,
|
||||
dbutils.Index{},
|
||||
},
|
||||
// no names
|
||||
{
|
||||
`create index on ()`,
|
||||
dbutils.Index{},
|
||||
},
|
||||
// invalid index name
|
||||
{
|
||||
`create index a.b.c on ()`,
|
||||
dbutils.Index{},
|
||||
},
|
||||
// simple (multiple spaces between the table and columns list)
|
||||
{
|
||||
`create index indexname on tablename (col1)`,
|
||||
|
||||
@@ -31,7 +31,7 @@ func Pick(data any, rawFields string) (any, error) {
|
||||
// json value that we can further operate on.
|
||||
//
|
||||
// Note that invalid UTF8 characters are mangled for compatibility
|
||||
// with earlier versions and to prevent unnecessery causing an error.
|
||||
// with earlier versions and to prevent unnecessary causing an error.
|
||||
//
|
||||
// @todo research other approaches to avoid the double serialization
|
||||
// ---
|
||||
|
||||
@@ -187,7 +187,7 @@ const jsonFieldsParam = "fields"
|
||||
// it should result in a JSON response like: `{"a":1, "b": 2}`.
|
||||
//
|
||||
// Note that invalid UTF8 characters are mangled for compatibility
|
||||
// with earlier versions and to prevent unnecessery causing a response error.
|
||||
// with earlier versions and to prevent unnecessary causing a response error.
|
||||
func (e *Event) JSON(status int, data any) error {
|
||||
e.setResponseHeaderIfEmpty(headerContentType, "application/json")
|
||||
e.Response.WriteHeader(status)
|
||||
|
||||
+28
-16
@@ -56,27 +56,39 @@ func (f FilterData) BuildExprWithLimit(
|
||||
raw := string(f)
|
||||
|
||||
// replace the placeholder params in the raw string filter
|
||||
for _, p := range placeholderReplacements {
|
||||
for key, value := range p {
|
||||
var replacement string
|
||||
switch v := value.(type) {
|
||||
case nil:
|
||||
replacement = "null"
|
||||
case bool, float64, float32, int, int64, int32, int16, int8, uint, uint64, uint32, uint16, uint8:
|
||||
replacement = cast.ToString(v)
|
||||
default:
|
||||
replacement = cast.ToString(v)
|
||||
if len(placeholderReplacements) > 0 {
|
||||
replacements := make([]string, 0, len(placeholderReplacements[0])*2)
|
||||
|
||||
// try to json serialize as fallback
|
||||
if replacement == "" {
|
||||
raw, _ := json.Marshal(v, json.Deterministic(true))
|
||||
replacement = string(raw)
|
||||
for _, p := range placeholderReplacements {
|
||||
for key, value := range p {
|
||||
var replacement string
|
||||
|
||||
switch v := value.(type) {
|
||||
case nil:
|
||||
replacement = "null"
|
||||
case bool, float64, float32, int, int64, int32, int16, int8, uint, uint64, uint32, uint16, uint8:
|
||||
replacement = cast.ToString(v)
|
||||
default:
|
||||
casted, err := cast.ToStringE(v)
|
||||
|
||||
// try to json serialize as fallback
|
||||
if err != nil {
|
||||
fallback, err := json.Marshal(v, json.Deterministic(true))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to serialize param %q: %w", key, err)
|
||||
}
|
||||
casted = string(fallback)
|
||||
}
|
||||
|
||||
replacement = strconv.Quote(casted)
|
||||
}
|
||||
|
||||
replacement = strconv.Quote(replacement)
|
||||
replacements = append(replacements, "{:"+key+"}", replacement)
|
||||
}
|
||||
raw = strings.ReplaceAll(raw, "{:"+key+"}", replacement)
|
||||
}
|
||||
|
||||
replacer := strings.NewReplacer(replacements...)
|
||||
raw = replacer.Replace(raw)
|
||||
}
|
||||
|
||||
cacheKey := raw + "/" + strconv.Itoa(maxExpressions)
|
||||
|
||||
@@ -3,6 +3,7 @@ package search_test
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
@@ -242,13 +243,45 @@ func TestFilterDataBuildExprWithParams(t *testing.T) {
|
||||
t.Fatalf("Expected 1 query, got %d", len(calledQueries))
|
||||
}
|
||||
|
||||
expectedQuery := `SELECT * WHERE ([[test1]] = 1 OR [[test2]] = 0 OR [[test3a]] = 123.456 OR [[test3b]] = 123.456 OR ([[test4]] = '' OR [[test4]] IS NULL) OR [[test5]] = '""' OR [[test6]] = 'simple' OR [[test7]] = '''single_quotes''' OR [[test8]] = '"double_quotes"' OR [[test9]] = '''"quote_with_backslash\' OR [[test10]] = '2023-01-01 00:00:00 +0000 UTC' OR [[test11]] = '["a","''quote","\"quote"]' OR [[test12]] = '{"a":123,"b":"quote\""}' OR [[test13]] = 'a`
|
||||
expectedQuery := `SELECT * WHERE ([[test1]] = 1 OR [[test2]] = 0 OR [[test3a]] = 123.456 OR [[test3b]] = 123.456 OR ([[test4]] = '' OR [[test4]] IS NULL) OR ([[test5]] = '' OR [[test5]] IS NULL) OR [[test6]] = 'simple' OR [[test7]] = '''single_quotes''' OR [[test8]] = '"double_quotes"' OR [[test9]] = '''"quote_with_backslash\' OR [[test10]] = '2023-01-01 00:00:00 +0000 UTC' OR [[test11]] = '["a","''quote","\"quote"]' OR [[test12]] = '{"a":123,"b":"quote\""}' OR [[test13]] = 'a`
|
||||
expectedQuery += "\nb')"
|
||||
if expectedQuery != calledQueries[0] {
|
||||
t.Fatalf("Expected query \n%s, \ngot \n%s", expectedQuery, calledQueries[0])
|
||||
}
|
||||
}
|
||||
|
||||
type brokenJSON struct{}
|
||||
|
||||
func (j brokenJSON) MarshalJSON() ([]byte, error) {
|
||||
return nil, errors.New("test_error")
|
||||
}
|
||||
|
||||
func TestFilterDataBuildExprWithParamsFallbackError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
resolver := search.NewSimpleFieldResolver("test")
|
||||
|
||||
filter := search.FilterData(`test = {:test}`)
|
||||
|
||||
t.Run("non-string type but valid json", func(t *testing.T) {
|
||||
_, err := filter.BuildExpr(resolver, dbx.Params{
|
||||
"test": map[string]any{"a": "123"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("non-string type but invalid json", func(t *testing.T) {
|
||||
_, err := filter.BuildExpr(resolver, dbx.Params{
|
||||
"test": brokenJSON{},
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("Expected filter build error, got nil")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestFilterDataBuildExprWithLimit(t *testing.T) {
|
||||
resolver := search.NewSimpleFieldResolver(`^\w+$`)
|
||||
|
||||
|
||||
@@ -12,4 +12,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.40.1"
|
||||
PB_VERSION = "v0.40.2"
|
||||
|
||||
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
import{n as e,t as n}from"./index-Bmez3bfs.js";function r(r){let i=r.params?.token||``,a=e(i);if(!a.newEmail||!a.collectionId){app.toasts.error(`Invalid or expired email change token.`),window.location.hash=`#/`;return}app.store.title=`Confirm email change`;let o=store({password:``,isSubmitting:!1,isSuccess:!1,showPassword:!1});async function s(){if(o.isSubmitting)return;o.isSubmitting=!0;let e=new n(app.pb.baseURL);try{await e.collection(a.collectionId).confirmEmailChange(i,o.password),o.isSuccess=!0}catch(e){app.checkApiError(e),o.isSuccess=!1}o.isSubmitting=!1}return t.div({pbEvent:`pageConfirmEmailChange`,className:`wrapper sm m-auto p-b-base`},t.header({className:`txt-center m-b-base`},t.img({className:`main-logo`,src:()=>app.store.mainLogo,ariaHidden:!0,alt:`App logo`}),t.h5({className:`m-t-10`},()=>app.store.title)),()=>o.isSuccess?t.div({pbEvent:`confirmEmailChangeAlert`,className:`alert success txt-center`},t.p(null,`The email was successfully changed.`),t.p(null,`You can go back and sign in with your new email address.`)):t.form({pbEvent:`confirmEmailChangeForm`,className:`grid confirm-email-change-form`,onsubmit:e=>{e.preventDefault(),s()}},t.div({className:`col-12`},t.div({className:`content txt-center m-b-sm`},`Type your password to confirm changing your email address to `,t.strong(null,a.newEmail),`:`),t.div({className:`fields`},t.div({className:`field`},t.label({htmlFor:`password_confirm`},`Password`),t.input({id:`password_confirm`,name:`password`,required:!0,autofocus:!0,type:()=>o.showPassword?`text`:`password`,value:()=>o.password,oninput:e=>o.password=e.target.value})),t.div({className:`field addon`},t.button({type:`button`,tabIndex:-1,className:`btn sm transparent secondary circle tooltip-right`,ariaLabel:app.attrs.tooltip(()=>o.showPassword?`Hide password`:`Show password`),onclick:()=>o.showPassword=!o.showPassword},t.i({className:()=>o.showPassword?`ri-eye-off-line`:`ri-eye-line`,ariaHidden:!0}))))),t.div({className:`col-12`},t.button({className:()=>`btn lg block ${o.isSubmitting?`loading`:``}`,disabled:()=>o.isSubmitting},t.span({className:`txt`},`Confirm new email`)))))}export{r as pageConfirmEmailChange};
|
||||
import{n as e,t as n}from"./index-DoeyJa3f.js";function r(r){let i=r.params?.token||``,a=e(i);if(!a.newEmail||!a.collectionId){app.toasts.error(`Invalid or expired email change token.`),window.location.hash=`#/`;return}app.store.title=`Confirm email change`;let o=store({password:``,isSubmitting:!1,isSuccess:!1,showPassword:!1});async function s(){if(o.isSubmitting)return;o.isSubmitting=!0;let e=new n(app.pb.baseURL);try{await e.collection(a.collectionId).confirmEmailChange(i,o.password),o.isSuccess=!0}catch(e){app.checkApiError(e),o.isSuccess=!1}o.isSubmitting=!1}return t.div({pbEvent:`pageConfirmEmailChange`,className:`wrapper sm m-auto p-b-base`},t.header({className:`txt-center m-b-base`},t.img({className:`main-logo`,src:()=>app.store.mainLogo,ariaHidden:!0,alt:`App logo`}),t.h5({className:`m-t-10`},()=>app.store.title)),()=>o.isSuccess?t.div({pbEvent:`confirmEmailChangeAlert`,className:`alert success txt-center`},t.p(null,`The email was successfully changed.`),t.p(null,`You can go back and sign in with your new email address.`)):t.form({pbEvent:`confirmEmailChangeForm`,className:`grid confirm-email-change-form`,onsubmit:e=>{e.preventDefault(),s()}},t.div({className:`col-12`},t.div({className:`content txt-center m-b-sm`},`Type your password to confirm changing your email address to `,t.strong(null,a.newEmail),`:`),t.div({className:`fields`},t.div({className:`field`},t.label({htmlFor:`password_confirm`},`Password`),t.input({id:`password_confirm`,name:`password`,required:!0,autofocus:!0,type:()=>o.showPassword?`text`:`password`,value:()=>o.password,oninput:e=>o.password=e.target.value})),t.div({className:`field addon`},t.button({type:`button`,tabIndex:-1,className:`btn sm transparent secondary circle tooltip-right`,ariaLabel:app.attrs.tooltip(()=>o.showPassword?`Hide password`:`Show password`),onclick:()=>o.showPassword=!o.showPassword},t.i({className:()=>o.showPassword?`ri-eye-off-line`:`ri-eye-line`,ariaHidden:!0}))))),t.div({className:`col-12`},t.button({className:()=>`btn lg block ${o.isSubmitting?`loading`:``}`,disabled:()=>o.isSubmitting},t.span({className:`txt`},`Confirm new email`)))))}export{r as pageConfirmEmailChange};
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{n as e,t as n}from"./index-Bmez3bfs.js";function r(r){let i=r.params?.token||``,a=e(i);if(!a.email||!a.collectionId){app.toasts.error(`Invalid or expired password reset token.`),window.location.hash=`#/`;return}app.store.title=`Confirm password reset`;let o=store({newPassword:``,newPasswordConfirm:``,showNewPassword:!1,showNewPasswordConfirm:!1,isSubmitting:!1,isSuccess:!1});async function s(){if(o.isSubmitting)return;o.isSubmitting=!0;let e=new n(app.pb.baseURL);try{await e.collection(a.collectionId).confirmPasswordReset(i,o.newPassword,o.newPasswordConfirm),o.isSuccess=!0}catch(e){app.checkApiError(e)}o.isSubmitting=!1}return t.div({pbEvent:`pageConfirmPasswordReset`,className:`wrapper sm m-auto p-b-base`},t.header({className:`txt-center m-b-base`},t.img({className:`main-logo`,src:()=>app.store.mainLogo,ariaHidden:!0,alt:`App logo`}),t.h5({className:`m-t-10`},()=>app.store.title)),()=>o.isSuccess?t.div({pbEvent:`confirmPasswordResetAlert`,className:`alert success txt-center`},t.p(null,`The password was successfully changed.`),t.p(null,`You can go back to sign in with your new password.`)):t.form({pbEvent:`confirmPasswordResetForm`,className:`grid confirm-password-reset-form`,onsubmit:e=>{e.preventDefault(),s()}},t.div({className:`col-12`},t.div({className:`content txt-center m-b-sm`},`Type your new password for `,t.strong(null,a.email),`:`),t.div({className:`fields`},t.div({className:`field`},t.label({htmlFor:`newPassword`},`New password`),t.input({id:`newPassword`,name:`password`,required:!0,autofocus:!0,autocomplete:`new-password`,type:()=>o.showNewPassword?`text`:`password`,value:()=>o.newPassword,oninput:e=>o.newPassword=e.target.value})),t.div({className:`field addon`},t.button({type:`button`,tabIndex:-1,className:`btn sm transparent secondary circle tooltip-right`,ariaLabel:app.attrs.tooltip(()=>o.showNewPassword?`Hide password`:`Show password`),onclick:()=>o.showNewPassword=!o.showNewPassword},t.i({className:()=>o.showNewPassword?`ri-eye-off-line`:`ri-eye-line`,ariaHidden:!0}))))),t.div({className:`col-12`},t.div({className:`fields`},t.div({className:`field`},t.label({htmlFor:`newPasswordConfirm`},`New password confirm`),t.input({id:`newPasswordConfirm`,name:`passwordConfirm`,required:!0,autocomplete:`new-password`,type:()=>o.showNewPasswordConfirm?`text`:`password`,value:()=>o.newPasswordConfirm,oninput:e=>o.newPasswordConfirm=e.target.value})),t.div({className:`field addon`},t.button({type:`button`,tabIndex:-1,className:`btn sm transparent secondary circle tooltip-right`,ariaLabel:app.attrs.tooltip(()=>o.showNewPasswordConfirm?`Hide password`:`Show password`),onclick:()=>o.showNewPasswordConfirm=!o.showNewPasswordConfirm},t.i({className:()=>o.showNewPasswordConfirm?`ri-eye-off-line`:`ri-eye-line`,ariaHidden:!0}))))),t.div({className:`col-12`},t.button({className:()=>`btn lg block ${o.isSubmitting?`loading`:``}`,disabled:()=>o.isSubmitting},t.span({className:`txt`},`Set new password`)))))}export{r as pageConfirmPasswordReset};
|
||||
import{n as e,t as n}from"./index-DoeyJa3f.js";function r(r){let i=r.params?.token||``,a=e(i);if(!a.email||!a.collectionId){app.toasts.error(`Invalid or expired password reset token.`),window.location.hash=`#/`;return}app.store.title=`Confirm password reset`;let o=store({newPassword:``,newPasswordConfirm:``,showNewPassword:!1,showNewPasswordConfirm:!1,isSubmitting:!1,isSuccess:!1});async function s(){if(o.isSubmitting)return;o.isSubmitting=!0;let e=new n(app.pb.baseURL);try{await e.collection(a.collectionId).confirmPasswordReset(i,o.newPassword,o.newPasswordConfirm),o.isSuccess=!0}catch(e){app.checkApiError(e)}o.isSubmitting=!1}return t.div({pbEvent:`pageConfirmPasswordReset`,className:`wrapper sm m-auto p-b-base`},t.header({className:`txt-center m-b-base`},t.img({className:`main-logo`,src:()=>app.store.mainLogo,ariaHidden:!0,alt:`App logo`}),t.h5({className:`m-t-10`},()=>app.store.title)),()=>o.isSuccess?t.div({pbEvent:`confirmPasswordResetAlert`,className:`alert success txt-center`},t.p(null,`The password was successfully changed.`),t.p(null,`You can go back to sign in with your new password.`)):t.form({pbEvent:`confirmPasswordResetForm`,className:`grid confirm-password-reset-form`,onsubmit:e=>{e.preventDefault(),s()}},t.div({className:`col-12`},t.div({className:`content txt-center m-b-sm`},`Type your new password for `,t.strong(null,a.email),`:`),t.div({className:`fields`},t.div({className:`field`},t.label({htmlFor:`newPassword`},`New password`),t.input({id:`newPassword`,name:`password`,required:!0,autofocus:!0,autocomplete:`new-password`,type:()=>o.showNewPassword?`text`:`password`,value:()=>o.newPassword,oninput:e=>o.newPassword=e.target.value})),t.div({className:`field addon`},t.button({type:`button`,tabIndex:-1,className:`btn sm transparent secondary circle tooltip-right`,ariaLabel:app.attrs.tooltip(()=>o.showNewPassword?`Hide password`:`Show password`),onclick:()=>o.showNewPassword=!o.showNewPassword},t.i({className:()=>o.showNewPassword?`ri-eye-off-line`:`ri-eye-line`,ariaHidden:!0}))))),t.div({className:`col-12`},t.div({className:`fields`},t.div({className:`field`},t.label({htmlFor:`newPasswordConfirm`},`New password confirm`),t.input({id:`newPasswordConfirm`,name:`passwordConfirm`,required:!0,autocomplete:`new-password`,type:()=>o.showNewPasswordConfirm?`text`:`password`,value:()=>o.newPasswordConfirm,oninput:e=>o.newPasswordConfirm=e.target.value})),t.div({className:`field addon`},t.button({type:`button`,tabIndex:-1,className:`btn sm transparent secondary circle tooltip-right`,ariaLabel:app.attrs.tooltip(()=>o.showNewPasswordConfirm?`Hide password`:`Show password`),onclick:()=>o.showNewPasswordConfirm=!o.showNewPasswordConfirm},t.i({className:()=>o.showNewPasswordConfirm?`ri-eye-off-line`:`ri-eye-line`,ariaHidden:!0}))))),t.div({className:`col-12`},t.button({className:()=>`btn lg block ${o.isSubmitting?`loading`:``}`,disabled:()=>o.isSubmitting},t.span({className:`txt`},`Set new password`)))))}export{r as pageConfirmPasswordReset};
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{n as e,t as n}from"./index-Bmez3bfs.js";function r(r){let i=r.params?.token||``,a=e(i);if(!a.email||!a.collectionId){app.toasts.error(`Invalid or expired verification token.`),window.location.hash=`#/`;return}app.store.title=`Confirm verification`;let o=store({isConfirming:!1,isConfirmSuccess:!1,isResending:!1,isResendSuccess:!1});s();async function s(){if(o.isConfirming)return;o.isConfirming=!0;let e=new n(app.pb.baseURL);try{await e.collection(a.collectionId).confirmVerification(i),o.isConfirmSuccess=!0}catch{o.isConfirmSuccess=!1}o.isConfirming=!1}async function c(){if(o.isResending)return;o.isResending=!0;let e=new n(`../`);try{await e.collection(a.collectionId).requestVerification(a.email),o.isResendSuccess=!0}catch(e){app.checkApiError(e),o.isResendSuccess=!1}o.isResending=!1}return t.div({pbEvent:`pageConfirmVerification`,className:`wrapper sm m-auto p-b-base`},t.header({className:`txt-center m-b-base`},t.img({className:`main-logo`,src:()=>app.store.mainLogo,ariaHidden:!0,alt:`App logo`}),t.h5({className:`m-t-10`},()=>app.store.title)),()=>o.isConfirming?t.div({className:`block txt-center`},t.span({className:`loader`},`Please wait...`)):o.isConfirmSuccess?t.div({pbEvent:`confirmVerificationSuccessAlert`,className:`alert success txt-center`},t.p(null,`Successfully verified `,t.strong(null,a.email),`.`)):o.isResendSuccess?t.div({pbEvent:`confirmVerificationResendAlert`,className:`alert success txt-center`},t.p(null,`Please check your email for the new verification link.`)):[t.div({pbEvent:`confirmVerificationErrorAlert`,className:`alert danger txt-center m-b-base`},t.p(null,`Invalid or expired verification token.`)),t.button({type:`button`,className:()=>`btn transparent lg block ${o.isResending?`loading`:``}`,disabled:()=>o.isResending,onclick:()=>c()},t.span({className:`txt`},`Resend`))])}export{r as pageConfirmVerification};
|
||||
import{n as e,t as n}from"./index-DoeyJa3f.js";function r(r){let i=r.params?.token||``,a=e(i);if(!a.email||!a.collectionId){app.toasts.error(`Invalid or expired verification token.`),window.location.hash=`#/`;return}app.store.title=`Confirm verification`;let o=store({isConfirming:!1,isConfirmSuccess:!1,isResending:!1,isResendSuccess:!1});s();async function s(){if(o.isConfirming)return;o.isConfirming=!0;let e=new n(app.pb.baseURL);try{await e.collection(a.collectionId).confirmVerification(i),o.isConfirmSuccess=!0}catch{o.isConfirmSuccess=!1}o.isConfirming=!1}async function c(){if(o.isResending)return;o.isResending=!0;let e=new n(`../`);try{await e.collection(a.collectionId).requestVerification(a.email),o.isResendSuccess=!0}catch(e){app.checkApiError(e),o.isResendSuccess=!1}o.isResending=!1}return t.div({pbEvent:`pageConfirmVerification`,className:`wrapper sm m-auto p-b-base`},t.header({className:`txt-center m-b-base`},t.img({className:`main-logo`,src:()=>app.store.mainLogo,ariaHidden:!0,alt:`App logo`}),t.h5({className:`m-t-10`},()=>app.store.title)),()=>o.isConfirming?t.div({className:`block txt-center`},t.span({className:`loader`},`Please wait...`)):o.isConfirmSuccess?t.div({pbEvent:`confirmVerificationSuccessAlert`,className:`alert success txt-center`},t.p(null,`Successfully verified `,t.strong(null,a.email),`.`)):o.isResendSuccess?t.div({pbEvent:`confirmVerificationResendAlert`,className:`alert success txt-center`},t.p(null,`Please check your email for the new verification link.`)):[t.div({pbEvent:`confirmVerificationErrorAlert`,className:`alert danger txt-center m-b-base`},t.p(null,`Invalid or expired verification token.`)),t.button({type:`button`,className:()=>`btn transparent lg block ${o.isResending?`loading`:``}`,disabled:()=>o.isResending,onclick:()=>c()},t.span({className:`txt`},`Resend`))])}export{r as pageConfirmVerification};
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{n as e,r as n}from"./index-Bmez3bfs.js";function r(r){let i=r.params?.token||``;if(e(i).type!=`auth`||n(i)){app.toasts.error(`The installer token is invalid or has expired.`),window.location.hash=`#/`;return}app.store.title=`Setup your PocketBase instance`;let a=store({email:``,password:``,passwordConfirm:``,showPassword:!1,showPasswordConfirm:!1,isSubmitting:!1,isUploading:!1,get isBusy(){return a.isSubmitting||a.isUploading}});async function o(){if(!a.isBusy){a.isSubmitting=!0;try{await app.pb.collection(`_superusers`).create({email:a.email,password:a.password,passwordConfirm:a.passwordConfirm},{headers:{Authorization:i}}),await app.pb.collection(`_superusers`).authWithPassword(a.email,a.password),window.location.hash=`#/`}catch(e){app.checkApiError(e)}a.isSubmitting=!1}}let s=`backupFileInput`;function c(){let e=document.getElementById(s);e&&(e.value=``)}function l(e){e&&app.modals.confirm(t.h6(null,`Note that we don't perform validations for the uploaded backup files. Proceed with caution and only if you trust the file source.\n\nDo you really want to upload and initialize "${e.name}"?`),()=>{u(e)},()=>{c()})}async function u(e){if(!(!e||a.isBusy)){a.isUploading=!0;try{await app.pb.backups.upload({file:e},{headers:{Authorization:i}}),await app.pb.backups.restore(e.name,{headers:{Authorization:i}}),app.toasts.info(`Please wait while extracting the uploaded archive!`),await new Promise(e=>setTimeout(e,3e3)),window.location.href=`#/`}catch(e){app.checkApiError(e)}c(),a.isUploading=!1}}return t.div({pbEvent:`pageInstaller`,className:`wrapper sm m-auto p-b-base`},t.header({className:`txt-center m-b-base`},t.img({className:`main-logo`,src:()=>app.store.mainLogo,ariaHidden:!0,alt:`App logo`}),t.h5({className:`m-t-10`},()=>app.store.title)),t.form({pbEvent:`installerForm`,className:`grid installer-form`,onsubmit:e=>{e.preventDefault(),o(a)}},t.div({className:`col-12 txt-center`},`Create your first superuser account in order to continue:`),t.div({className:`col-12`},t.div({className:`field`},t.label({htmlFor:`superuser_email`},`Email`),t.input({id:`superuser_email`,name:`email`,type:`email`,required:!0,autofocus:!0,autocomplete:`off`,disabled:()=>a.isBusy,value:()=>a.email,oninput:e=>a.email=e.target.value}))),t.div({className:`col-12`},t.div({className:`fields`},t.div({className:`field`},t.label({htmlFor:`superuser_password`},`Password`),t.input({id:`superuser_password`,name:`password`,min:10,required:!0,disabled:()=>a.isBusy,type:()=>a.showPassword?`text`:`password`,value:()=>a.password,oninput:e=>a.password=e.target.value})),t.div({className:`field addon`},t.button({type:`button`,tabIndex:-1,className:`btn sm transparent secondary circle tooltip-right`,ariaLabel:app.attrs.tooltip(()=>a.showPassword?`Hide password`:`Show password`),onclick:()=>a.showPassword=!a.showPassword},t.i({className:()=>a.showPassword?`ri-eye-off-line`:`ri-eye-line`,ariaHidden:!0})))),t.div({className:`field-help`},`Recommended at least 10 characters.`)),t.div({className:`col-12`},t.div({className:`fields`},t.div({className:`field`},t.label({htmlFor:`superuser_password_confirm`},`Password confirm`),t.input({id:`superuser_password_confirm`,name:`passwordConfirm`,required:!0,disabled:()=>a.isBusy,type:()=>a.showPasswordConfirm?`text`:`password`,value:()=>a.passwordConfirm,oninput:e=>a.passwordConfirm=e.target.value})),t.div({className:`field addon`},t.button({type:`button`,tabIndex:-1,className:`btn sm transparent secondary circle tooltip-right`,ariaLabel:app.attrs.tooltip(()=>a.showPasswordConfirm?`Hide password`:`Show password`),onclick:()=>a.showPasswordConfirm=!a.showPasswordConfirm},t.i({className:()=>a.showPasswordConfirm?`ri-eye-off-line`:`ri-eye-line`,ariaHidden:!0}))))),t.div({className:`col-12`},t.button({className:()=>`btn lg next block ${a.isSubmitting?`loading`:``}`,disabled:()=>a.isBusy},t.span({className:`txt`},`Create superuser and login`),t.i({className:`ri-arrow-right-line`,ariaHidden:!0})))),t.hr(),t.label({htmlFor:s,className:()=>`btn secondary transparent lg block ${a.isBusy?`disabled`:``} ${a.isUploading?`loading`:``}`},t.i({className:`ri-upload-cloud-line`,ariaHidden:!0}),t.span({className:`txt`},`Or initialize from backup`)),t.input({id:s,type:`file`,className:`hidden`,accept:`.zip`,onchange:e=>{l(e.target?.files?.[0])}}))}export{r as pageInstaller};
|
||||
import{n as e,r as n}from"./index-DoeyJa3f.js";function r(r){let i=r.params?.token||``;if(e(i).type!=`auth`||n(i)){app.toasts.error(`The installer token is invalid or has expired.`),window.location.hash=`#/`;return}app.store.title=`Setup your PocketBase instance`;let a=store({email:``,password:``,passwordConfirm:``,showPassword:!1,showPasswordConfirm:!1,isSubmitting:!1,isUploading:!1,get isBusy(){return a.isSubmitting||a.isUploading}});async function o(){if(!a.isBusy){a.isSubmitting=!0;try{await app.pb.collection(`_superusers`).create({email:a.email,password:a.password,passwordConfirm:a.passwordConfirm},{headers:{Authorization:i}}),await app.pb.collection(`_superusers`).authWithPassword(a.email,a.password),window.location.hash=`#/`}catch(e){app.checkApiError(e)}a.isSubmitting=!1}}let s=`backupFileInput`;function c(){let e=document.getElementById(s);e&&(e.value=``)}function l(e){e&&app.modals.confirm(t.h6(null,`Note that we don't perform validations for the uploaded backup files. Proceed with caution and only if you trust the file source.\n\nDo you really want to upload and initialize "${e.name}"?`),()=>{u(e)},()=>{c()})}async function u(e){if(!(!e||a.isBusy)){a.isUploading=!0;try{await app.pb.backups.upload({file:e},{headers:{Authorization:i}}),await app.pb.backups.restore(e.name,{headers:{Authorization:i}}),app.toasts.info(`Please wait while extracting the uploaded archive!`),await new Promise(e=>setTimeout(e,3e3)),window.location.href=`#/`}catch(e){app.checkApiError(e)}c(),a.isUploading=!1}}return t.div({pbEvent:`pageInstaller`,className:`wrapper sm m-auto p-b-base`},t.header({className:`txt-center m-b-base`},t.img({className:`main-logo`,src:()=>app.store.mainLogo,ariaHidden:!0,alt:`App logo`}),t.h5({className:`m-t-10`},()=>app.store.title)),t.form({pbEvent:`installerForm`,className:`grid installer-form`,onsubmit:e=>{e.preventDefault(),o(a)}},t.div({className:`col-12 txt-center`},`Create your first superuser account in order to continue:`),t.div({className:`col-12`},t.div({className:`field`},t.label({htmlFor:`superuser_email`},`Email`),t.input({id:`superuser_email`,name:`email`,type:`email`,required:!0,autofocus:!0,autocomplete:`off`,disabled:()=>a.isBusy,value:()=>a.email,oninput:e=>a.email=e.target.value}))),t.div({className:`col-12`},t.div({className:`fields`},t.div({className:`field`},t.label({htmlFor:`superuser_password`},`Password`),t.input({id:`superuser_password`,name:`password`,min:10,required:!0,disabled:()=>a.isBusy,type:()=>a.showPassword?`text`:`password`,value:()=>a.password,oninput:e=>a.password=e.target.value})),t.div({className:`field addon`},t.button({type:`button`,tabIndex:-1,className:`btn sm transparent secondary circle tooltip-right`,ariaLabel:app.attrs.tooltip(()=>a.showPassword?`Hide password`:`Show password`),onclick:()=>a.showPassword=!a.showPassword},t.i({className:()=>a.showPassword?`ri-eye-off-line`:`ri-eye-line`,ariaHidden:!0})))),t.div({className:`field-help`},`Recommended at least 10 characters.`)),t.div({className:`col-12`},t.div({className:`fields`},t.div({className:`field`},t.label({htmlFor:`superuser_password_confirm`},`Password confirm`),t.input({id:`superuser_password_confirm`,name:`passwordConfirm`,required:!0,disabled:()=>a.isBusy,type:()=>a.showPasswordConfirm?`text`:`password`,value:()=>a.passwordConfirm,oninput:e=>a.passwordConfirm=e.target.value})),t.div({className:`field addon`},t.button({type:`button`,tabIndex:-1,className:`btn sm transparent secondary circle tooltip-right`,ariaLabel:app.attrs.tooltip(()=>a.showPasswordConfirm?`Hide password`:`Show password`),onclick:()=>a.showPasswordConfirm=!a.showPasswordConfirm},t.i({className:()=>a.showPasswordConfirm?`ri-eye-off-line`:`ri-eye-line`,ariaHidden:!0}))))),t.div({className:`col-12`},t.button({className:()=>`btn lg next block ${a.isSubmitting?`loading`:``}`,disabled:()=>a.isBusy},t.span({className:`txt`},`Create superuser and login`),t.i({className:`ri-arrow-right-line`,ariaHidden:!0})))),t.hr(),t.label({htmlFor:s,className:()=>`btn secondary transparent lg block ${a.isBusy?`disabled`:``} ${a.isUploading?`loading`:``}`},t.i({className:`ri-upload-cloud-line`,ariaHidden:!0}),t.span({className:`txt`},`Or initialize from backup`)),t.input({id:s,type:`file`,className:`hidden`,accept:`.zip`,onchange:e=>{l(e.target?.files?.[0])}}))}export{r as pageInstaller};
|
||||
Vendored
+1
-1
@@ -13,7 +13,7 @@
|
||||
|
||||
<!-- prism -->
|
||||
<script src="./libs/prism/prism.js" data-manual></script>
|
||||
<script type="module" crossorigin src="./assets/index-Bmez3bfs.js"></script>
|
||||
<script type="module" crossorigin src="./assets/index-DoeyJa3f.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-CZinEv5W.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
+74
-57
@@ -108,6 +108,8 @@ window.app.components.codeEditor = function(propsArg = {}) {
|
||||
}
|
||||
|
||||
function closeAutocompleteDropdown() {
|
||||
clearTimeout(autocompleteTimeoutId);
|
||||
|
||||
if (dropdown) {
|
||||
dropdown.remove();
|
||||
dropdown = null;
|
||||
@@ -131,6 +133,8 @@ window.app.components.codeEditor = function(propsArg = {}) {
|
||||
|
||||
let valueWatcher;
|
||||
|
||||
let autocompleteTimeoutId;
|
||||
|
||||
// note1: use contenteditable so that we can call getBoundingClientRect on the selected text
|
||||
// note2: getSelection also doesn't seem to work in Firefox for textarea and inputs
|
||||
const editorContent = t.div({
|
||||
@@ -173,86 +177,98 @@ window.app.components.codeEditor = function(propsArg = {}) {
|
||||
props.onblur?.(props.value);
|
||||
},
|
||||
oninput: (e) => {
|
||||
closeAutocompleteDropdown();
|
||||
clearTimeout(autocompleteTimeoutId);
|
||||
|
||||
updateValue(editorContent.textContent);
|
||||
|
||||
if (!props.value?.length) {
|
||||
editorContent.textContent = ""; // ensure that no comments, br, etc. tags are left
|
||||
closeAutocompleteDropdown();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!editorContent?.isConnected) {
|
||||
return;
|
||||
}
|
||||
autocompleteTimeoutId = setTimeout(() => {
|
||||
closeAutocompleteDropdown();
|
||||
|
||||
const pos = getCaretPos(editorContent);
|
||||
if (!editorContent?.isConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
const match = getWord(props.value, pos);
|
||||
const pos = getCaretPos(editorContent);
|
||||
|
||||
if (
|
||||
!match.word.length
|
||||
// don't show suggestions in case the cursor is at the
|
||||
// beginning of an already typed word
|
||||
|| pos == match.start
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const match = getWord(props.value, pos);
|
||||
|
||||
let suggestions = [];
|
||||
if (typeof props.autocomplete == "function") {
|
||||
suggestions = props.autocomplete(match.word) || [];
|
||||
} else if (!app.utils.isEmpty(props.autocomplete)) {
|
||||
const wordLowercased = match.word.toLowerCase();
|
||||
suggestions = props.autocomplete.filter((item) => {
|
||||
if (typeof item == "object") {
|
||||
item = item?.value;
|
||||
}
|
||||
if (
|
||||
!match.word.length
|
||||
// don't show suggestions in case the cursor is at the
|
||||
// beginning of an already typed word
|
||||
|| pos == match.start
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
item = item?.toLowerCase();
|
||||
let suggestions = [];
|
||||
if (typeof props.autocomplete == "function") {
|
||||
suggestions = props.autocomplete(match.prefix || match.word) || [];
|
||||
} else if (!app.utils.isEmpty(props.autocomplete)) {
|
||||
const wordLowercased = (match.prefix || match.word).toLowerCase();
|
||||
suggestions = props.autocomplete.filter((item) => {
|
||||
if (typeof item == "object") {
|
||||
item = item?.value;
|
||||
}
|
||||
|
||||
return item && item != wordLowercased && item.includes(wordLowercased);
|
||||
});
|
||||
}
|
||||
item = item?.toLowerCase();
|
||||
|
||||
if (!suggestions?.length) {
|
||||
return;
|
||||
}
|
||||
return item && item != wordLowercased && item.includes(wordLowercased);
|
||||
});
|
||||
}
|
||||
|
||||
openAutocompleteDropdown(() => {
|
||||
return suggestions.map((suggestion, i) => {
|
||||
return t.button({
|
||||
type: "button",
|
||||
className: `dropdown-item ${i == 0 ? "active" : ""}`,
|
||||
textContent: suggestion.label || suggestion.value || suggestion,
|
||||
onclick: (e) => {
|
||||
e.preventDefault();
|
||||
if (
|
||||
!suggestions?.length
|
||||
// don't show if the only suggestion is exact match
|
||||
|| (
|
||||
suggestions.length == 1
|
||||
&& (suggestions[0].value || suggestions[0]) == match.word
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
editorContent.focus();
|
||||
openAutocompleteDropdown(() => {
|
||||
return suggestions.map((suggestion, i) => {
|
||||
return t.button({
|
||||
type: "button",
|
||||
className: `dropdown-item ${i == 0 ? "active" : ""}`,
|
||||
textContent: suggestion.label || suggestion.value || suggestion,
|
||||
onclick: (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const word = suggestion.value || suggestion;
|
||||
editorContent.focus();
|
||||
|
||||
// note: replacing the text doesn't preserve the native "undo" history
|
||||
// (document.execCommand is being deprecated)
|
||||
editorContent.textContent = editorContent.textContent.substring(0, match.start)
|
||||
+ word
|
||||
+ editorContent.textContent.substring(match.end + 1);
|
||||
const word = suggestion.value || suggestion;
|
||||
|
||||
updateValue(editorContent.textContent);
|
||||
// note: replacing the text doesn't preserve the native "undo" history
|
||||
// (document.execCommand is being deprecated)
|
||||
editorContent.textContent = editorContent.textContent.substring(0, match.start)
|
||||
+ word
|
||||
+ editorContent.textContent.substring(match.end + 1);
|
||||
|
||||
try {
|
||||
window
|
||||
.getSelection()
|
||||
.setPosition(editorContent.childNodes[0], match.start + word.length);
|
||||
} catch (err) {
|
||||
console.warn("failed to set caret position", err);
|
||||
}
|
||||
updateValue(editorContent.textContent);
|
||||
|
||||
closeAutocompleteDropdown();
|
||||
},
|
||||
try {
|
||||
window
|
||||
.getSelection()
|
||||
.setPosition(editorContent.childNodes[0], match.start + word.length);
|
||||
} catch (err) {
|
||||
console.warn("failed to set caret position", err);
|
||||
}
|
||||
|
||||
closeAutocompleteDropdown();
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}, 50);
|
||||
},
|
||||
onkeydown: (e) => {
|
||||
isCtrlOrCmdKey = e.ctrlKey || e.metaKey;
|
||||
@@ -404,7 +420,7 @@ window.app.components.codeEditor = function(propsArg = {}) {
|
||||
);
|
||||
};
|
||||
|
||||
const highlightThreshold = 500;
|
||||
const highlightThreshold = 800;
|
||||
|
||||
function highlight(content, language) {
|
||||
content = typeof content == "string" ? content : "";
|
||||
@@ -444,6 +460,7 @@ function getWord(value, caretPos) {
|
||||
|
||||
return {
|
||||
word: value.substring(start, end + 1),
|
||||
prefix: value.substring(start, caretPos),
|
||||
start: start,
|
||||
end: end,
|
||||
};
|
||||
|
||||
+1
-1
@@ -129,7 +129,7 @@ app.pb.authStore.onChange((_, record) => {
|
||||
|
||||
/**
|
||||
* Return a superuser file token.
|
||||
* Optionally you can provide a collection identifier, to avoid unnecessery
|
||||
* Optionally you can provide a collection identifier, to avoid unnecessary
|
||||
* calls in case the collection doesn't have protected files.
|
||||
*
|
||||
* @param {String} optCollectionIdORName
|
||||
|
||||
Reference in New Issue
Block a user