[#3191] added client-side validation and syntax highlight for the json field
This commit is contained in:
@@ -53,6 +53,7 @@
|
||||
closeBracketsKeymap,
|
||||
} from "@codemirror/autocomplete";
|
||||
import { html as htmlLang } from "@codemirror/lang-html";
|
||||
import { json as jsonLang } from "@codemirror/lang-json";
|
||||
import { sql, SQLDialect } from "@codemirror/lang-sql";
|
||||
import { javascript as javascriptLang } from "@codemirror/lang-javascript";
|
||||
// ---
|
||||
@@ -160,6 +161,8 @@
|
||||
switch (language) {
|
||||
case "html":
|
||||
return htmlLang();
|
||||
case "json":
|
||||
return jsonLang();
|
||||
case "sql-create-index":
|
||||
return sql({
|
||||
// lightweight sql dialect with mostly SELECT statements keywords
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { createEventDispatcher, tick } from "svelte";
|
||||
import { slide } from "svelte/transition";
|
||||
import CommonHelper from "@/utils/CommonHelper";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
import ApiClient from "@/utils/ApiClient";
|
||||
import tooltip from "@/actions/tooltip";
|
||||
import { setErrors } from "@/stores/errors";
|
||||
@@ -174,43 +175,40 @@
|
||||
window.localStorage.removeItem(draftKey());
|
||||
}
|
||||
|
||||
function save(hidePanel = true) {
|
||||
async function save(hidePanel = true) {
|
||||
if (isSaving || !canSave || !collection?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
isSaving = true;
|
||||
|
||||
const data = exportFormData();
|
||||
try {
|
||||
const data = exportFormData();
|
||||
|
||||
let request;
|
||||
if (isNew) {
|
||||
request = ApiClient.collection(collection.id).create(data);
|
||||
} else {
|
||||
request = ApiClient.collection(collection.id).update(record.id, data);
|
||||
let result;
|
||||
if (isNew) {
|
||||
result = await ApiClient.collection(collection.id).create(data);
|
||||
} else {
|
||||
result = await ApiClient.collection(collection.id).update(record.id, data);
|
||||
}
|
||||
|
||||
addSuccessToast(isNew ? "Successfully created record." : "Successfully updated record.");
|
||||
|
||||
deleteDraft();
|
||||
|
||||
if (hidePanel) {
|
||||
confirmClose = false;
|
||||
hide();
|
||||
} else {
|
||||
replaceOriginal(result);
|
||||
}
|
||||
|
||||
dispatch("save", result);
|
||||
} catch (err) {
|
||||
ApiClient.error(err);
|
||||
}
|
||||
|
||||
request
|
||||
.then((result) => {
|
||||
addSuccessToast(isNew ? "Successfully created record." : "Successfully updated record.");
|
||||
|
||||
deleteDraft();
|
||||
|
||||
if (hidePanel) {
|
||||
confirmClose = false;
|
||||
hide();
|
||||
} else {
|
||||
replaceOriginal(result);
|
||||
}
|
||||
|
||||
dispatch("save", result);
|
||||
})
|
||||
.catch((err) => {
|
||||
ApiClient.error(err);
|
||||
})
|
||||
.finally(() => {
|
||||
isSaving = false;
|
||||
});
|
||||
isSaving = false;
|
||||
}
|
||||
|
||||
function deleteConfirm() {
|
||||
@@ -240,8 +238,14 @@
|
||||
id: data.id,
|
||||
};
|
||||
|
||||
const jsonFields = {};
|
||||
|
||||
for (const field of collection?.schema || []) {
|
||||
exportableFields[field.name] = true;
|
||||
|
||||
if (field.type == "json") {
|
||||
jsonFields[field.name] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isAuthCollection) {
|
||||
@@ -265,6 +269,26 @@
|
||||
data[key] = null;
|
||||
}
|
||||
|
||||
// "validate" json fields
|
||||
if (jsonFields[key] && data[key] !== "") {
|
||||
try {
|
||||
JSON.parse(data[key]);
|
||||
} catch (err) {
|
||||
const fieldErr = {};
|
||||
fieldErr[key] = {
|
||||
code: "invalid_json",
|
||||
message: err.toString(),
|
||||
};
|
||||
// emulate server error
|
||||
throw new ClientResponseError({
|
||||
status: 400,
|
||||
response: {
|
||||
data: fieldErr,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
CommonHelper.addValueToFormData(formData, key, data[key]);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,31 +1,80 @@
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
import tooltip from "@/actions/tooltip";
|
||||
import CommonHelper from "@/utils/CommonHelper";
|
||||
import Field from "@/components/base/Field.svelte";
|
||||
|
||||
export let field;
|
||||
export let value = undefined;
|
||||
|
||||
let serialized = JSON.stringify(typeof value === "undefined" ? null : value, null, 2);
|
||||
let editorComponent;
|
||||
|
||||
let serialized = serialize(value);
|
||||
|
||||
$: if (value !== serialized?.trim()) {
|
||||
serialized = JSON.stringify(typeof value === "undefined" ? null : value, null, 2);
|
||||
serialized = serialize(value);
|
||||
value = serialized;
|
||||
}
|
||||
|
||||
$: isValid = isValidJson(serialized);
|
||||
|
||||
function serialize(val) {
|
||||
return JSON.stringify(typeof val === "undefined" ? null : val, null, 2);
|
||||
}
|
||||
|
||||
function isValidJson(val) {
|
||||
try {
|
||||
JSON.parse(val === "" ? null : val);
|
||||
return true;
|
||||
} catch (_) {}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
editorComponent = (await import("@/components/base/CodeEditor.svelte")).default;
|
||||
} catch (err) {
|
||||
console.warn(err);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<Field class="form-field {field.required ? 'required' : ''}" name={field.name} let:uniqueId>
|
||||
<label for={uniqueId}>
|
||||
<i class={CommonHelper.getFieldTypeIcon(field.type)} />
|
||||
<span class="txt">{field.name}</span>
|
||||
<span
|
||||
class="json-state"
|
||||
use:tooltip={{ position: "left", text: isValid ? "Valid JSON" : "Invalid JSON" }}
|
||||
>
|
||||
{#if isValid}
|
||||
<i class="ri-checkbox-circle-fill txt-success" />
|
||||
{:else}
|
||||
<i class="ri-error-warning-fill txt-danger" />
|
||||
{/if}
|
||||
</span>
|
||||
</label>
|
||||
<textarea
|
||||
id={uniqueId}
|
||||
class="txt-mono"
|
||||
required={field.required}
|
||||
value={serialized}
|
||||
on:input={(e) => {
|
||||
serialized = e.target.value;
|
||||
value = e.target.value.trim(); // trim the submitted value
|
||||
}}
|
||||
/>
|
||||
{#if editorComponent}
|
||||
<svelte:component
|
||||
this={editorComponent}
|
||||
id={uniqueId}
|
||||
maxHeight="500"
|
||||
language="json"
|
||||
value={serialized}
|
||||
on:change={(e) => {
|
||||
serialized = e.detail;
|
||||
value = serialized.trim(); // trim the submitted value
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<input type="text" class="txt-mono" value="Loading..." disabled />
|
||||
{/if}
|
||||
</Field>
|
||||
|
||||
<style>
|
||||
.json-state {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user