various minor ui fixes

This commit is contained in:
Gani Georgiev
2026-04-27 01:13:08 +03:00
parent 326f150db2
commit 419f335f5b
8 changed files with 148 additions and 123 deletions

View File

@@ -2,21 +2,21 @@
- Added backups list scroll container ([#7655](https://github.com/pocketbase/pocketbase/issues/7655)).
- Optimized record upsert and preview modals loading to minimize layout jumps.
- Optimized record upsert and preview modals data loading to minimize layout jumps.
- Fixed SMTP IPv6 network address format ([#7659](https://github.com/pocketbase/pocketbase/issues/7659)).
- Fixed autocomplete selection not properly updating the underlying input value ([#7664](https://github.com/pocketbase/pocketbase/issues/7664)).
- Added dummy bcrypt password check for the failure auth path to minimize enumaration timing attacks.
- Added `ghupdate.BaseURL` config option ([#7665](https://github.com/pocketbase/pocketbase/issues/7665)).
- Added dummy bcrypt password check for the failure auth path to minimize enumaration timing attacks when registrations are disabled.
- Adjusted Bitbucket, GitHub and Gitea/Forgejo OAuth2 providers to better reflect recent API updates and doc references.
_The providers also now always send a sepatate emails list internal request since it contains more information about the fetched email than the userinfo endpoint in order to minimize eventual linking security issues caused by custom onpremise setups (e.g. Gitea/Forgejo allows skipping the emails verification if an ENV variable is configured)._
_The providers also now always send a sepatate emails list request since it has more information about the fetched email than the userinfo endpoint in order to minimize eventual linking security issues caused by custom onpremise setups (e.g. Gitea/Forgejo allows skipping the email verification if an ENV variable is configured)._
- ⚠️ Fixed a pre-hijacking OAuth2 linking vulnerability ([#7662](https://github.com/pocketbase/pocketbase/discussions/7662); thanks @Alardiians for reporting it privately).
- Added `ghupdate.BaseURL` config option ([#7665](https://github.com/pocketbase/pocketbase/issues/7665)).
- Bumped Go and npm dependencies.

View File

File diff suppressed because one or more lines are too long

81
ui/dist/assets/index-DijNBRV3.js vendored Normal file
View File

File diff suppressed because one or more lines are too long

2
ui/dist/index.html vendored
View File

@@ -13,7 +13,7 @@
<!-- prism -->
<script src="./libs/prism/prism.js" data-manual></script>
<script type="module" crossorigin src="./assets/index-DbuYk4bQ.js"></script>
<script type="module" crossorigin src="./assets/index-DijNBRV3.js"></script>
<link rel="modulepreload" crossorigin href="./assets/pocketbase.es-B_4DUNUU.js">
<link rel="stylesheet" crossorigin href="./assets/index-ouas71Vg.css">
</head>

View File

@@ -124,10 +124,10 @@ window.app.components.tinymce = function(propsArg = {}) {
clearTimeout(changeTimeoutId);
// workaround for https://github.com/tinymce/tinymce/issues/9377
editorRef.dom?.unbind(document);
catchError(() => {
// workaround for https://github.com/tinymce/tinymce/issues/9377
editorRef.dom?.unbind(document);
window.tinymce?.remove(editorRef);
});
editorRef = null;

View File

@@ -7,9 +7,32 @@
export function input(props) {
const uniqueId = "editor_" + app.utils.randomString();
const data = store({
lazyEditor: "",
});
let lazyEditorTimeoutId;
return t.div(
{
className: "record-field-input field-type-editor large-modal",
onmount: () => {
lazyEditorTimeoutId = setTimeout(() => {
data.lazyEditor = app.components.tinymce({
id: uniqueId,
name: () => props.field.name,
required: () => props.field.required,
convertURLs: () => props.field.convertURLs,
value: () => props.record[props.field.name] || "",
onchange: (val) => {
props.record[props.field.name] = val;
},
});
}, 0);
},
onunmount: () => {
clearTimeout(lazyEditorTimeoutId);
},
},
t.div(
{ className: "field" },
@@ -18,18 +41,7 @@ export function input(props) {
t.i({ className: app.fieldTypes.editor.icon, ariaHidden: true }),
t.span({ className: "txt" }, () => props.field.name),
),
() => {
return app.components.tinymce({
id: uniqueId,
name: () => props.field.name,
required: () => props.field.required,
convertURLs: () => props.field.convertURLs,
value: () => props.record[props.field.name] || "",
onchange: (val) => {
props.record[props.field.name] = val;
},
});
},
() => data.lazyEditor,
),
() => {
if (props.field.help) {

View File

@@ -304,7 +304,7 @@ export function settings(data) {
t.span({ className: "txt" }, "Protected"),
t.small(
{ className: "txt-hint" },
"Files will require View API rule permissions and file token (",
"File download requests will need to satisfy the View API rule (",
t.a({
href: import.meta.env.PB_PROTECTED_FILE_DOCS,
target: "_blank",

View File

@@ -43,28 +43,27 @@ window.app.modals.openRecordUpsert = function(collection, record = null, modalSe
app.modals.open(modal);
};
const defaultRedactFields = ["expand"];
function redacted(record, redactFields = defaultRedactFields) {
// create redacted clone only if necessery
if (redactFields.find((f) => typeof record[f] !== "undefined")) {
record = Object.assign({}, record);
for (let f of redactFields) {
delete record[f];
}
// redact common sensitive fields
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#the_replacer_parameter
function redactedReplacer(key, val) {
switch (key) {
case "expand":
case "password":
case "passwordConfirm":
case "tokenKey":
return undefined;
}
return record;
return val;
}
function downloadJSON(record) {
record = redacted(record);
app.utils.downloadJSON(record, record.collectionName + "_" + record.id + ".json");
const pojo = JSON.parse(JSON.stringify(record, redactedReplacer));
app.utils.downloadJSON(pojo, record.collectionName + "_" + record.id + ".json");
}
function copyJSON(record) {
record = redacted(record);
app.utils.copyToClipboard(JSON.stringify(record, null, 2));
app.utils.copyToClipboard(JSON.stringify(record, redactedReplacer, 2));
app.toasts.success("Record copied to clipboard!");
}
@@ -73,7 +72,7 @@ function serializeRecord(record) {
return "";
}
return JSON.stringify(redacted(record));
return JSON.stringify(record, redactedReplacer);
}
const TAB_MAIN = "main";
@@ -253,7 +252,11 @@ function recordUpsertModal(collection, rawRecord, modalSettings) {
Object.assign(data.record, JSON.parse(JSON.stringify(record)));
data.isLoading = false;
initDraftWatcher();
// schedule a macro task to allow fields to populate their reactive values
setTimeout(() => {
initDraftWatcher();
}, 0);
} catch (err) {
if (!err?.isAbort) {
app.checkApiError(err);
@@ -263,24 +266,32 @@ function recordUpsertModal(collection, rawRecord, modalSettings) {
}
}
function deleteInternalKeys(record) {
for (let key in record) {
if (key.startsWith("@@")) {
delete record[key];
}
}
}
async function exportPayload() {
const payload = {};
// shallow copy of the record fields
for (const prop in data.record) {
for (const key in data.record) {
// skip expand and internal dynamic enumerable props
if (prop == "expand" || prop.startsWith("@@")) {
if (key == "expand" || key.startsWith("@@")) {
continue;
}
let val = data.record[prop]?.__raw || data.record[prop];
let val = data.record[key]?.__raw || data.record[key];
// normalize undefined values
if (typeof val == "undefined") {
val = null;
}
payload[prop] = val;
payload[key] = val;
}
// apply fields save normalization funcs
@@ -331,6 +342,8 @@ function recordUpsertModal(collection, rawRecord, modalSettings) {
// extend, not overwrite, to prevent reseting the reference passed down to the inputs
Object.assign(data.originalRecord, structuredClone(record));
Object.assign(data.record, structuredClone(record));
deleteInternalKeys(data.originalRecord);
deleteInternalKeys(data.record);
}
modalSettings.onsave?.(record, isNew);