@kyro-cms/admin 0.9.1 → 0.9.2
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/index.cjs +1196 -1727
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +891 -1422
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/ActionBar.tsx +25 -174
- package/src/components/Admin.tsx +1 -3
- package/src/components/AuditLogsPage.tsx +2 -13
- package/src/components/AutoForm.tsx +160 -265
- package/src/components/DetailView.tsx +38 -66
- package/src/components/FieldRenderer.tsx +1 -1
- package/src/components/ListView.tsx +26 -198
- package/src/components/MediaGallery.tsx +117 -175
- package/src/components/RestPlayground.tsx +54 -47
- package/src/components/fields/BlocksField.tsx +8 -10
- package/src/components/fields/RelationshipBlockField.tsx +2 -3
- package/src/components/fields/RelationshipField.tsx +2 -3
- package/src/components/fix_imports.cjs +23 -0
- package/src/components/fix_imports2.cjs +19 -0
- package/src/components/replace_svgs.cjs +63 -0
- package/src/components/ui/Dropdown.tsx +7 -2
- package/src/components/ui/Modal.tsx +24 -27
- package/src/components/ui/PromptModal.tsx +2 -10
- package/src/components/ui/SlidePanel.tsx +2 -10
- package/src/components/ui/SplitButton.tsx +107 -0
- package/src/components/ui/Toaster.tsx +0 -1
- package/src/components/ui/icons.tsx +1 -0
- package/src/components/users/UsersList.tsx +8 -85
- package/src/hooks/useAutoFormState.ts +89 -161
- package/src/hooks/useQueue.ts +60 -0
- package/src/layouts/AdminLayout.astro +22 -2
- package/src/layouts/AuthLayout.astro +66 -18
- package/src/lib/autoform-store.ts +6 -2
- package/src/lib/globals.ts +5 -3
- package/src/pages/auth/register.astro +5 -2
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ChevronRight, Check, ExternalLink, X } from "./ui/icons";
|
|
1
2
|
import { useState, useRef, useEffect } from "react";
|
|
2
3
|
import type {
|
|
3
4
|
CollectionConfig,
|
|
@@ -22,6 +23,7 @@ import { normalizeUploadFields } from "../lib/normalize-upload-fields";
|
|
|
22
23
|
import { useAutoFormStore } from "../lib/autoform-store";
|
|
23
24
|
import { useAutoFormState } from "../hooks/useAutoFormState";
|
|
24
25
|
import { useUIStore, toast } from "../lib/stores";
|
|
26
|
+
import { EmptyState } from "./ui/EmptyState";
|
|
25
27
|
|
|
26
28
|
import { adminPath as ADMIN_BASE, apiPath as API_BASE } from "../lib/paths";
|
|
27
29
|
|
|
@@ -30,6 +32,9 @@ import { ConfirmModal, Modal as UIModal } from "./ui/Modal";
|
|
|
30
32
|
import { ListField } from "./fields/ListField";
|
|
31
33
|
import { RelationshipBlockField } from "./fields/RelationshipBlockField";
|
|
32
34
|
import { FieldRenderer } from "./FieldRenderer";
|
|
35
|
+
import { Dropdown, DropdownItem, DropdownSeparator } from "./ui/Dropdown";
|
|
36
|
+
import { SplitButton } from "./ui/SplitButton";
|
|
37
|
+
import type { SplitButtonStatus } from "./ui/SplitButton";
|
|
33
38
|
import { TabsLayout } from "./fields/TabsLayout";
|
|
34
39
|
import { GroupLayout } from "./fields/GroupLayout";
|
|
35
40
|
import { ArrayLayout } from "./fields/ArrayLayout";
|
|
@@ -80,6 +85,7 @@ export function AutoForm({
|
|
|
80
85
|
lastSavedData,
|
|
81
86
|
hasUnsavedChanges,
|
|
82
87
|
isAutoSaving,
|
|
88
|
+
backgroundProcessing,
|
|
83
89
|
autoSaveStatus,
|
|
84
90
|
lastSavedAt,
|
|
85
91
|
retryCount,
|
|
@@ -116,8 +122,6 @@ export function AutoForm({
|
|
|
116
122
|
setAutoSaveStatus,
|
|
117
123
|
fetchVersions,
|
|
118
124
|
saveDocument,
|
|
119
|
-
publishDocument,
|
|
120
|
-
clearDraftArtifacts,
|
|
121
125
|
autoSaveSkipRef,
|
|
122
126
|
lastAutoSaveTimeRef,
|
|
123
127
|
documentStatus,
|
|
@@ -136,8 +140,16 @@ export function AutoForm({
|
|
|
136
140
|
const menuRef = useRef<HTMLDivElement>(null);
|
|
137
141
|
const scheduleRef = useRef<HTMLDivElement>(null);
|
|
138
142
|
const [showSchedulePicker, setShowSchedulePicker] = useState(false);
|
|
143
|
+
const [localSaveStatus, setLocalSaveStatus] = useState<"idle" | "saving" | "saved" | "error">("idle");
|
|
144
|
+
const [now, setNow] = useState(Date.now());
|
|
139
145
|
const disabled = propDisabled;
|
|
140
146
|
|
|
147
|
+
// Tick every 10s so the "saved X ago" label stays fresh
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
const id = setInterval(() => setNow(Date.now()), 10_000);
|
|
150
|
+
return () => clearInterval(id);
|
|
151
|
+
}, []);
|
|
152
|
+
|
|
141
153
|
const resolveAdminFlag = (
|
|
142
154
|
value: boolean | ((
|
|
143
155
|
data: Record<string, unknown>,
|
|
@@ -367,18 +379,16 @@ export function AutoForm({
|
|
|
367
379
|
message: "Unpublish this document?",
|
|
368
380
|
onConfirm: async () => {
|
|
369
381
|
try {
|
|
370
|
-
const response = await
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
method: "POST",
|
|
374
|
-
},
|
|
382
|
+
const response = await saveDocument(
|
|
383
|
+
{ ...formData, status: 'draft' } as Record<string, unknown>,
|
|
384
|
+
false,
|
|
375
385
|
);
|
|
376
|
-
if (response
|
|
386
|
+
if (response?.ok) {
|
|
377
387
|
onActionSuccess?.("Document unpublished successfully");
|
|
378
388
|
location.reload();
|
|
379
389
|
} else {
|
|
380
|
-
const error = await response
|
|
381
|
-
toast.error(error
|
|
390
|
+
const error = await response?.json().catch(() => ({}));
|
|
391
|
+
toast.error(error?.error || "Failed to unpublish");
|
|
382
392
|
}
|
|
383
393
|
} catch (err) {
|
|
384
394
|
toast.error("Failed to unpublish");
|
|
@@ -390,13 +400,7 @@ export function AutoForm({
|
|
|
390
400
|
const handleSaveDraft = async () => {
|
|
391
401
|
const isNewDoc = !formData.id;
|
|
392
402
|
autoSaveSkipRef.current = true;
|
|
393
|
-
|
|
394
|
-
const btn = document.getElementById("btn-publish") as HTMLButtonElement | null;
|
|
395
|
-
const originalText = btn?.textContent || "";
|
|
396
|
-
if (btn) {
|
|
397
|
-
btn.textContent = "Saving...";
|
|
398
|
-
btn.setAttribute("disabled", "true");
|
|
399
|
-
}
|
|
403
|
+
setLocalSaveStatus("saving");
|
|
400
404
|
|
|
401
405
|
try {
|
|
402
406
|
const data = normalizeUploadFields({ ...formData }) as Record<string, unknown>;
|
|
@@ -417,9 +421,12 @@ export function AutoForm({
|
|
|
417
421
|
setLastSavedData({ ...formData, ...savedData });
|
|
418
422
|
lastAutoSaveTimeRef.current = Date.now();
|
|
419
423
|
setAutoSaveStatus("success");
|
|
420
|
-
|
|
424
|
+
setLocalSaveStatus("saved");
|
|
421
425
|
if (versionsEnabled) fetchVersions();
|
|
422
|
-
setTimeout(() =>
|
|
426
|
+
setTimeout(() => {
|
|
427
|
+
setAutoSaveStatus("idle");
|
|
428
|
+
setLocalSaveStatus("idle");
|
|
429
|
+
}, 2000);
|
|
423
430
|
onActionSuccess?.(
|
|
424
431
|
isPost ? "Document created successfully" : "Changes saved",
|
|
425
432
|
);
|
|
@@ -433,33 +440,27 @@ export function AutoForm({
|
|
|
433
440
|
if (response.status === 409) {
|
|
434
441
|
setAutoSaveStatus("conflict");
|
|
435
442
|
}
|
|
443
|
+
setLocalSaveStatus("error");
|
|
436
444
|
toast.error(error.error || "Failed to save");
|
|
445
|
+
setTimeout(() => setLocalSaveStatus("idle"), 3000);
|
|
437
446
|
}
|
|
438
447
|
} catch (err) {
|
|
448
|
+
setLocalSaveStatus("error");
|
|
439
449
|
toast.error("Failed to save document");
|
|
450
|
+
setTimeout(() => setLocalSaveStatus("idle"), 3000);
|
|
440
451
|
} finally {
|
|
441
452
|
autoSaveSkipRef.current = false;
|
|
442
|
-
if (btn) {
|
|
443
|
-
btn.textContent = originalText;
|
|
444
|
-
btn.removeAttribute("disabled");
|
|
445
|
-
}
|
|
446
453
|
}
|
|
447
454
|
};
|
|
448
455
|
|
|
449
456
|
const handlePublish = async () => {
|
|
450
457
|
const isNewDoc = !formData.id;
|
|
451
458
|
autoSaveSkipRef.current = true;
|
|
452
|
-
|
|
453
|
-
const btn = document.getElementById("btn-publish") as HTMLButtonElement | null;
|
|
454
|
-
const originalText = btn?.textContent || "";
|
|
455
|
-
if (btn) {
|
|
456
|
-
btn.textContent = "Publishing...";
|
|
457
|
-
btn.setAttribute("disabled", "true");
|
|
458
|
-
}
|
|
459
|
+
setLocalSaveStatus("saving");
|
|
459
460
|
|
|
460
461
|
try {
|
|
461
|
-
// Step 1: Create or save the document
|
|
462
462
|
if (isNewDoc && !globalSlug) {
|
|
463
|
+
// Create then immediately publish
|
|
463
464
|
const data = normalizeUploadFields({ ...formData }) as Record<string, unknown>;
|
|
464
465
|
const response = await fetchWithAuth(`/api/${collectionSlug}`, {
|
|
465
466
|
method: "POST",
|
|
@@ -469,49 +470,39 @@ export function AutoForm({
|
|
|
469
470
|
if (!response.ok) {
|
|
470
471
|
const error = await response.json().catch(() => ({}));
|
|
471
472
|
if (response.status === 409) setAutoSaveStatus("conflict");
|
|
473
|
+
setLocalSaveStatus("error");
|
|
472
474
|
toast.error(error.error || "Failed to create document");
|
|
475
|
+
setTimeout(() => setLocalSaveStatus("idle"), 3000);
|
|
473
476
|
return;
|
|
474
477
|
}
|
|
475
478
|
const result = await response.json();
|
|
476
479
|
const savedData = result.data || data;
|
|
477
480
|
setFormData({ ...formData, ...savedData });
|
|
478
481
|
setLastSavedData({ ...formData, ...savedData });
|
|
479
|
-
} else if (hasUnsavedChanges) {
|
|
480
|
-
const response = await saveDocument(formData);
|
|
481
|
-
if (!response.ok) {
|
|
482
|
-
const error = await response.json().catch(() => ({}));
|
|
483
|
-
if (response.status === 409) setAutoSaveStatus("conflict");
|
|
484
|
-
toast.error(error.error || "Failed to save before publishing");
|
|
485
|
-
return;
|
|
486
|
-
}
|
|
487
|
-
const result = await response.json();
|
|
488
|
-
if (result.data) {
|
|
489
|
-
setFormData({ ...formData, ...result.data });
|
|
490
|
-
setLastSavedData({ ...formData, ...result.data });
|
|
491
|
-
}
|
|
492
482
|
}
|
|
493
483
|
|
|
494
|
-
//
|
|
495
|
-
const
|
|
484
|
+
// Save and publish (X-Draft: false writes to main doc + versions table)
|
|
485
|
+
const data = normalizeUploadFields({ ...formData }) as Record<string, unknown>;
|
|
486
|
+
const response = await saveDocument(data, false);
|
|
496
487
|
|
|
497
|
-
if (response
|
|
498
|
-
|
|
488
|
+
if (response?.ok) {
|
|
489
|
+
setLocalSaveStatus("saved");
|
|
499
490
|
onActionSuccess?.("Published successfully");
|
|
500
|
-
await new Promise((r) => setTimeout(r,
|
|
491
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
501
492
|
location.reload();
|
|
502
493
|
} else {
|
|
503
|
-
const error = await response
|
|
504
|
-
if (response
|
|
505
|
-
|
|
494
|
+
const error = await response?.json().catch(() => ({}));
|
|
495
|
+
if (response?.status === 409) setAutoSaveStatus("conflict");
|
|
496
|
+
setLocalSaveStatus("error");
|
|
497
|
+
toast.error(error?.error || "Failed to publish");
|
|
498
|
+
setTimeout(() => setLocalSaveStatus("idle"), 3000);
|
|
506
499
|
}
|
|
507
500
|
} catch (err) {
|
|
501
|
+
setLocalSaveStatus("error");
|
|
508
502
|
toast.error("Failed to publish");
|
|
503
|
+
setTimeout(() => setLocalSaveStatus("idle"), 3000);
|
|
509
504
|
} finally {
|
|
510
505
|
autoSaveSkipRef.current = false;
|
|
511
|
-
if (btn) {
|
|
512
|
-
btn.textContent = originalText;
|
|
513
|
-
btn.removeAttribute("disabled");
|
|
514
|
-
}
|
|
515
506
|
}
|
|
516
507
|
};
|
|
517
508
|
|
|
@@ -870,8 +861,8 @@ export function AutoForm({
|
|
|
870
861
|
(typeof formData.name === "object" ? "" : formData.name) ||
|
|
871
862
|
"Untitled",
|
|
872
863
|
);
|
|
873
|
-
// Use
|
|
874
|
-
const docStatus = documentStatus ?? formData.
|
|
864
|
+
// Use status from the document (merged from version table on draft reads)
|
|
865
|
+
const docStatus = documentStatus ?? formData.status ?? 'draft';
|
|
875
866
|
const isNew = !formData.id;
|
|
876
867
|
const lastModified = formData.updatedAt
|
|
877
868
|
? new Date(formData.updatedAt as string).toLocaleString()
|
|
@@ -880,13 +871,11 @@ export function AutoForm({
|
|
|
880
871
|
? new Date(formData.createdAt as string).toLocaleString()
|
|
881
872
|
: "Just now";
|
|
882
873
|
|
|
883
|
-
const isDraftMode = !formData.id || documentStatus === 'draft'
|
|
874
|
+
const isDraftMode = !formData.id || documentStatus === 'draft';
|
|
884
875
|
|
|
885
876
|
// Status label shown in the header
|
|
886
877
|
const statusLabel = hasUnpublishedChanges
|
|
887
|
-
?
|
|
888
|
-
? 'Draft'
|
|
889
|
-
: 'Published (unpublished changes)'
|
|
878
|
+
? 'Draft (unpublished changes)'
|
|
890
879
|
: docStatus === 'published'
|
|
891
880
|
? 'Published'
|
|
892
881
|
: 'Draft';
|
|
@@ -911,19 +900,7 @@ export function AutoForm({
|
|
|
911
900
|
href={`/${collectionSlug}`}
|
|
912
901
|
className="p-2 border border-[var(--kyro-border)] rounded-xl hover:bg-[var(--kyro-bg-secondary)] transition-colors"
|
|
913
902
|
>
|
|
914
|
-
<
|
|
915
|
-
className="w-4 h-4"
|
|
916
|
-
fill="none"
|
|
917
|
-
stroke="currentColor"
|
|
918
|
-
viewBox="0 0 24 24"
|
|
919
|
-
>
|
|
920
|
-
<path
|
|
921
|
-
strokeLinecap="round"
|
|
922
|
-
strokeLinejoin="round"
|
|
923
|
-
strokeWidth="2.5"
|
|
924
|
-
d="M15 19l-7-7 7-7"
|
|
925
|
-
/>
|
|
926
|
-
</svg>
|
|
903
|
+
<ChevronRight className="w-4 h-4" />
|
|
927
904
|
</a>
|
|
928
905
|
<h1 className="text-xl font-bold tracking-tighter">{docTitle}</h1>
|
|
929
906
|
<span className={`inline-flex items-center gap-1.5 px-2 rounded-full text-[10px] font-regular border ${statusBadgeBg}`}>
|
|
@@ -958,16 +935,7 @@ export function AutoForm({
|
|
|
958
935
|
)}
|
|
959
936
|
{autoSaveStatus === "success" && (
|
|
960
937
|
<span className="text-[var(--kyro-success)] flex items-center gap-1">
|
|
961
|
-
<
|
|
962
|
-
width="12"
|
|
963
|
-
height="12"
|
|
964
|
-
viewBox="0 0 24 24"
|
|
965
|
-
fill="none"
|
|
966
|
-
stroke="currentColor"
|
|
967
|
-
strokeWidth="3"
|
|
968
|
-
>
|
|
969
|
-
<path d="M20 6L9 17l-5-5" />
|
|
970
|
-
</svg>
|
|
938
|
+
<Check className="w-4 h-4" />
|
|
971
939
|
{lastSavedAt ? `Saved ${Math.floor((Date.now() - lastSavedAt) / 60000)}m ago` : "Draft saved"}
|
|
972
940
|
</span>
|
|
973
941
|
)}
|
|
@@ -1031,7 +999,6 @@ export function AutoForm({
|
|
|
1031
999
|
onClick={async () => {
|
|
1032
1000
|
setFormData(lastSavedData);
|
|
1033
1001
|
markSaved();
|
|
1034
|
-
await clearDraftArtifacts();
|
|
1035
1002
|
}}
|
|
1036
1003
|
className="text-[var(--kyro-primary)] hover:underline"
|
|
1037
1004
|
>
|
|
@@ -1039,6 +1006,19 @@ export function AutoForm({
|
|
|
1039
1006
|
</button>
|
|
1040
1007
|
</>
|
|
1041
1008
|
)}
|
|
1009
|
+
{/* Live auto-save timestamp */}
|
|
1010
|
+
{lastSavedAt && autoSaveStatus !== "saving" && autoSaveStatus !== "retrying" && autoSaveStatus !== "success" && (
|
|
1011
|
+
<span className="border-l border-[var(--kyro-border)] pl-4">
|
|
1012
|
+
Draft saved {(() => {
|
|
1013
|
+
const diffMs = now - lastSavedAt;
|
|
1014
|
+
const diffMin = Math.floor(diffMs / 60_000);
|
|
1015
|
+
const diffSec = Math.floor(diffMs / 1_000);
|
|
1016
|
+
if (diffMin >= 1) return `${diffMin}m ago`;
|
|
1017
|
+
if (diffSec >= 5) return `${diffSec}s ago`;
|
|
1018
|
+
return "just now";
|
|
1019
|
+
})()}
|
|
1020
|
+
</span>
|
|
1021
|
+
)}
|
|
1042
1022
|
<span className="border-l border-[var(--kyro-border)] pl-4">
|
|
1043
1023
|
Modified {lastModified}
|
|
1044
1024
|
</span>
|
|
@@ -1071,16 +1051,7 @@ export function AutoForm({
|
|
|
1071
1051
|
className={`kyro-btn p-2.5 rounded-xl transition-all flex items-center gap-2 ${showPreview ? "shadow-lg" : "text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-bg-secondary)]"}`}
|
|
1072
1052
|
title="Live Preview"
|
|
1073
1053
|
>
|
|
1074
|
-
<
|
|
1075
|
-
width="20"
|
|
1076
|
-
height="20"
|
|
1077
|
-
viewBox="0 0 24 24"
|
|
1078
|
-
fill="none"
|
|
1079
|
-
stroke="currentColor"
|
|
1080
|
-
strokeWidth="2"
|
|
1081
|
-
>
|
|
1082
|
-
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6M15 3h6v6M10 14L21 3" />
|
|
1083
|
-
</svg>
|
|
1054
|
+
<ExternalLink className="w-4 h-4" />
|
|
1084
1055
|
{showPreview && (
|
|
1085
1056
|
<span className="text-[10px] font-bold tracking-widest pr-1">
|
|
1086
1057
|
Active
|
|
@@ -1108,152 +1079,106 @@ export function AutoForm({
|
|
|
1108
1079
|
</svg>
|
|
1109
1080
|
</button>
|
|
1110
1081
|
|
|
1111
|
-
{
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
onClick={isDraftMode ? handleSaveDraft : handlePublish}
|
|
1125
|
-
className={`px-6 py-2.5 text-xs font-bold rounded-l-xl rounded-r-none transition-all whitespace-nowrap ${isDraftMode ? 'kyro-btn-primary' : 'kyro-btn-success'}`}
|
|
1126
|
-
>
|
|
1127
|
-
{isDraftMode ? "Save Draft" : "Publish Changes"}
|
|
1128
|
-
</button>
|
|
1082
|
+
{/* ── Publish button (no dropdown) ──────────────────────────────── */}
|
|
1083
|
+
<SplitButton
|
|
1084
|
+
status={documentStatus as SplitButtonStatus}
|
|
1085
|
+
saveStatus={localSaveStatus}
|
|
1086
|
+
hasChanges={hasUnsavedChanges}
|
|
1087
|
+
onPublish={handlePublish}
|
|
1088
|
+
disabled={localSaveStatus === "saving"}
|
|
1089
|
+
/>
|
|
1090
|
+
|
|
1091
|
+
{/* ── Kebab: document management actions ───────────────────────── */}
|
|
1092
|
+
{!isNew && (
|
|
1093
|
+
<Dropdown
|
|
1094
|
+
trigger={
|
|
1129
1095
|
<button
|
|
1130
1096
|
type="button"
|
|
1131
|
-
|
|
1132
|
-
|
|
1097
|
+
className="kyro-btn p-2.5 rounded-xl border border-[var(--kyro-border)] hover:bg-[var(--kyro-bg-secondary)] transition-all"
|
|
1098
|
+
title="More actions"
|
|
1133
1099
|
>
|
|
1134
|
-
<svg width="
|
|
1135
|
-
<
|
|
1100
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
|
|
1101
|
+
<circle cx="12" cy="5" r="1.5" />
|
|
1102
|
+
<circle cx="12" cy="12" r="1.5" />
|
|
1103
|
+
<circle cx="12" cy="19" r="1.5" />
|
|
1136
1104
|
</svg>
|
|
1137
1105
|
</button>
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
className="w-full px-4 py-2.5 text-left text-xs font-medium text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)] flex items-center gap-3 transition-colors"
|
|
1149
|
-
>
|
|
1150
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
1151
|
-
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" />
|
|
1152
|
-
<polyline points="17 21 17 13 7 13 7 21" />
|
|
1153
|
-
<polyline points="7 3 7 8 15 8" />
|
|
1106
|
+
}
|
|
1107
|
+
direction="down"
|
|
1108
|
+
>
|
|
1109
|
+
{!globalSlug && (
|
|
1110
|
+
<DropdownItem
|
|
1111
|
+
onClick={handleCreateNew}
|
|
1112
|
+
icon={
|
|
1113
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
1114
|
+
<line x1="12" y1="5" x2="12" y2="19" />
|
|
1115
|
+
<line x1="5" y1="12" x2="19" y2="12" />
|
|
1154
1116
|
</svg>
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
<
|
|
1117
|
+
}
|
|
1118
|
+
>
|
|
1119
|
+
Create New
|
|
1120
|
+
</DropdownItem>
|
|
1121
|
+
)}
|
|
1122
|
+
{!globalSlug && (
|
|
1123
|
+
<DropdownItem
|
|
1124
|
+
onClick={handleDuplicate}
|
|
1125
|
+
icon={
|
|
1126
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
1127
|
+
<rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
|
|
1128
|
+
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
|
|
1167
1129
|
</svg>
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
<
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1130
|
+
}
|
|
1131
|
+
>
|
|
1132
|
+
Duplicate
|
|
1133
|
+
</DropdownItem>
|
|
1134
|
+
)}
|
|
1135
|
+
<DropdownItem
|
|
1136
|
+
onClick={() => setShowSchedulePicker(true)}
|
|
1137
|
+
icon={
|
|
1138
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
1139
|
+
<rect x="3" y="4" width="18" height="18" rx="2" ry="2" />
|
|
1140
|
+
<line x1="16" y1="2" x2="16" y2="6" />
|
|
1141
|
+
<line x1="8" y1="2" x2="8" y2="6" />
|
|
1142
|
+
<line x1="3" y1="10" x2="21" y2="10" />
|
|
1143
|
+
</svg>
|
|
1144
|
+
}
|
|
1145
|
+
>
|
|
1146
|
+
Schedule Publish
|
|
1147
|
+
</DropdownItem>
|
|
1148
|
+
{documentStatus === "published" && (
|
|
1149
|
+
<DropdownItem
|
|
1150
|
+
onClick={handleUnpublish}
|
|
1151
|
+
icon={
|
|
1152
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
1153
|
+
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24" />
|
|
1154
|
+
<line x1="1" y1="1" x2="23" y2="23" />
|
|
1183
1155
|
</svg>
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
<svg
|
|
1197
|
-
<
|
|
1198
|
-
<
|
|
1156
|
+
}
|
|
1157
|
+
>
|
|
1158
|
+
Unpublish
|
|
1159
|
+
</DropdownItem>
|
|
1160
|
+
)}
|
|
1161
|
+
{!globalSlug && (
|
|
1162
|
+
<>
|
|
1163
|
+
<DropdownSeparator />
|
|
1164
|
+
<DropdownItem
|
|
1165
|
+
onClick={handleDelete}
|
|
1166
|
+
danger
|
|
1167
|
+
icon={
|
|
1168
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
1169
|
+
<polyline points="3 6 5 6 21 6" />
|
|
1170
|
+
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
|
|
1199
1171
|
</svg>
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
<button
|
|
1206
|
-
type="button"
|
|
1207
|
-
onClick={() => {
|
|
1208
|
-
handleDuplicate();
|
|
1209
|
-
setIsMenuOpen(false);
|
|
1210
|
-
}}
|
|
1211
|
-
className="w-full px-4 py-2.5 text-left text-xs font-medium text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)] flex items-center gap-3 transition-colors"
|
|
1212
|
-
>
|
|
1213
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
1214
|
-
<rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
|
|
1215
|
-
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
|
|
1216
|
-
</svg>
|
|
1217
|
-
Duplicate
|
|
1218
|
-
</button>
|
|
1219
|
-
{documentStatus === "published" && (
|
|
1220
|
-
<button
|
|
1221
|
-
type="button"
|
|
1222
|
-
onClick={() => {
|
|
1223
|
-
handleUnpublish();
|
|
1224
|
-
setIsMenuOpen(false);
|
|
1225
|
-
}}
|
|
1226
|
-
className="w-full px-4 py-2.5 text-left text-xs font-medium text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)] flex items-center gap-3 transition-colors"
|
|
1227
|
-
>
|
|
1228
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
1229
|
-
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24" />
|
|
1230
|
-
<line x1="1" y1="1" x2="23" y2="23" />
|
|
1231
|
-
</svg>
|
|
1232
|
-
Unpublish
|
|
1233
|
-
</button>
|
|
1234
|
-
)}
|
|
1235
|
-
<div className="h-px bg-[var(--kyro-border)]" />
|
|
1236
|
-
<button
|
|
1237
|
-
type="button"
|
|
1238
|
-
onClick={() => {
|
|
1239
|
-
handleDelete();
|
|
1240
|
-
setIsMenuOpen(false);
|
|
1241
|
-
}}
|
|
1242
|
-
className="w-full px-4 py-2.5 text-left text-xs font-medium text-red-600 hover:bg-red-50 flex items-center gap-3 transition-colors"
|
|
1243
|
-
>
|
|
1244
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
1245
|
-
<polyline points="3 6 5 6 21 6" />
|
|
1246
|
-
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
|
|
1247
|
-
</svg>
|
|
1248
|
-
Delete
|
|
1249
|
-
</button>
|
|
1250
|
-
</>
|
|
1251
|
-
)}
|
|
1252
|
-
</div>
|
|
1172
|
+
}
|
|
1173
|
+
>
|
|
1174
|
+
Delete
|
|
1175
|
+
</DropdownItem>
|
|
1176
|
+
</>
|
|
1253
1177
|
)}
|
|
1254
|
-
</
|
|
1178
|
+
</Dropdown>
|
|
1255
1179
|
)}
|
|
1256
1180
|
|
|
1181
|
+
|
|
1257
1182
|
{showSchedulePicker && (
|
|
1258
1183
|
<div ref={scheduleRef} className="relative">
|
|
1259
1184
|
<div className="absolute right-0 top-2 p-4 rounded-lg border border-[var(--kyro-border)] bg-[var(--kyro-surface)] shadow-2xl z-50">
|
|
@@ -1422,15 +1347,7 @@ export function AutoForm({
|
|
|
1422
1347
|
onClick={() => setCompareDiffs([])}
|
|
1423
1348
|
className="p-1 rounded hover:bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-muted)]"
|
|
1424
1349
|
>
|
|
1425
|
-
<
|
|
1426
|
-
className="w-3.5 h-3.5"
|
|
1427
|
-
viewBox="0 0 24 24"
|
|
1428
|
-
fill="none"
|
|
1429
|
-
stroke="currentColor"
|
|
1430
|
-
strokeWidth="2.5"
|
|
1431
|
-
>
|
|
1432
|
-
<path d="M18 6L6 18M6 6l12 12" />
|
|
1433
|
-
</svg>
|
|
1350
|
+
<X className="w-4 h-4" />
|
|
1434
1351
|
</button>
|
|
1435
1352
|
</div>
|
|
1436
1353
|
<div className="max-h-[400px] overflow-y-auto">
|
|
@@ -1497,15 +1414,7 @@ export function AutoForm({
|
|
|
1497
1414
|
}`}
|
|
1498
1415
|
>
|
|
1499
1416
|
{isSelected && (
|
|
1500
|
-
<
|
|
1501
|
-
className="w-full h-full text-white p-0.5"
|
|
1502
|
-
viewBox="0 0 24 24"
|
|
1503
|
-
fill="none"
|
|
1504
|
-
stroke="currentColor"
|
|
1505
|
-
strokeWidth="3"
|
|
1506
|
-
>
|
|
1507
|
-
<path d="M20 6L9 17l-5-5" />
|
|
1508
|
-
</svg>
|
|
1417
|
+
<Check className="w-4 h-4" />
|
|
1509
1418
|
)}
|
|
1510
1419
|
</div>
|
|
1511
1420
|
) : (
|
|
@@ -1645,16 +1554,7 @@ export function AutoForm({
|
|
|
1645
1554
|
className={`w-4 h-4 rounded border transition-all flex items-center justify-center ${item.checked ? "bg-[var(--kyro-primary)] border-[var(--kyro-primary)]" : "border-[var(--kyro-border)] group-hover:border-[var(--kyro-text-secondary)]"}`}
|
|
1646
1555
|
>
|
|
1647
1556
|
{item.checked && (
|
|
1648
|
-
<
|
|
1649
|
-
width="10"
|
|
1650
|
-
height="10"
|
|
1651
|
-
viewBox="0 0 24 24"
|
|
1652
|
-
fill="none"
|
|
1653
|
-
stroke="white"
|
|
1654
|
-
strokeWidth="4"
|
|
1655
|
-
>
|
|
1656
|
-
<path d="M20 6L9 17l-5-5" />
|
|
1657
|
-
</svg>
|
|
1557
|
+
<Check className="w-4 h-4" />
|
|
1658
1558
|
)}
|
|
1659
1559
|
</div>
|
|
1660
1560
|
<span className="text-xs font-medium text-[var(--kyro-text-secondary)] group-hover:text-[var(--kyro-text-primary)] transition-colors">
|
|
@@ -1702,9 +1602,7 @@ export function AutoForm({
|
|
|
1702
1602
|
)}
|
|
1703
1603
|
{autoSaveStatus === "success" && (
|
|
1704
1604
|
<span className="text-[var(--kyro-success)] flex items-center gap-1">
|
|
1705
|
-
<
|
|
1706
|
-
<path d="M20 6L9 17l-5-5" />
|
|
1707
|
-
</svg>
|
|
1605
|
+
<Check className="w-4 h-4" />
|
|
1708
1606
|
{lastSavedAt ? `Saved ${Math.floor((Date.now() - lastSavedAt) / 60000)}m ago` : "Saved"}
|
|
1709
1607
|
</span>
|
|
1710
1608
|
)}
|
|
@@ -1742,7 +1640,6 @@ export function AutoForm({
|
|
|
1742
1640
|
type="button"
|
|
1743
1641
|
style={{ width: 0, height: 0, opacity: 0, padding: 0, margin: 0, border: 'none', position: 'absolute' }}
|
|
1744
1642
|
onClick={async () => {
|
|
1745
|
-
console.log("[AutoForm] Hidden save button clicked");
|
|
1746
1643
|
try {
|
|
1747
1644
|
const response = await saveDocument();
|
|
1748
1645
|
if (response.ok) {
|
|
@@ -1943,9 +1840,7 @@ function RelationshipField({
|
|
|
1943
1840
|
{loading ? (
|
|
1944
1841
|
<div className="kyro-relation-modal-empty">Loading...</div>
|
|
1945
1842
|
) : filteredOptions.length === 0 ? (
|
|
1946
|
-
<
|
|
1947
|
-
No results found.
|
|
1948
|
-
</div>
|
|
1843
|
+
<EmptyState title="No results found." />
|
|
1949
1844
|
) : (
|
|
1950
1845
|
filteredOptions.map((opt) => {
|
|
1951
1846
|
const o = opt as { id?: string };
|