Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions app/ycode/components/Canvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { getCanvasIframeHtml } from '@/lib/canvas-utils';
import { CanvasPortalProvider } from '@/lib/canvas-portal-context';
import { cn } from '@/lib/utils';
import { loadSwiperCss } from '@/lib/slider-utils';
import { resolveReferenceFieldsSync } from '@/lib/collection-utils';
import { useEditorStore } from '@/stores/useEditorStore';
import { useFontsStore } from '@/stores/useFontsStore';
import { useColorVariablesStore } from '@/stores/useColorVariablesStore';
Expand Down Expand Up @@ -298,6 +299,19 @@ export default function Canvas({
return serializeLayers(layers, components, editingComponentVariables);
}, [layers, components, editingComponentVariables]);

// Enrich page collection item data with reference field dotted keys
// so variables like "refFieldId.targetFieldId" resolve on canvas
const enrichedPageCollectionItemData = useMemo(() => {
const values = pageCollectionItem?.values;
if (!values || !pageCollectionFields?.length) return values || null;
return resolveReferenceFieldsSync(
values,
pageCollectionFields,
collectionItems,
collectionFields
);
}, [pageCollectionItem?.values, pageCollectionFields, collectionItems, collectionFields]);

// Collect layer IDs that should be hidden on canvas (display: hidden with on-load)
const editorHiddenLayerIds = useMemo(() => {
if (disableEditorHiddenLayers) return undefined;
Expand Down Expand Up @@ -472,7 +486,7 @@ export default function Canvas({
hoveredLayerId={effectiveHoveredLayerId}
pageId={pageId}
pageCollectionItemId={pageCollectionItem?.id}
pageCollectionItemData={pageCollectionItem?.values || null}
pageCollectionItemData={enrichedPageCollectionItemData}
onLayerClick={handleLayerClick}
onLayerUpdate={onLayerUpdate}
onLayerHover={handleLayerHover}
Expand All @@ -495,7 +509,8 @@ export default function Canvas({
editingComponentId,
editingComponentVariables,
pageId,
pageCollectionItem,
pageCollectionItem?.id,
enrichedPageCollectionItemData,
handleLayerClick,
onLayerUpdate,
handleLayerHover,
Expand Down
2 changes: 2 additions & 0 deletions app/ycode/components/CollectionItemSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,7 @@ export default function CollectionItemSheet({
variant="full"
withFormatting={true}
excludedLinkTypes={['asset', 'field']}
hidePageContextOptions={true}
onExpandClick={() => setExpandedRichTextField(field.id)}
/>
<RichTextEditorSheet
Expand All @@ -578,6 +579,7 @@ export default function CollectionItemSheet({
value={formField.value || ''}
onChange={formField.onChange}
placeholder={field.default || `Enter ${field.name.toLowerCase()}...`}
hidePageContextOptions={true}
/>
</div>
) : field.type === 'reference' && field.reference_collection_id ? (
Expand Down
69 changes: 69 additions & 0 deletions app/ycode/components/LinkItemOptions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
'use client';

import { SelectItem, SelectSeparator } from '@/components/ui/select';
import type { CollectionItemWithValues, CollectionField } from '@/types';
import type { ReferenceItemOption } from '@/lib/collection-field-utils';

interface CollectionItemSelectOptionsProps {
canUseCurrentPageItem: boolean;
canUseCurrentCollectionItem: boolean;
referenceItemOptions: ReferenceItemOption[];
collectionItems: CollectionItemWithValues[];
/** Fields for the linked page's collection, used to derive display names */
collectionFields: CollectionField[];
}

/** Derives a human-readable label for a collection item. */
function getDisplayName(item: CollectionItemWithValues, collectionFields: CollectionField[]): string {
const nameField = collectionFields.find(f => f.key === 'name');
if (nameField && item.values[nameField.id]) return item.values[nameField.id];
const values = Object.values(item.values);
return values[0] || item.id;
}

/**
* Shared SelectContent items for CMS item pickers used in link settings.
* Renders "Current page item", "Current collection item", reference field options,
* a separator, and the concrete item list.
*/
export default function LinkItemOptions({
canUseCurrentPageItem,
canUseCurrentCollectionItem,
referenceItemOptions,
collectionItems,
collectionFields,
}: CollectionItemSelectOptionsProps) {
const hasSpecialOptions = canUseCurrentPageItem || canUseCurrentCollectionItem || referenceItemOptions.length > 0;

return (
<>
{canUseCurrentPageItem && (
<SelectItem value="current-page">
<div className="flex items-center gap-2">
Current page item
</div>
</SelectItem>
)}
{canUseCurrentCollectionItem && (
<SelectItem value="current-collection">
<div className="flex items-center gap-2">
Current collection item
</div>
</SelectItem>
)}
{referenceItemOptions.map((opt) => (
<SelectItem key={opt.value} value={opt.value}>
<div className="flex items-center gap-2">
{opt.label}
</div>
</SelectItem>
))}
{hasSpecialOptions && <SelectSeparator />}
{collectionItems.map((item) => (
<SelectItem key={item.id} value={item.id}>
{getDisplayName(item, collectionFields)}
</SelectItem>
))}
</>
);
}
70 changes: 26 additions & 44 deletions app/ycode/components/LinkSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ import { Checkbox } from '@/components/ui/checkbox';
import Icon, { type IconProps } from '@/components/ui/icon';
import SettingsPanel from './SettingsPanel';
import RichTextEditor from './RichTextEditor';
import { filterFieldGroupsByType, flattenFieldGroups, LINK_FIELD_TYPES } from '@/lib/collection-field-utils';
import { filterFieldGroupsByType, flattenFieldGroups, LINK_FIELD_TYPES, buildReferenceItemOptions } from '@/lib/collection-field-utils';
import { generateLinkHref } from '@/lib/link-utils';
import LinkItemOptions from './LinkItemOptions';
import { FieldSelectDropdown, type FieldGroup, type FieldSourceType } from './CollectionFieldSelector';
import ComponentVariableLabel, { VARIABLE_TYPE_ICONS } from './ComponentVariableLabel';
import {
Expand Down Expand Up @@ -239,6 +241,12 @@ export default function LinkSettings(props: LinkSettingsProps) {
const currentPage = currentPageId ? pages.find(p => p.id === currentPageId) : null;
const isCurrentPageDynamic = currentPage?.is_dynamic || false;

// "Current page item" only makes sense when both pages use the same collection
const currentPageCollectionId = currentPage?.settings?.cms?.collection_id || null;
const targetPageCollectionId = selectedPage?.settings?.cms?.collection_id || null;
const canUseCurrentPageItem = isDynamicPage && isCurrentPageDynamic
&& !!currentPageCollectionId && currentPageCollectionId === targetPageCollectionId;

// Check if the layer itself is a collection layer
const isCollectionLayer = !!(layer && getCollectionVariable(layer));

Expand All @@ -261,6 +269,12 @@ export default function LinkSettings(props: LinkSettingsProps) {
const hasCollectionFields = !!(collectionGroup && collectionGroup.fields.length > 0 && isInsideCollectionLayer);
const canUseCurrentCollectionItem = hasCollectionFields || isCollectionLayer;

// Find reference fields that point to the target page's collection
const referenceItemOptions = useMemo(
() => buildReferenceItemOptions(isDynamicPage, targetPageCollectionId, fieldGroups),
[isDynamicPage, targetPageCollectionId, fieldGroups]
);

// Get collection ID from dynamic page settings
const pageCollectionId = selectedPage?.settings?.cms?.collection_id || null;

Expand Down Expand Up @@ -646,27 +660,10 @@ export default function LinkSettings(props: LinkSettingsProps) {
// Get asset info for display
const selectedAsset = assetId ? getAsset(assetId) : null;

// Get display name for selected collection item
const getItemDisplayName = useCallback(
(itemId: string) => {
if (itemId === 'current') return 'Current Item';
const item = collectionItems.find((i) => i.id === itemId);
if (!item) return itemId;

// Get fields from store for the page's collection
const collectionFields = pageCollectionId ? collectionsStoreFields[pageCollectionId] : [];

// Find the field with key === 'name'
const nameField = collectionFields?.find((field) => field.key === 'name');
if (nameField && item.values[nameField.id]) {
return item.values[nameField.id];
}

// Fall back to first available value
const values = Object.values(item.values);
return values[0] || itemId;
},
[collectionItems, pageCollectionId, collectionsStoreFields]
// Fields for the linked page's collection (for display names)
const linkedPageCollectionFields = useMemo(
() => pageCollectionId ? collectionsStoreFields[pageCollectionId] || [] : [],
[pageCollectionId, collectionsStoreFields]
);

// Layer mode requires a layer
Expand Down Expand Up @@ -903,28 +900,13 @@ export default function LinkSettings(props: LinkSettingsProps) {
<SelectValue placeholder={loadingItems ? 'Loading...' : 'Select...'} />
</SelectTrigger>
<SelectContent>
{/* Current page item option (when on a dynamic page AND linking to a dynamic page) */}
{isDynamicPage && isCurrentPageDynamic && (
<SelectItem value="current-page">
<div className="flex items-center gap-2">
Current page item
</div>
</SelectItem>
)}
{/* Current collection item option (when inside a collection layer OR when the layer IS a collection layer) */}
{canUseCurrentCollectionItem && (
<SelectItem value="current-collection">
<div className="flex items-center gap-2">
Current collection item
</div>
</SelectItem>
)}
{((isDynamicPage && isCurrentPageDynamic) || canUseCurrentCollectionItem) && <SelectSeparator />}
{collectionItems.map((item) => (
<SelectItem key={item.id} value={item.id}>
{getItemDisplayName(item.id)}
</SelectItem>
))}
<LinkItemOptions
canUseCurrentPageItem={canUseCurrentPageItem}
canUseCurrentCollectionItem={canUseCurrentCollectionItem}
referenceItemOptions={referenceItemOptions}
collectionItems={collectionItems}
collectionFields={linkedPageCollectionFields}
/>
</SelectContent>
</Select>
</div>
Expand Down
5 changes: 5 additions & 0 deletions app/ycode/components/RichTextEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ interface RichTextEditorProps {
size?: 'xs' | 'sm';
/** Link types to exclude from the link settings dropdown */
excludedLinkTypes?: LinkType[];
/** Hide "Current page item" and "Reference field" options (e.g. when editing CMS item content) */
hidePageContextOptions?: boolean;
/** Stretch editor to fill parent height (scrolls content instead of growing) */
fullHeight?: boolean;
/** Callback to open the full editor sheet (shown as expand button in toolbar) */
Expand Down Expand Up @@ -320,6 +322,7 @@ const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorProps>(({
variant = 'compact',
size = 'xs',
excludedLinkTypes = [],
hidePageContextOptions = false,
fullHeight = false,
onExpandClick,
allowedFieldTypes = RICH_TEXT_ONLY_FIELD_TYPES,
Expand Down Expand Up @@ -813,6 +816,7 @@ const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorProps>(({
open={linkPopoverOpen}
onOpenChange={setLinkPopoverOpen}
excludedLinkTypes={excludedLinkTypes}
hidePageContextOptions={hidePageContextOptions}
trigger={
<ToggleGroupItem
value="link"
Expand Down Expand Up @@ -1252,6 +1256,7 @@ const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorProps>(({
open={linkPopoverOpen}
onOpenChange={setLinkPopoverOpen}
excludedLinkTypes={excludedLinkTypes}
hidePageContextOptions={hidePageContextOptions}
trigger={
<Button
type="button"
Expand Down
4 changes: 4 additions & 0 deletions app/ycode/components/RichTextEditorSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ interface RichTextEditorSheetProps {
fieldGroups?: FieldGroup[];
allFields?: Record<string, CollectionField[]>;
collections?: Collection[];
/** Hide "Current page item" and "Reference field" options */
hidePageContextOptions?: boolean;
}

export default function RichTextEditorSheet({
Expand All @@ -44,6 +46,7 @@ export default function RichTextEditorSheet({
fieldGroups,
allFields,
collections,
hidePageContextOptions = false,
}: RichTextEditorSheetProps) {
return (
<Sheet open={open} onOpenChange={onOpenChange}>
Expand Down Expand Up @@ -83,6 +86,7 @@ export default function RichTextEditorSheet({
variant="full"
fullHeight
allowedFieldTypes={RICH_TEXT_FIELD_TYPES}
hidePageContextOptions={hidePageContextOptions}
/>
</SheetContent>
</Sheet>
Expand Down
4 changes: 4 additions & 0 deletions app/ycode/components/RichTextLinkPopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ export interface RichTextLinkPopoverProps {
disabled?: boolean;
/** Link types to exclude from the dropdown */
excludedLinkTypes?: LinkType[];
/** Hide "Current page item" and "Reference field" options (e.g. when editing CMS item content) */
hidePageContextOptions?: boolean;
}

/**
Expand All @@ -59,6 +61,7 @@ export default function RichTextLinkPopover({
onOpenChange: controlledOnOpenChange,
disabled = false,
excludedLinkTypes = [],
hidePageContextOptions = false,
}: RichTextLinkPopoverProps) {
// Use controlled state if provided, otherwise internal state
const [internalOpen, setInternalOpen] = useState(false);
Expand Down Expand Up @@ -268,6 +271,7 @@ export default function RichTextLinkPopover({
isInsideCollectionLayer={isInsideCollectionLayer}
layer={layer}
excludedLinkTypes={excludedLinkTypes}
hidePageContextOptions={hidePageContextOptions}
/>
</div>

Expand Down
Loading