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
6 changes: 3 additions & 3 deletions app/ycode/components/LayersTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { useAuthStore } from '@/stores/useAuthStore';
// 6. Utils/lib
import { cn } from '@/lib/utils';
import { flattenTree, type FlattenedItem } from '@/lib/tree-utilities';
import { canHaveChildren, getLayerIcon, getLayerName, getCollectionVariable, isTextContentLayer, isRichTextLayer, getRichTextSublayers, getTextStyleSublayers, canMoveLayer, updateLayerProps, filterDisabledSliderLayers, getLayerCmsFieldBinding, extractBlockText } from '@/lib/layer-utils';
import { canHaveChildren, getLayerIcon, getLayerName, getCollectionVariable, isTextContentLayer, isRichTextLayer, hasRichTextContent, getRichTextSublayers, getTextStyleSublayers, canMoveLayer, updateLayerProps, filterDisabledSliderLayers, getLayerCmsFieldBinding, extractBlockText } from '@/lib/layer-utils';
import { getBlockName } from '@/lib/templates/blocks';
import { MULTI_ASSET_COLLECTION_ID } from '@/lib/collection-field-utils';
import { hasStyleOverrides } from '@/lib/layer-style-utils';
Expand Down Expand Up @@ -252,7 +252,7 @@ const LayerRow = React.memo(function LayerRow({
// Component instances should not show children in the tree (unless editing master)
// Children can only be edited via "Edit master component"
const shouldHideChildren = isComponentInstance && !editingComponentId;
const hasContentSublayers = isRichTextLayer(node.layer) && getRichTextSublayers(node.layer).length > 0;
const hasContentSublayers = hasRichTextContent(node.layer);
const hasStyleSublayers = isTextContentLayer(node.layer) && getTextStyleSublayers(node.layer).length > 0;
const hasSublayers = hasContentSublayers || hasStyleSublayers;
const effectiveHasChildren = (hasChildren && !shouldHideChildren) || hasSublayers;
Expand Down Expand Up @@ -1883,7 +1883,7 @@ export default function LayersTree({
hasVisibleChildren = node.canHaveChildren && !collapsedIds.has(node.id);
} else {
// Real layer nodes: check actual children and sublayer presence
const hasAnySublayers = (isRichTextLayer(node.layer) && getRichTextSublayers(node.layer).length > 0)
const hasAnySublayers = hasRichTextContent(node.layer)
|| (isTextContentLayer(node.layer) && getTextStyleSublayers(node.layer).length > 0);
hasVisibleChildren = (!collapsedIds.has(node.id)) && (
!!(node.layer.children && node.layer.children.length > 0) || hasAnySublayers
Expand Down
15 changes: 15 additions & 0 deletions lib/layer-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,21 @@ function extractInlineMarks(block: any): string[] {
* When a CMS field is bound, pass the resolved CMS content via `cmsContent`
* so sublayers reflect the actual CMS item data.
*/
/**
* Check if a richText layer has content blocks (either its own or via CMS binding).
* Used for determining collapsibility in the layers tree without requiring resolved CMS data.
*/
export function hasRichTextContent(layer: Layer): boolean {
if (!isRichTextLayer(layer)) return false;
const textVar = layer.variables?.text;
if (textVar?.type !== 'dynamic_rich_text') return false;
const layerDoc = (textVar.data as any)?.content;
if (!layerDoc?.content || !Array.isArray(layerDoc.content)) return false;
const binding = getCmsFieldBinding(layerDoc);
if (binding) return true;
return layerDoc.content.some((block: any) => block.type !== 'paragraph' || block.content?.length);
}

export function getRichTextSublayers(layer: Layer, cmsContent?: any): RichTextSublayer[] {
const textVar = layer.variables?.text;
if (textVar?.type !== 'dynamic_rich_text') return [];
Expand Down
18 changes: 11 additions & 7 deletions lib/text-format-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -442,8 +442,8 @@ function renderTextNode(
/**
* Render nested rich text content from a Tiptap JSON structure.
* Used when a rich_text CMS field is inserted as an inline variable.
* Delegates to renderBlock with useSpanForParagraphs=true since this
* content is always nested inside another element.
* When useSpanForParagraphs is true (default), block elements render as spans
* to avoid invalid HTML nesting inside restrictive tags like <p> or <h1>.
*/
function renderNestedRichTextContent(
richTextValue: any,
Expand All @@ -458,6 +458,7 @@ function renderNestedRichTextContent(
components?: Component[],
renderComponentBlock?: RenderComponentBlockFn,
ancestorComponentIds?: Set<string>,
useSpanForParagraphs = true,
): React.ReactNode[] {
if (!richTextValue) {
return [];
Expand All @@ -478,7 +479,7 @@ function renderNestedRichTextContent(

if (parsed.type === 'doc' && Array.isArray(parsed.content)) {
return parsed.content.map((block: any, blockIdx: number) =>
renderBlock(block, blockIdx, collectionItemData, pageCollectionItemData, textStyles, true, isEditMode, linkContext, timezone, layerDataMap, components, renderComponentBlock, ancestorComponentIds)
renderBlock(block, blockIdx, collectionItemData, pageCollectionItemData, textStyles, useSpanForParagraphs, isEditMode, linkContext, timezone, layerDataMap, components, renderComponentBlock, ancestorComponentIds)
).filter(Boolean);
}

Expand All @@ -500,6 +501,7 @@ function renderInlineContent(
components?: Component[],
renderComponentBlock?: RenderComponentBlockFn,
ancestorComponentIds?: Set<string>,
useSpanForParagraphs = true,
): React.ReactNode[] {
return content.flatMap((node, idx) => {
const key = `node-${idx}`;
Expand Down Expand Up @@ -537,6 +539,7 @@ function renderInlineContent(
components,
renderComponentBlock,
ancestorComponentIds,
useSpanForParagraphs,
);
}
}
Expand Down Expand Up @@ -699,7 +702,7 @@ function renderBlock(
);
const tag = hasBlockContent ? 'div' : useSpanForParagraphs ? 'span' : 'p';

return React.createElement(tag, paragraphProps, ...renderInlineContent(block.content, collectionItemData, pageCollectionItemData, textStyles, isEditMode, linkContext, timezone, layerDataMap, components, renderComponentBlock, ancestorComponentIds));
return React.createElement(tag, paragraphProps, ...renderInlineContent(block.content, collectionItemData, pageCollectionItemData, textStyles, isEditMode, linkContext, timezone, layerDataMap, components, renderComponentBlock, ancestorComponentIds, useSpanForParagraphs));
}

if (block.type === 'heading') {
Expand All @@ -718,7 +721,7 @@ function renderBlock(
if (isEditMode) {
headingProps['data-style'] = styleKey;
}
return React.createElement(tag, headingProps, ...renderInlineContent(block.content, collectionItemData, pageCollectionItemData, textStyles, isEditMode, linkContext, timezone, layerDataMap, components, renderComponentBlock, ancestorComponentIds));
return React.createElement(tag, headingProps, ...renderInlineContent(block.content, collectionItemData, pageCollectionItemData, textStyles, isEditMode, linkContext, timezone, layerDataMap, components, renderComponentBlock, ancestorComponentIds, useSpanForParagraphs));
}

if (block.type === 'bulletList') {
Expand Down Expand Up @@ -935,7 +938,8 @@ export function renderRichText(
paragraph.content[0].attrs?.variable?.data?.field_type === 'rich_text';

if (hasSoleRichTextVariable) {
const inlineContent = renderInlineContent(paragraph.content, collectionItemData, pageCollectionItemData, textStyles, isEditMode, linkContext, timezone, layerDataMap, components, renderComponentBlock, ancestorComponentIds);
const nestedUseSpans = isSimpleTextElement ? true : useSpanForParagraphs;
const inlineContent = renderInlineContent(paragraph.content, collectionItemData, pageCollectionItemData, textStyles, isEditMode, linkContext, timezone, layerDataMap, components, renderComponentBlock, ancestorComponentIds, nestedUseSpans);
return Array.isArray(inlineContent) ? inlineContent : [inlineContent];
}

Expand All @@ -946,7 +950,7 @@ export function renderRichText(
}
return null;
}
const inlineContent = renderInlineContent(paragraph.content, collectionItemData, pageCollectionItemData, textStyles, isEditMode, linkContext, timezone, layerDataMap, components, renderComponentBlock, ancestorComponentIds);
const inlineContent = renderInlineContent(paragraph.content, collectionItemData, pageCollectionItemData, textStyles, isEditMode, linkContext, timezone, layerDataMap, components, renderComponentBlock, ancestorComponentIds, useSpanForParagraphs);
if (isEditMode && !isSimpleTextElement) {
const paragraphClass = textStyles?.paragraph?.classes ?? DEFAULT_TEXT_STYLES.paragraph?.classes ?? '';
const children = Array.isArray(inlineContent) ? inlineContent : [inlineContent];
Expand Down