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

File diff suppressed because one or more lines are too long

81
ui/dist/assets/index-DijNBRV3.js vendored Normal 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);