11import { nanoid } from "nanoid" ;
2- import { getStyleDeclKey , Instance , type StyleSource } from "@webstudio-is/sdk" ;
3- import { generateDataFromEmbedTemplate } from "@webstudio-is/react-sdk" ;
2+ import {
3+ getStyleDeclKey ,
4+ Instance ,
5+ isComponentDetachable ,
6+ type StyleSource ,
7+ } from "@webstudio-is/sdk" ;
48import type { copywriter , operations } from "@webstudio-is/ai" ;
59import { serverSyncStore } from "~/shared/sync" ;
610import { isBaseBreakpoint } from "~/shared/breakpoints" ;
711import {
812 deleteInstanceMutable ,
13+ findClosestInsertable ,
914 insertWebstudioFragmentAt ,
1015 updateWebstudioData ,
1116 type Insertable ,
1217} from "~/shared/instance-utils" ;
1318import {
1419 $breakpoints ,
1520 $instances ,
21+ $props ,
1622 $registeredComponentMetas ,
1723 $selectedInstanceSelector ,
1824 $styleSourceSelections ,
@@ -21,7 +27,8 @@ import {
2127} from "~/shared/nano-states" ;
2228import type { InstanceSelector } from "~/shared/tree-utils" ;
2329import { $selectedInstance , getInstancePath } from "~/shared/awareness" ;
24- import { isInstanceDetachable } from "~/shared/matcher" ;
30+ import { isRichTextTree } from "~/shared/content-model" ;
31+ import { generateDataFromEmbedTemplate } from "./embed-template" ;
2532
2633export const applyOperations = ( operations : operations . WsOperations ) => {
2734 for ( const operation of operations ) {
@@ -47,7 +54,7 @@ const insertTemplateByOp = (
4754 operation : operations . generateInsertTemplateWsOperation
4855) => {
4956 const metas = $registeredComponentMetas . get ( ) ;
50- const templateData = generateDataFromEmbedTemplate ( operation . template , metas ) ;
57+ const fragment = generateDataFromEmbedTemplate ( operation . template , metas ) ;
5158
5259 // @todo Find a way to avoid the workaround below, peharps improving the prompt.
5360 // Occasionally the LLM picks a component name or the entire data-ws-id attribute as the insertion point.
@@ -63,26 +70,18 @@ const insertTemplateByOp = (
6370 }
6471 }
6572
66- const rootInstanceIds = templateData . children
73+ const rootInstanceIds = fragment . children
6774 . filter ( ( child ) => child . type === "id" )
6875 . map ( ( child ) => child . value ) ;
6976
7077 const instanceSelector = computeSelectorForInstanceId ( operation . addTo ) ;
7178 if ( instanceSelector ) {
72- const currentInstance = $instances . get ( ) . get ( instanceSelector [ 0 ] ) ;
73- // Only container components are allowed to have child elements.
74- if (
75- currentInstance &&
76- metas . get ( currentInstance . component ) ?. type !== "container"
77- ) {
78- return ;
79- }
80-
81- const dropTarget : Insertable = {
79+ let insertable : Insertable = {
8280 parentSelector : instanceSelector ,
8381 position : operation . addAtIndex + 1 ,
8482 } ;
85- insertWebstudioFragmentAt ( templateData , dropTarget ) ;
83+ insertable = findClosestInsertable ( fragment , insertable ) ?? insertable ;
84+ insertWebstudioFragmentAt ( fragment , insertable ) ;
8685 return rootInstanceIds ;
8786 }
8887} ;
@@ -96,15 +95,10 @@ const deleteInstanceByOp = (
9695 if ( instanceSelector . length === 1 ) {
9796 return ;
9897 }
99- const metas = $registeredComponentMetas . get ( ) ;
10098 updateWebstudioData ( ( data ) => {
101- if (
102- isInstanceDetachable ( {
103- metas,
104- instances : data . instances ,
105- instanceSelector,
106- } ) === false
107- ) {
99+ const [ instanceId ] = instanceSelector ;
100+ const instance = data . instances . get ( instanceId ) ;
101+ if ( instance && ! isComponentDetachable ( instance . component ) ) {
108102 return ;
109103 }
110104 deleteInstanceMutable (
@@ -214,39 +208,47 @@ const computeSelectorForInstanceId = (instanceId: Instance["id"]) => {
214208} ;
215209
216210export const patchTextInstance = ( textInstance : copywriter . TextInstance ) => {
217- serverSyncStore . createTransaction ( [ $instances ] , ( instances ) => {
218- const currentInstance = instances . get ( textInstance . instanceId ) ;
219-
220- if ( currentInstance === undefined ) {
221- return ;
222- }
223-
224- const meta = $registeredComponentMetas . get ( ) . get ( currentInstance . component ) ;
211+ serverSyncStore . createTransaction (
212+ [ $instances , $props ] ,
213+ ( instances , props ) => {
214+ const currentInstance = instances . get ( textInstance . instanceId ) ;
225215
226- // Only container components are allowed to have child elements.
227- if ( meta ?. type !== "container" ) {
228- return ;
229- }
216+ if ( currentInstance === undefined ) {
217+ return ;
218+ }
230219
231- if ( currentInstance . children . length === 0 ) {
232- currentInstance . children = [ { type : "text" , value : textInstance . text } ] ;
233- return ;
234- }
220+ const canBeEdited = isRichTextTree ( {
221+ instanceId : textInstance . instanceId ,
222+ instances,
223+ props,
224+ metas : $registeredComponentMetas . get ( ) ,
225+ } ) ;
226+ if ( ! canBeEdited ) {
227+ return ;
228+ }
235229
236- // Instances can have a number of text child nodes without interleaving components.
237- // When this is the case we treat the child nodes as a single text node,
238- // otherwise the AI would generate children.length chunks of separate text.
239- // We can identify this case of "joint" text instances when the index is -1.
240- const replaceAll = textInstance . index === - 1 ;
241- if ( replaceAll ) {
242- if ( currentInstance . children . every ( ( child ) => child . type === "text" ) ) {
230+ if ( currentInstance . children . length === 0 ) {
243231 currentInstance . children = [ { type : "text" , value : textInstance . text } ] ;
232+ return ;
244233 }
245- return ;
246- }
247234
248- if ( currentInstance . children [ textInstance . index ] . type === "text" ) {
249- currentInstance . children [ textInstance . index ] . value = textInstance . text ;
235+ // Instances can have a number of text child nodes without interleaving components.
236+ // When this is the case we treat the child nodes as a single text node,
237+ // otherwise the AI would generate children.length chunks of separate text.
238+ // We can identify this case of "joint" text instances when the index is -1.
239+ const replaceAll = textInstance . index === - 1 ;
240+ if ( replaceAll ) {
241+ if ( currentInstance . children . every ( ( child ) => child . type === "text" ) ) {
242+ currentInstance . children = [
243+ { type : "text" , value : textInstance . text } ,
244+ ] ;
245+ }
246+ return ;
247+ }
248+
249+ if ( currentInstance . children [ textInstance . index ] . type === "text" ) {
250+ currentInstance . children [ textInstance . index ] . value = textInstance . text ;
251+ }
250252 }
251- } ) ;
253+ ) ;
252254} ;
0 commit comments