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:
@@ -167,7 +167,16 @@ export function useApplyAiResponse() {
|
||||
const { actions, query } = useEditor();
|
||||
const pages = usePages();
|
||||
|
||||
return async function apply(resp: SitesmithResponse): Promise<{ ok: boolean; message?: string }> {
|
||||
/**
|
||||
* @param targetNodeId If set and the AI returned a section-scoped replace
|
||||
* instead of a patch, treat the first returned tree as a replacement for
|
||||
* this node (the user said "edit this block" — they don't want a new
|
||||
* section appended at the bottom).
|
||||
*/
|
||||
return async function apply(
|
||||
resp: SitesmithResponse,
|
||||
targetNodeId?: string,
|
||||
): Promise<{ ok: boolean; message?: string }> {
|
||||
// 'ask' type = AI wants clarification, nothing to apply
|
||||
if (resp.type === 'ask') return { ok: true };
|
||||
|
||||
@@ -185,6 +194,21 @@ export function useApplyAiResponse() {
|
||||
}
|
||||
|
||||
if (resp.scope === 'section') {
|
||||
// When targeted at a specific node, the AI's tree replaces that node
|
||||
// in place (vs appending a fresh section at the end of ROOT).
|
||||
if (targetNodeId && resp.pages.length > 0) {
|
||||
try {
|
||||
const nodeTree = buildNodeTree(query, resp.pages[0].tree);
|
||||
const parent: string = query.node(targetNodeId).get().data.parent ?? 'ROOT';
|
||||
const siblings: string[] = query.node(parent).childNodes();
|
||||
const index = siblings.indexOf(targetNodeId);
|
||||
actions.delete(targetNodeId);
|
||||
actions.addNodeTree(nodeTree, parent, index);
|
||||
return { ok: true };
|
||||
} catch (e) {
|
||||
console.warn('sitesmith: targeted section replace failed, falling back to append', e);
|
||||
}
|
||||
}
|
||||
// Insert each provided tree as a new node tree appended to ROOT
|
||||
for (const p of resp.pages) {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user