@nextlyhq/plugin-form-builder 0.0.2-alpha.73 → 0.0.2-alpha.74
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/index.d.ts +7 -0
- package/dist/admin/index.js +61 -9
- package/dist/admin/index.js.map +1 -1
- package/dist/index.js +8 -5
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
package/dist/admin/index.d.ts
CHANGED
|
@@ -18,6 +18,11 @@ interface FormBuilderViewProps {
|
|
|
18
18
|
fields?: unknown[];
|
|
19
19
|
settings?: Record<string, unknown>;
|
|
20
20
|
notifications?: unknown[];
|
|
21
|
+
/**
|
|
22
|
+
* The form's version as read, which a save sends back so one made from an
|
|
23
|
+
* older version is refused rather than replacing what someone saved since.
|
|
24
|
+
*/
|
|
25
|
+
_rev?: string;
|
|
21
26
|
};
|
|
22
27
|
onSave?: (data: unknown) => void;
|
|
23
28
|
onSuccess?: (entry?: Record<string, unknown>) => void;
|
|
@@ -49,6 +54,8 @@ interface SubmissionRow {
|
|
|
49
54
|
submittedAt?: string | null;
|
|
50
55
|
editedAt?: string | null;
|
|
51
56
|
editedBy?: string | null;
|
|
57
|
+
/** The submission's version as the list read it; see `submissionPatch`. */
|
|
58
|
+
_rev?: string;
|
|
52
59
|
}
|
|
53
60
|
interface FormOption {
|
|
54
61
|
id: string;
|
package/dist/admin/index.js
CHANGED
|
@@ -388,6 +388,19 @@ function SubmissionSheet({ submission, form, canUpdate, onClose, onPrev, onNext,
|
|
|
388
388
|
*
|
|
389
389
|
* @module admin/components/submissions/SubmissionsView
|
|
390
390
|
*/
|
|
391
|
+
/**
|
|
392
|
+
* What a change to a submission sends: the change, with the version its row
|
|
393
|
+
* had when the list read it, so a change made from an older copy is refused
|
|
394
|
+
* rather than replacing what someone saved since.
|
|
395
|
+
*/
|
|
396
|
+
function submissionPatch(row, changes) {
|
|
397
|
+
return typeof row?._rev === "string" ? {
|
|
398
|
+
...changes,
|
|
399
|
+
_rev: row._rev
|
|
400
|
+
} : changes;
|
|
401
|
+
}
|
|
402
|
+
/** What a refused change says when someone saved the submission since. */
|
|
403
|
+
const SUBMISSION_CHANGED_SINCE = "Someone changed this submission since the list loaded. The list now shows their change; make yours again.";
|
|
391
404
|
const STATUS_TABS = [
|
|
392
405
|
{
|
|
393
406
|
value: "all",
|
|
@@ -469,7 +482,7 @@ function SubmissionsView({ collectionSlug }) {
|
|
|
469
482
|
const allForms = [];
|
|
470
483
|
const formsPageSize = 200;
|
|
471
484
|
for (let formsPage = 1;; formsPage += 1) {
|
|
472
|
-
const formsRes = await fetch(`/admin/api/collections/${resolved.forms}/entries?
|
|
485
|
+
const formsRes = await fetch(`/admin/api/collections/${resolved.forms}/entries?limit=${formsPageSize}&page=${formsPage}`, { credentials: "include" });
|
|
473
486
|
if (!formsRes.ok) break;
|
|
474
487
|
const items = (await formsRes.json()).items ?? [];
|
|
475
488
|
allForms.push(...items.map((item) => ({
|
|
@@ -513,7 +526,7 @@ function SubmissionsView({ collectionSlug }) {
|
|
|
513
526
|
else where.status = { equals: statusTab };
|
|
514
527
|
const params = new URLSearchParams({
|
|
515
528
|
page: String(page),
|
|
516
|
-
|
|
529
|
+
limit: String(PAGE_SIZE$1),
|
|
517
530
|
sortBy: "submittedAt",
|
|
518
531
|
sortOrder: "desc",
|
|
519
532
|
where: JSON.stringify(where)
|
|
@@ -592,14 +605,22 @@ function SubmissionsView({ collectionSlug }) {
|
|
|
592
605
|
formNameById,
|
|
593
606
|
hiddenFieldColumns
|
|
594
607
|
]);
|
|
608
|
+
const rowsNow = useRef(rows);
|
|
609
|
+
rowsNow.current = rows;
|
|
595
610
|
const patchSubmission = useCallback(async (id, body) => {
|
|
596
611
|
const res = await fetch(`/admin/api/collections/${submissionsSlug}/entries/${id}`, {
|
|
597
612
|
method: "PATCH",
|
|
598
613
|
headers: { "Content-Type": "application/json" },
|
|
599
614
|
credentials: "include",
|
|
600
|
-
body: JSON.stringify(body)
|
|
615
|
+
body: JSON.stringify(submissionPatch(rowsNow.current.find((row) => row.id === id), body))
|
|
601
616
|
});
|
|
602
|
-
if (!res.ok)
|
|
617
|
+
if (!res.ok) {
|
|
618
|
+
if ((await res.json().catch(() => null))?.error?.code === "STALE_VERSION") {
|
|
619
|
+
await fetchRows();
|
|
620
|
+
throw new Error(SUBMISSION_CHANGED_SINCE);
|
|
621
|
+
}
|
|
622
|
+
throw new Error(`Update failed (${res.status})`);
|
|
623
|
+
}
|
|
603
624
|
await fetchRows();
|
|
604
625
|
}, [submissionsSlug, fetchRows]);
|
|
605
626
|
const deleteSubmission = useCallback(async (id) => {
|
|
@@ -619,7 +640,7 @@ function SubmissionsView({ collectionSlug }) {
|
|
|
619
640
|
onSelect: (target) => void patchSubmission(target.id, {
|
|
620
641
|
status: "new",
|
|
621
642
|
spamReason: null
|
|
622
|
-
})
|
|
643
|
+
}).catch((reason) => setError(reason instanceof Error ? reason.message : "Update failed."))
|
|
623
644
|
});
|
|
624
645
|
if (canDelete) actions.push({
|
|
625
646
|
id: "delete",
|
|
@@ -4407,10 +4428,32 @@ function FormPreview({ fields, formData }) {
|
|
|
4407
4428
|
* @module admin/FormBuilderView
|
|
4408
4429
|
* @since 0.1.0
|
|
4409
4430
|
*/
|
|
4431
|
+
/**
|
|
4432
|
+
* The key a form's version travels under, as the admin reads and sends it.
|
|
4433
|
+
* Spelled here because the plugin reaches the admin only through the SDK,
|
|
4434
|
+
* which does not publish it.
|
|
4435
|
+
*/
|
|
4436
|
+
const DOCUMENT_VERSION_KEY = "_rev";
|
|
4437
|
+
/**
|
|
4438
|
+
* What a save sends: an existing form's save names the version the editor's
|
|
4439
|
+
* copy started from, where it has one, so a save from an older copy is refused.
|
|
4440
|
+
*/
|
|
4441
|
+
function saveBody(saveData, isCreating, startedFrom) {
|
|
4442
|
+
return isCreating || startedFrom === void 0 ? saveData : {
|
|
4443
|
+
...saveData,
|
|
4444
|
+
[DOCUMENT_VERSION_KEY]: startedFrom
|
|
4445
|
+
};
|
|
4446
|
+
}
|
|
4447
|
+
/** The version a save answered, or the copy's own where it names none. */
|
|
4448
|
+
function versionAfterSave(saved, previous) {
|
|
4449
|
+
const answered = saved[DOCUMENT_VERSION_KEY];
|
|
4450
|
+
return typeof answered === "string" ? answered : previous;
|
|
4451
|
+
}
|
|
4410
4452
|
function slugify(text) {
|
|
4411
4453
|
return text.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
4412
4454
|
}
|
|
4413
|
-
function FormBuilderViewInner({ isCreating = false, collectionSlug = "forms", entryId, onSave, onSuccess, onCancel, documentLock }) {
|
|
4455
|
+
function FormBuilderViewInner({ isCreating = false, collectionSlug = "forms", entryId, onSave, onSuccess, onCancel, documentLock, version }) {
|
|
4456
|
+
const startedFrom = useRef(version);
|
|
4414
4457
|
const { fields, activeTab, isDirty, formData, settings, notifications, setActiveTab, addField, selectField, seedNotifications, updateFormData, markAsSaved } = useFormBuilder();
|
|
4415
4458
|
const queryClient = useQueryClient();
|
|
4416
4459
|
const [isSaving, setIsSaving] = useState(false);
|
|
@@ -4497,11 +4540,12 @@ function FormBuilderViewInner({ isCreating = false, collectionSlug = "forms", en
|
|
|
4497
4540
|
method: isCreating ? "POST" : "PATCH",
|
|
4498
4541
|
headers: { "Content-Type": "application/json" },
|
|
4499
4542
|
credentials: "include",
|
|
4500
|
-
body: JSON.stringify(saveData)
|
|
4543
|
+
body: JSON.stringify(saveBody(saveData, isCreating, startedFrom.current))
|
|
4501
4544
|
});
|
|
4502
4545
|
if (!response.ok) throw new Error(saveErrorMessage(await response.json().catch(() => ({})), response.statusText));
|
|
4503
4546
|
const result = await response.json();
|
|
4504
4547
|
const savedEntry = result.item ?? result;
|
|
4548
|
+
startedFrom.current = versionAfterSave(savedEntry, startedFrom.current);
|
|
4505
4549
|
queryClient.invalidateQueries({ queryKey: [
|
|
4506
4550
|
"entries",
|
|
4507
4551
|
"list",
|
|
@@ -4744,6 +4788,10 @@ function FormBuilderViewInner({ isCreating = false, collectionSlug = "forms", en
|
|
|
4744
4788
|
*/
|
|
4745
4789
|
function saveErrorMessage(body, statusText) {
|
|
4746
4790
|
const error = body?.error;
|
|
4791
|
+
if (error?.code === "STALE_VERSION") {
|
|
4792
|
+
const name = error.data?.changedBy?.name;
|
|
4793
|
+
return `${typeof name === "string" && name.trim() !== "" ? name.trim() : "Someone"} changed this form since you opened it. Reload to see their change; your edits are still here, not saved.`;
|
|
4794
|
+
}
|
|
4747
4795
|
return (error?.data?.errors ?? []).map((issue) => issue.message).filter((message) => Boolean(message)).join(" ") || error?.message || `Failed to save form: ${statusText}`;
|
|
4748
4796
|
}
|
|
4749
4797
|
function FormBuilderView({ id, entryId, collection, collectionSlug, isCreating, initialData, onSave, onSuccess, onCancel, documentLock }) {
|
|
@@ -4771,7 +4819,8 @@ function FormBuilderView({ id, entryId, collection, collectionSlug, isCreating,
|
|
|
4771
4819
|
onSave,
|
|
4772
4820
|
onSuccess,
|
|
4773
4821
|
onCancel,
|
|
4774
|
-
documentLock
|
|
4822
|
+
documentLock,
|
|
4823
|
+
version: initialData?._rev
|
|
4775
4824
|
})
|
|
4776
4825
|
}, providerKey);
|
|
4777
4826
|
}
|
|
@@ -4808,7 +4857,10 @@ function markReadRequest(collection, id) {
|
|
|
4808
4857
|
id: { equals: id },
|
|
4809
4858
|
status: { equals: "new" }
|
|
4810
4859
|
},
|
|
4811
|
-
data: {
|
|
4860
|
+
data: {
|
|
4861
|
+
status: READ_STATUS,
|
|
4862
|
+
_overwrite: true
|
|
4863
|
+
}
|
|
4812
4864
|
})
|
|
4813
4865
|
}
|
|
4814
4866
|
};
|