site-builder: dynamic CTAs, section anchors, edit-with-Sitesmith
Three related features: 1. Dynamic CTA buttons on HeroSimple, CTASection, CallToAction. New shared ctas[] array (text + href + variant + target) replaces the primary/secondary pair. Settings panel gets add/remove/reorder controls. Legacy fields stay readable for backwards compat — first user edit migrates the section onto the new array. 2. Anchor IDs on all layout/section components (Container, Section, BackgroundSection, ColumnLayout, plus 6 section blocks done by parallel subagent, plus Hero/CTA/CallToAction). Anchor input lives in the settings panel with an "auto from heading" button that walks the subtree for the first Heading.text. Renders as id="..." on the outermost element so #anchor URLs resolve. 3. Edit-with-Sitesmith targeted invocation. Right-click → "Ask Sitesmith" and a button at the top of the right-side settings panel both open the modal pre-targeted at the selected node. The node's serialized subtree is sent to the server; system prompt is augmented to require a patch with replace_node. Editor lifts modal state into a new SitesmithContext. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
import { SitesmithTarget } from '../state/SitesmithContext';
|
||||
|
||||
/**
|
||||
* Build a Sitesmith target descriptor from a Craft.js node id. The returned
|
||||
* `treeJson` is a flat node-map (compatible with the editor's serialized
|
||||
* format) for just the selected subtree; the server includes it in the AI
|
||||
* prompt so the model has the exact current shape of the block to modify.
|
||||
*/
|
||||
export function buildSitesmithTarget(query: any, nodeId: string): SitesmithTarget | null {
|
||||
if (!nodeId || nodeId === 'ROOT') return null;
|
||||
try {
|
||||
const node = query.node(nodeId).get();
|
||||
const displayName = node?.data?.displayName || node?.data?.type?.resolvedName || 'Block';
|
||||
// Use Craft.js' own subtree serializer — toNodeTree gives a flat map keyed
|
||||
// by node id, identical to what `actions.deserialize()` consumes.
|
||||
const subtree = query.node(nodeId).toNodeTree();
|
||||
const serializedMap: Record<string, unknown> = {};
|
||||
for (const [id, n] of Object.entries(subtree.nodes ?? {}) as [string, any][]) {
|
||||
serializedMap[id] = {
|
||||
type: n.data.type,
|
||||
props: n.data.props,
|
||||
displayName: n.data.displayName,
|
||||
isCanvas: n.data.isCanvas ?? false,
|
||||
parent: n.data.parent,
|
||||
nodes: n.data.nodes ?? [],
|
||||
linkedNodes: n.data.linkedNodes ?? {},
|
||||
hidden: n.data.hidden ?? false,
|
||||
custom: n.data.custom ?? {},
|
||||
};
|
||||
}
|
||||
return {
|
||||
nodeId,
|
||||
displayName,
|
||||
treeJson: JSON.stringify({ root: subtree.rootNodeId, nodes: serializedMap }),
|
||||
};
|
||||
} catch (e) {
|
||||
console.warn('buildSitesmithTarget failed:', e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user