Skip to content

Commit ed9d522

Browse files
authored
Merge pull request #91 from ycode/develop
Develop
2 parents 4554561 + 75df4b6 commit ed9d522

File tree

12 files changed

+118
-36
lines changed

12 files changed

+118
-36
lines changed

app/ycode/api/collections/[id]/fields/route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,9 @@ export async function POST(
7878
order: body.order ?? 0,
7979
reference_collection_id: body.reference_collection_id || null,
8080
hidden: body.hidden ?? false,
81+
is_computed: body.is_computed ?? false,
8182
data: body.data || {},
82-
is_published: false, // Always create as draft
83+
is_published: false,
8384
});
8485

8586
return noCache(

app/ycode/components/CMS.tsx

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ interface SortableRowProps {
112112
isCollectionPublished: boolean;
113113
children: React.ReactNode;
114114
statusValue: import('./CollectionStatusPill').ItemStatusValue | null;
115+
onEdit: () => void;
115116
onSetAsDraft: () => void;
116117
onStageForPublish: () => void;
117118
onSetAsPublished: () => void;
@@ -120,7 +121,7 @@ interface SortableRowProps {
120121
lockInfo?: ItemLockInfo;
121122
}
122123

123-
function SortableRow({ item, isSaving, isManualMode, isCollectionPublished, children, statusValue, onSetAsDraft, onStageForPublish, onSetAsPublished, onDuplicate, onDelete, lockInfo }: SortableRowProps) {
124+
function SortableRow({ item, isSaving, isManualMode, isCollectionPublished, children, statusValue, onEdit, onSetAsDraft, onStageForPublish, onSetAsPublished, onDuplicate, onDelete, lockInfo }: SortableRowProps) {
124125
const {
125126
attributes,
126127
listeners,
@@ -147,7 +148,9 @@ function SortableRow({ item, isSaving, isManualMode, isCollectionPublished, chil
147148
<CollectionItemContextMenu
148149
isPublishable={statusValue?.is_publishable ?? item.is_publishable}
149150
hasPublishedVersion={statusValue?.is_published ?? false}
151+
isModified={statusValue?.is_modified ?? false}
150152
isCollectionPublished={isCollectionPublished}
153+
onEdit={onEdit}
151154
onSetAsDraft={onSetAsDraft}
152155
onStageForPublish={onStageForPublish}
153156
onSetAsPublished={onSetAsPublished}
@@ -1565,6 +1568,7 @@ const CMS = React.memo(function CMS() {
15651568
isManualMode={isManualMode}
15661569
isCollectionPublished={selectedCollection?.has_published_version ?? false}
15671570
statusValue={statusFieldId ? parseStatusValue(item.values[statusFieldId]) : null}
1571+
onEdit={() => handleEditItem(item)}
15681572
onSetAsDraft={() => handleSetItemStatus(item.id, 'draft')}
15691573
onStageForPublish={() => handleSetItemStatus(item.id, 'stage')}
15701574
onSetAsPublished={() => handleSetItemStatus(item.id, 'publish')}
@@ -1576,9 +1580,7 @@ const CMS = React.memo(function CMS() {
15761580
className="pl-5 pr-3 py-3 w-12"
15771581
onClick={(e) => {
15781582
e.stopPropagation();
1579-
if (!isManualMode) {
1580-
handleEditItem(item);
1581-
}
1583+
handleEditItem(item);
15821584
}}
15831585
>
15841586
<div className="flex">
@@ -1599,7 +1601,7 @@ const CMS = React.memo(function CMS() {
15991601
<td
16001602
key={field.id}
16011603
className="px-4 py-5"
1602-
onClick={() => !isManualMode && handleEditItem(item)}
1604+
onClick={() => handleEditItem(item)}
16031605
>
16041606
<CollectionStatusPill
16051607
statusValue={statusFieldId ? parseStatusValue(item.values[statusFieldId]) : null}
@@ -1616,7 +1618,7 @@ const CMS = React.memo(function CMS() {
16161618
<td
16171619
key={field.id}
16181620
className="px-4 py-5 text-muted-foreground"
1619-
onClick={() => !isManualMode && handleEditItem(item)}
1621+
onClick={() => handleEditItem(item)}
16201622
>
16211623
<span className="line-clamp-1 truncate">
16221624
{formatDateInTimezone(value, timezone, 'display')}
@@ -1637,7 +1639,7 @@ const CMS = React.memo(function CMS() {
16371639
<td
16381640
key={field.id}
16391641
className="px-4 py-5 text-muted-foreground"
1640-
onClick={() => !isManualMode && handleEditItem(item)}
1642+
onClick={() => handleEditItem(item)}
16411643
>
16421644
-
16431645
</td>
@@ -1648,7 +1650,7 @@ const CMS = React.memo(function CMS() {
16481650
<td
16491651
key={field.id}
16501652
className="px-4"
1651-
onClick={() => !isManualMode && handleEditItem(item)}
1653+
onClick={() => handleEditItem(item)}
16521654
>
16531655
<div className="flex items-center gap-1 -my-1.5">
16541656
{assetIds.slice(0, 3).map((assetId, idx) => {
@@ -1716,7 +1718,7 @@ const CMS = React.memo(function CMS() {
17161718
<td
17171719
key={field.id}
17181720
className="px-4 py-5 text-muted-foreground"
1719-
onClick={() => !isManualMode && handleEditItem(item)}
1721+
onClick={() => handleEditItem(item)}
17201722
>
17211723
-
17221724
</td>
@@ -1727,7 +1729,7 @@ const CMS = React.memo(function CMS() {
17271729
<td
17281730
key={field.id}
17291731
className="px-4"
1730-
onClick={() => !isManualMode && handleEditItem(item)}
1732+
onClick={() => handleEditItem(item)}
17311733
>
17321734
<div className="flex items-center gap-1 -my-1.5">
17331735
{assetIds.slice(0, 3).map((assetId, idx) => {
@@ -1763,7 +1765,7 @@ const CMS = React.memo(function CMS() {
17631765
<td
17641766
key={field.id}
17651767
className="px-4 py-5 text-muted-foreground"
1766-
onClick={() => !isManualMode && handleEditItem(item)}
1768+
onClick={() => handleEditItem(item)}
17671769
>
17681770

17691771
<ReferenceFieldCell
@@ -1783,7 +1785,7 @@ const CMS = React.memo(function CMS() {
17831785
<td
17841786
key={field.id}
17851787
className="px-4 py-5 text-muted-foreground max-w-50"
1786-
onClick={() => !isManualMode && handleEditItem(item)}
1788+
onClick={() => handleEditItem(item)}
17871789
>
17881790
<span className="block truncate">
17891791
{plainText || '-'}
@@ -1828,7 +1830,7 @@ const CMS = React.memo(function CMS() {
18281830
<td
18291831
key={field.id}
18301832
className="px-4 py-5 text-muted-foreground max-w-50"
1831-
onClick={() => !isManualMode && handleEditItem(item)}
1833+
onClick={() => handleEditItem(item)}
18321834
>
18331835
<span className="block truncate">
18341836
{displayValue}
@@ -1843,7 +1845,7 @@ const CMS = React.memo(function CMS() {
18431845
<td
18441846
key={field.id}
18451847
className="px-4 py-5 text-muted-foreground"
1846-
onClick={() => !isManualMode && handleEditItem(item)}
1848+
onClick={() => handleEditItem(item)}
18471849
>
18481850
<div className="flex items-center gap-2">
18491851
<div
@@ -1863,7 +1865,7 @@ const CMS = React.memo(function CMS() {
18631865
<td
18641866
key={field.id}
18651867
className="px-4 py-5"
1866-
onClick={() => !isManualMode && handleEditItem(item)}
1868+
onClick={() => handleEditItem(item)}
18671869
>
18681870
<div className="pointer-events-none">
18691871
<Checkbox
@@ -1880,7 +1882,7 @@ const CMS = React.memo(function CMS() {
18801882
<td
18811883
key={field.id}
18821884
className="px-4 py-5 text-muted-foreground"
1883-
onClick={() => !isManualMode && handleEditItem(item)}
1885+
onClick={() => handleEditItem(item)}
18841886
>
18851887
<span className="line-clamp-1 truncate">
18861888
{value || '-'}

app/ycode/components/CollectionItemContextMenu.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ interface CollectionItemContextMenuProps {
77
children: React.ReactNode;
88
isPublishable: boolean;
99
hasPublishedVersion: boolean;
10+
isModified: boolean;
1011
isCollectionPublished: boolean;
12+
onEdit: () => void;
1113
onSetAsDraft: () => void;
1214
onStageForPublish: () => void;
1315
onSetAsPublished: () => void;
@@ -20,7 +22,9 @@ export default function CollectionItemContextMenu({
2022
children,
2123
isPublishable,
2224
hasPublishedVersion,
25+
isModified,
2326
isCollectionPublished,
27+
onEdit,
2428
onSetAsDraft,
2529
onStageForPublish,
2630
onSetAsPublished,
@@ -38,20 +42,24 @@ export default function CollectionItemContextMenu({
3842
{children}
3943
</ContextMenuTrigger>
4044
<ContextMenuContent className="w-48">
45+
<ContextMenuItem onClick={onEdit}>
46+
Edit CMS item
47+
</ContextMenuItem>
48+
<ContextMenuSeparator />
4149
<ContextMenuItem
4250
disabled={!isPublishable && !hasPublishedVersion}
4351
onClick={onSetAsDraft}
4452
>
4553
Set as draft
4654
</ContextMenuItem>
4755
<ContextMenuItem
48-
disabled={isPublishable && !hasPublishedVersion}
56+
disabled={isPublishable || hasPublishedVersion}
4957
onClick={onStageForPublish}
5058
>
5159
Stage for publish
5260
</ContextMenuItem>
5361
<ContextMenuItem
54-
disabled={!isCollectionPublished || (hasPublishedVersion && isPublishable)}
62+
disabled={!isCollectionPublished || (hasPublishedVersion && isPublishable && !isModified)}
5563
onClick={onSetAsPublished}
5664
>
5765
Set as published

app/ycode/components/CollectionItemSheet.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -211,24 +211,24 @@ export default function CollectionItemSheet({
211211

212212
// Reset form when editing item changes
213213
useEffect(() => {
214+
// Only include fillable/visible fields in form state to avoid
215+
// sending computed fields (status, ID, timestamps) to the API
216+
const editableFields = collectionFields.filter(f => f.fillable && !f.hidden);
217+
214218
if (editingItem) {
215-
// Ensure all values are defined (not undefined)
216219
const values: Record<string, any> = {};
217-
collectionFields.forEach(field => {
220+
editableFields.forEach(field => {
218221
let value = editingItem.values[field.id] ?? '';
219-
// Normalize boolean values to strings
220222
if (field.type === 'boolean') {
221223
value = normalizeBooleanValue(value);
222224
}
223225
values[field.id] = value;
224226
});
225227
form.reset(values);
226228
} else {
227-
// Reset with default values for new items
228229
const defaultValues: Record<string, any> = {};
229-
collectionFields.forEach(field => {
230+
editableFields.forEach(field => {
230231
let value = field.default || '';
231-
// Normalize boolean values to strings
232232
if (field.type === 'boolean') {
233233
value = normalizeBooleanValue(value);
234234
}
@@ -607,17 +607,20 @@ export default function CollectionItemSheet({
607607
<Input
608608
type="email"
609609
placeholder={field.default || `Enter ${field.name.toLowerCase()}...`}
610+
autoComplete="off"
610611
{...formField}
611612
/>
612613
) : field.type === 'phone' ? (
613614
<Input
614615
type="tel"
615616
placeholder={field.default || `Enter ${field.name.toLowerCase()}...`}
617+
autoComplete="off"
616618
{...formField}
617619
/>
618620
) : field.type === 'date' ? (
619621
<Input
620622
type="datetime-local"
623+
autoComplete="off"
621624
value={formatDateInTimezone(formField.value, timezone, 'datetime-local')}
622625
onChange={(e) => {
623626
const utcValue = localDatetimeToUTC(e.target.value, timezone);
@@ -786,6 +789,7 @@ export default function CollectionItemSheet({
786789
<Input
787790
ref={nameInputRef}
788791
placeholder={field.default || `Enter ${field.name.toLowerCase()}...`}
792+
autoComplete="off"
789793
name={formField.name}
790794
value={formField.value}
791795
onChange={formField.onChange}
@@ -794,6 +798,7 @@ export default function CollectionItemSheet({
794798
) : (
795799
<Input
796800
placeholder={field.default || `Enter ${field.name.toLowerCase()}...`}
801+
autoComplete="off"
797802
{...formField}
798803
/>
799804
)}

app/ycode/components/FieldFormDialog.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ export default function FieldFormDialog({
190190
value={fieldName}
191191
onChange={(e) => setFieldName(e.target.value)}
192192
placeholder="Field name"
193+
autoComplete="off"
193194
/>
194195
</div>
195196
</div>
@@ -351,13 +352,15 @@ export default function FieldFormDialog({
351352
value={fieldDefault}
352353
onChange={(e) => setFieldDefault(e.target.value)}
353354
placeholder="0"
355+
autoComplete="off"
354356
/>
355357
) : fieldType === 'date' ? (
356358
<Input
357359
id="field-default"
358360
type="datetime-local"
359361
value={fieldDefault}
360362
onChange={(e) => setFieldDefault(e.target.value)}
363+
autoComplete="off"
361364
/>
362365
) : fieldType === 'email' ? (
363366
<Input
@@ -366,6 +369,7 @@ export default function FieldFormDialog({
366369
value={fieldDefault}
367370
onChange={(e) => setFieldDefault(e.target.value)}
368371
placeholder="[email protected]"
372+
autoComplete="off"
369373
/>
370374
) : fieldType === 'phone' ? (
371375
<Input
@@ -374,13 +378,15 @@ export default function FieldFormDialog({
374378
value={fieldDefault}
375379
onChange={(e) => setFieldDefault(e.target.value)}
376380
placeholder="+1 (555) 000-0000"
381+
autoComplete="off"
377382
/>
378383
) : (
379384
<Input
380385
id="field-default"
381386
value={fieldDefault}
382387
onChange={(e) => setFieldDefault(e.target.value)}
383388
placeholder="Default value"
389+
autoComplete="off"
384390
/>
385391
)}
386392
</div>

app/ycode/components/RichTextEditor.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,6 @@ const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorProps>(({
445445
Code,
446446
RichTextImageWithNodeView,
447447
HorizontalRule,
448-
RichTextImage,
449448
];
450449

451450
// Always include heading extension so content with headings is preserved

app/ycode/components/RichTextLinkSettings.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -517,7 +517,7 @@ export default function RichTextLinkSettings({
517517
{/* Link Type */}
518518
<div className="grid grid-cols-3 items-center gap-2">
519519
<Label className="text-xs text-muted-foreground">Link To</Label>
520-
<div className="col-span-2">
520+
<div className="col-span-2 *:w-full">
521521
<Select
522522
value={linkType === 'none' ? '' : linkType}
523523
onValueChange={(newVal) => handleLinkTypeChange(newVal as LinkType | 'none')}

components/ui/select.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ function SelectValue({
2828
}
2929

3030
const selectVariants = cva(
31-
"w-full border-transparent data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-[0px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive flex items-center justify-between gap-1 rounded-lg border bg-transparent px-2 py-1 text-sm whitespace-nowrap transition-[color,box-shadow] outline-none cursor-pointer disabled:cursor-not-allowed disabled:opacity-50 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
31+
"border-transparent data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-[0px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive flex items-center justify-between gap-1 rounded-lg border bg-transparent px-2 py-1 text-sm whitespace-nowrap transition-[color,box-shadow] outline-none cursor-pointer disabled:cursor-not-allowed disabled:opacity-50 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
3232
{
3333
variants: {
3434
variant: {

lib/collection-field-utils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ export const FIELD_TYPES_BY_CATEGORY = FIELD_TYPE_CATEGORIES.map(cat => ({
5959
types: FIELD_TYPES.filter(t => t.category === cat.id),
6060
}));
6161

62-
/** Valid field type values for API validation */
63-
export const VALID_FIELD_TYPES: readonly string[] = FIELD_TYPES.map((t) => t.value);
62+
/** Valid field type values for API validation (includes system types like 'status') */
63+
export const VALID_FIELD_TYPES: readonly string[] = [...FIELD_TYPES.map((t) => t.value), 'status'];
6464

6565
/** Check if a field type supports setting a default value */
6666
export function supportsDefaultValue(fieldType: CollectionFieldType | undefined): boolean {

0 commit comments

Comments
 (0)