merge v0.23.0-rc changes

This commit is contained in:
Gani Georgiev
2024-09-29 19:23:19 +03:00
parent ad92992324
commit 844f18cac3
753 changed files with 85141 additions and 63396 deletions
@@ -1,79 +1,65 @@
<script>
import { slide } from "svelte/transition";
import CommonHelper from "@/utils/CommonHelper";
import tooltip from "@/actions/tooltip";
import { confirm } from "@/stores/confirmation";
import { removeError } from "@/stores/errors";
import Field from "@/components/base/Field.svelte";
import SecretGeneratorButton from "@/components/base/SecretGeneratorButton.svelte";
import { confirm } from "@/stores/confirmation";
import { removeError } from "@/stores/errors";
import CommonHelper from "@/utils/CommonHelper";
import { slide } from "svelte/transition";
export let record;
export let collection;
export let isNew = !record?.id;
let originalUsername = record.username || null;
$: isSuperusers = collection?.name == "_superusers";
$: emailField = collection?.fields?.find((f) => f.name == "email") || {};
$: passwordField = collection?.fields?.find((f) => f.name == "password") || {};
let changePasswordToggle = false;
$: if (!record.username && record.username !== null) {
record.username = null;
}
$: if (!changePasswordToggle) {
record.password = null;
record.passwordConfirm = null;
record.password = undefined;
record.passwordConfirm = undefined;
removeError("password");
removeError("passwordConfirm");
}
</script>
<div class="grid m-b-base">
<div class="col-lg-6">
<Field class="form-field {!isNew ? 'required' : ''}" name="username" let:uniqueId>
<label for={uniqueId}>
<i class={CommonHelper.getFieldTypeIcon("user")} />
<span class="txt">Username</span>
</label>
<input
type="text"
requried={!isNew}
placeholder={isNew ? "Leave empty to auto generate..." : originalUsername}
id={uniqueId}
bind:value={record.username}
/>
</Field>
</div>
<div class="col-lg-6">
<Field
class="form-field {collection.options?.requireEmail ? 'required' : ''}"
name="email"
let:uniqueId
>
<div class="col-lg-12">
<Field class="form-field {emailField?.required ? 'required' : ''}" name="email" let:uniqueId>
<label for={uniqueId}>
<i class={CommonHelper.getFieldTypeIcon("email")} />
<span class="txt">Email</span>
<span class="txt">email</span>
</label>
<div class="form-field-addon email-visibility-addon">
<button
type="button"
class="btn btn-sm btn-transparent {record.emailVisibility ? 'btn-success' : 'btn-hint'}"
use:tooltip={{
text: "Make email public or private",
position: "top-right",
}}
on:click|preventDefault={() => (record.emailVisibility = !record.emailVisibility)}
>
<span class="txt">Public: {record.emailVisibility ? "On" : "Off"}</span>
</button>
</div>
{#if !isSuperusers}
<div class="form-field-addon email-visibility-addon">
<button
type="button"
class="btn btn-sm btn-transparent {record.emailVisibility
? 'btn-success'
: 'btn-hint'}"
use:tooltip={{
text: "Make email public or private",
position: "top-right",
}}
on:click|preventDefault={() => (record.emailVisibility = !record.emailVisibility)}
>
<span class="txt">Public: {record.emailVisibility ? "On" : "Off"}</span>
</button>
</div>
{/if}
<!-- svelte-ignore a11y-autofocus -->
<input
type="email"
autofocus={isNew}
autocomplete="off"
id={uniqueId}
required={collection.options?.requireEmail}
required={emailField.required}
bind:value={record.email}
/>
</Field>
@@ -104,9 +90,7 @@
bind:value={record.password}
/>
<div class="form-field-addon">
<SecretGeneratorButton
length={Math.max(15, collection?.options?.minPasswordLength || 0)}
/>
<SecretGeneratorButton length={Math.max(15, passwordField.min || 0)} />
</div>
</Field>
</div>
@@ -130,28 +114,30 @@
{/if}
</div>
<div class="col-lg-12">
<Field class="form-field form-field-toggle" name="verified" let:uniqueId>
<input
type="checkbox"
id={uniqueId}
bind:checked={record.verified}
on:change|preventDefault={(e) => {
if (isNew) {
return; // no confirmation required
}
confirm(
`Do you really want to manually change the verified account state?`,
() => {},
() => {
record.verified = !e.target.checked;
},
);
}}
/>
<label for={uniqueId}>Verified</label>
</Field>
</div>
{#if !isSuperusers}
<div class="col-lg-12">
<Field class="form-field form-field-toggle" name="verified" let:uniqueId>
<input
type="checkbox"
id={uniqueId}
bind:checked={record.verified}
on:change|preventDefault={(e) => {
if (isNew) {
return; // no confirmation required
}
confirm(
`Do you really want to manually change the verified account state?`,
() => {},
() => {
record.verified = !e.target.checked;
},
);
}}
/>
<label for={uniqueId}>Verified</label>
</Field>
</div>
{/if}
</div>
<style>
@@ -1,5 +1,6 @@
<script>
import Field from "@/components/base/Field.svelte";
import FieldLabel from "@/components/records/fields/FieldLabel.svelte";
export let field;
export let value = false;
@@ -7,5 +8,5 @@
<Field class="form-field form-field-toggle {field.required ? 'required' : ''}" name={field.name} let:uniqueId>
<input type="checkbox" id={uniqueId} bind:checked={value} />
<label for={uniqueId}>{field.name}</label>
<FieldLabel {uniqueId} {field} icon={false} />
</Field>
@@ -1,8 +1,9 @@
<script>
import Flatpickr from "svelte-flatpickr";
import tooltip from "@/actions/tooltip";
import CommonHelper from "@/utils/CommonHelper";
import Field from "@/components/base/Field.svelte";
import tooltip from "@/actions/tooltip";
import FieldLabel from "@/components/records/fields/FieldLabel.svelte";
export let field;
export let value = undefined;
@@ -33,10 +34,7 @@
</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} (UTC)</span>
</label>
<FieldLabel {uniqueId} {field} />
{#if value && !field.required}
<div class="form-field-addon">
@@ -1,10 +1,11 @@
<script>
import { onMount } from "svelte";
import CommonHelper from "@/utils/CommonHelper";
import ApiClient from "@/utils/ApiClient";
import CommonHelper from "@/utils/CommonHelper";
import Field from "@/components/base/Field.svelte";
import TinyMCE from "@/components/base/TinyMCE.svelte";
import RecordFilePicker from "@/components/records/RecordFilePicker.svelte";
import FieldLabel from "@/components/records/fields/FieldLabel.svelte";
export let field;
export let value = "";
@@ -15,7 +16,7 @@
let mountedTimeoutId = null;
$: conf = Object.assign(CommonHelper.defaultEditorOptions(), {
convert_urls: field.options?.convertUrls,
convert_urls: field.convertURLs,
relative_urls: false,
});
@@ -42,10 +43,8 @@
</script>
<Field class="form-field form-field-editor {field.required ? 'required' : ''}" name={field.name} let:uniqueId>
<label for={uniqueId}>
<i class={CommonHelper.getFieldTypeIcon(field.type)} />
<span class="txt">{field.name}</span>
</label>
<FieldLabel {uniqueId} {field} />
{#if mounted}
<TinyMCE
id={uniqueId}
@@ -71,9 +70,9 @@
editor?.execCommand(
"InsertImage",
false,
ApiClient.files.getUrl(e.detail.record, e.detail.name, {
ApiClient.files.getURL(e.detail.record, e.detail.name, {
thumb: e.detail.size,
})
}),
);
}}
/>
@@ -1,15 +1,13 @@
<script>
import CommonHelper from "@/utils/CommonHelper";
import Field from "@/components/base/Field.svelte";
import FieldLabel from "@/components/records/fields/FieldLabel.svelte";
export let field;
export let value = undefined;
</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>
</label>
<FieldLabel {uniqueId} {field} />
<input type="email" id={uniqueId} required={field.required} bind:value />
</Field>
@@ -0,0 +1,25 @@
<script>
import CommonHelper from "@/utils/CommonHelper";
export let uniqueId;
export let field;
export let icon = true;
</script>
<label for={uniqueId}>
{#if icon}
{#if field.primaryKey}
<i class={CommonHelper.getFieldTypeIcon("primary")} />
{:else}
<i class={CommonHelper.getFieldTypeIcon(field.type)} />
{/if}
{/if}
<span class="txt">{field.name}</span>
{#if field.hidden}
<small class="label label-sm label-danger">Hidden</small>
{/if}
<slot />
</label>
@@ -1,12 +1,13 @@
<script>
import { onMount } from "svelte";
import tooltip from "@/actions/tooltip";
import ApiClient from "@/utils/ApiClient";
import CommonHelper from "@/utils/CommonHelper";
import tooltip from "@/actions/tooltip";
import Field from "@/components/base/Field.svelte";
import Draggable from "@/components/base/Draggable.svelte";
import Field from "@/components/base/Field.svelte";
import UploadedFilePreview from "@/components/base/UploadedFilePreview.svelte";
import RecordFileThumb from "@/components/records/RecordFileThumb.svelte";
import { onMount } from "svelte";
import FieldLabel from "@/components/records/fields/FieldLabel.svelte";
export let record;
export let field;
@@ -29,7 +30,7 @@
deletedFileNames = CommonHelper.toArray(deletedFileNames);
}
$: isMultiple = field.options?.maxSelect > 1;
$: isMultiple = field.maxSelect != 1;
$: if (CommonHelper.isEmpty(value)) {
value = isMultiple ? [] : "";
@@ -39,7 +40,7 @@
$: maxReached =
(valueAsArray.length || uploadedFiles.length) &&
field.options?.maxSelect <= valueAsArray.length + uploadedFiles.length - deletedFileNames.length;
field.maxSelect <= valueAsArray.length + uploadedFiles.length - deletedFileNames.length;
$: if (uploadedFiles !== -1 || deletedFileNames !== -1) {
triggerListChange();
@@ -68,7 +69,7 @@
new CustomEvent("change", {
detail: { value, uploadedFiles, deletedFileNames },
bubbles: true,
})
}),
);
}
@@ -86,7 +87,7 @@
for (const file of files) {
const currentTotal = valueAsArray.length + uploadedFiles.length - deletedFileNames.length;
if (field.options?.maxSelect <= currentTotal) {
if (field.maxSelect <= currentTotal) {
break;
}
@@ -97,7 +98,7 @@
}
onMount(async () => {
fileToken = await ApiClient.getAdminFileToken(record.collectionId);
fileToken = await ApiClient.getSuperuserFileToken(record.collectionId);
});
</script>
@@ -121,10 +122,7 @@
name={field.name}
let:uniqueId
>
<label for={uniqueId}>
<i class={CommonHelper.getFieldTypeIcon(field.type)} />
<span class="txt">{field.name}</span>
</label>
<FieldLabel {uniqueId} {field} />
<div bind:this={filesListElem} class="list">
{#each valueAsArray as filename, i (filename + record.id)}
@@ -145,7 +143,7 @@
<div class="content">
<a
draggable={false}
href={ApiClient.files.getUrl(record, filename, { token: fileToken })}
href={ApiClient.files.getURL(record, filename, { token: fileToken })}
class="txt-ellipsis {isDeleted
? 'txt-strikethrough txt-hint'
: 'link-primary'}"
@@ -215,7 +213,7 @@
bind:this={fileInput}
type="file"
class="hidden"
accept={field.options?.mimeTypes?.join(",") || null}
accept={field.mimeTypes?.join(",") || null}
multiple={isMultiple}
on:change={() => {
for (let file of fileInput.files) {
@@ -1,8 +1,8 @@
<script>
import { onMount } from "svelte";
import tooltip from "@/actions/tooltip";
import CommonHelper from "@/utils/CommonHelper";
import Field from "@/components/base/Field.svelte";
import FieldLabel from "@/components/records/fields/FieldLabel.svelte";
export let field;
export let value = undefined;
@@ -45,9 +45,7 @@
</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>
<FieldLabel {uniqueId} {field}>
<span
class="json-state"
use:tooltip={{ position: "left", text: isValid ? "Valid JSON" : "Invalid JSON" }}
@@ -58,7 +56,8 @@
<i class="ri-error-warning-fill txt-danger" />
{/if}
</span>
</label>
</FieldLabel>
{#if editorComponent}
<svelte:component
this={editorComponent}
@@ -1,22 +1,20 @@
<script>
import CommonHelper from "@/utils/CommonHelper";
import Field from "@/components/base/Field.svelte";
import FieldLabel from "@/components/records/fields/FieldLabel.svelte";
export let field;
export let value = undefined;
</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>
</label>
<FieldLabel {uniqueId} {field} />
<input
type="number"
id={uniqueId}
required={field.required}
min={field.options?.min}
max={field.options?.max}
min={field.min}
max={field.max}
step="any"
bind:value
/>
@@ -0,0 +1,13 @@
<script>
import Field from "@/components/base/Field.svelte";
import FieldLabel from "@/components/records/fields/FieldLabel.svelte";
export let field;
export let value = undefined;
</script>
<Field class="form-field {field.required ? 'required' : ''}" name={field.name} let:uniqueId>
<FieldLabel {uniqueId} {field} />
<input type="password" id={uniqueId} autocomplete="new-password" required={field.required} bind:value />
</Field>
@@ -1,12 +1,14 @@
<script>
import { onDestroy } from "svelte";
import CommonHelper from "@/utils/CommonHelper";
import ApiClient from "@/utils/ApiClient";
import tooltip from "@/actions/tooltip";
import Field from "@/components/base/Field.svelte";
import { collections } from "@/stores/collections";
import Draggable from "@/components/base/Draggable.svelte";
import RecordsPicker from "@/components/records/RecordsPicker.svelte";
import Field from "@/components/base/Field.svelte";
import RecordInfo from "@/components/records/RecordInfo.svelte";
import RecordsPicker from "@/components/records/RecordsPicker.svelte";
import ApiClient from "@/utils/ApiClient";
import CommonHelper from "@/utils/CommonHelper";
import FieldLabel from "@/components/records/fields/FieldLabel.svelte";
const batchSize = 100;
@@ -20,7 +22,7 @@
let loadTimeoutId;
let invalidIds = [];
$: isMultiple = field.options?.maxSelect != 1;
$: isMultiple = field.maxSelect != 1;
$: if (typeof value != "undefined") {
fieldRef?.changed();
@@ -55,13 +57,27 @@
list = [];
invalidIds = [];
if (!field?.options?.collectionId || !ids.length) {
if (!field?.collectionId || !ids.length) {
isLoading = false;
return;
}
isLoading = true;
let expand = "";
const presentableRelFields = $collections
.find((c) => c.id == field.collectionId)
?.fields?.filter((f) => !f.hidden && f.presentable && f.type == "relation");
for (const field of presentableRelFields) {
const expandItem = CommonHelper.getExpandPresentableRelField(field, $collections, 2);
if (expandItem) {
if (expand != "") {
expand += ",";
}
expand += expandItem;
}
}
// batch load all selected records to avoid parser stack overflow errors
const filterIds = ids.slice();
const loadPromises = [];
@@ -72,9 +88,10 @@
}
loadPromises.push(
ApiClient.collection(field?.options?.collectionId).getFullList(batchSize, {
ApiClient.collection(field.collectionId).getFullList(batchSize, {
filter: filters.join("||"),
fields: "*:excerpt(200)",
expand: expand,
requestKey: null,
}),
);
@@ -134,9 +151,7 @@
name={field.name}
let:uniqueId
>
<label for={uniqueId}>
<i class={CommonHelper.getFieldTypeIcon(field.type)} />
<span class="txt">{field.name}</span>
<FieldLabel {uniqueId} {field}>
{#if invalidIds.length}
<i
class="ri-error-warning-line link-hint m-l-auto flex-order-10"
@@ -148,7 +163,7 @@
}}
/>
{/if}
</label>
</FieldLabel>
<div class="list">
<div class="relations-list">
@@ -1,37 +1,40 @@
<script>
import CommonHelper from "@/utils/CommonHelper";
import Select from "@/components/base/Select.svelte";
import Field from "@/components/base/Field.svelte";
import Select from "@/components/base/Select.svelte";
import FieldLabel from "@/components/records/fields/FieldLabel.svelte";
export let field;
export let value = undefined;
$: isMultiple = field.options?.maxSelect > 1;
$: isMultiple = field.maxSelect != 1;
$: if (typeof value === "undefined") {
value = isMultiple ? [] : "";
}
$: if (isMultiple && Array.isArray(value) && value.length > field.options.maxSelect) {
value = value.slice(value.length - field.options.maxSelect);
$: maxSelect = field.maxSelect || field.values.length;
$: if (isMultiple && Array.isArray(value)) {
value = value.filter((v) => field.values.includes(v));
if (value.length > maxSelect) {
value = value.slice(value.length - maxSelect);
}
}
</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>
</label>
<FieldLabel {uniqueId} {field} />
<Select
id={uniqueId}
toggle={!field.required || isMultiple}
multiple={isMultiple}
closable={!isMultiple || value?.length >= field.options?.maxSelect}
items={field.options?.values}
searchable={field.options?.values?.length > 5}
closable={!isMultiple || value?.length >= field.maxSelect}
items={field.values}
searchable={field.values?.length > 5}
bind:selected={value}
/>
{#if field.options?.maxSelect > 1}
<div class="help-block">Select up to {field.options.maxSelect} items.</div>
{#if field.maxSelect != 1}
<div class="help-block">Select up to {maxSelect} items.</div>
{/if}
</Field>
@@ -2,15 +2,24 @@
import CommonHelper from "@/utils/CommonHelper";
import Field from "@/components/base/Field.svelte";
import AutoExpandTextarea from "@/components/base/AutoExpandTextarea.svelte";
import FieldLabel from "@/components/records/fields/FieldLabel.svelte";
export let original;
export let field;
export let value = undefined;
$: hasAutogenerate = !CommonHelper.isEmpty(field.autogeneratePattern) && !original?.id;
$: isRequired = field.required && !hasAutogenerate;
</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>
</label>
<AutoExpandTextarea id={uniqueId} required={field.required} bind:value />
<Field class="form-field {isRequired ? 'required' : ''}" name={field.name} let:uniqueId>
<FieldLabel {uniqueId} {field} />
<AutoExpandTextarea
id={uniqueId}
required={isRequired}
placeholder={hasAutogenerate ? "Leave empty to autogenerate..." : ""}
bind:value
/>
</Field>
@@ -1,15 +1,13 @@
<script>
import CommonHelper from "@/utils/CommonHelper";
import Field from "@/components/base/Field.svelte";
import FieldLabel from "@/components/records/fields/FieldLabel.svelte";
export let field;
export let value = undefined;
</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>
</label>
<FieldLabel {uniqueId} {field} />
<input type="url" id={uniqueId} required={field.required} bind:value />
</Field>