@elementor/editor-canvas 4.1.0-802 → 4.1.0-804
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +7 -2
- package/dist/index.d.ts +7 -2
- package/dist/index.js +647 -95
- package/dist/index.mjs +596 -40
- package/package.json +18 -18
- package/src/index.ts +1 -0
- package/src/legacy/create-templated-element-type.ts +2 -2
- package/src/legacy/types.ts +4 -0
- package/src/mcp/canvas-mcp.ts +8 -0
- package/src/mcp/resources/available-widgets-resource.ts +67 -0
- package/src/mcp/resources/document-structure-resource.ts +51 -36
- package/src/mcp/resources/editor-state-resource.ts +122 -0
- package/src/mcp/resources/general-context-resource.ts +99 -0
- package/src/mcp/resources/selected-element-resource.ts +217 -0
- package/src/mcp/resources/widgets-schema-resource.ts +71 -6
- package/src/mcp/tools/build-composition/prompt.ts +6 -0
- package/src/mcp/tools/build-composition/tool.ts +26 -0
- package/src/mcp/tools/configure-element/tool.ts +12 -0
- package/src/mcp/tools/get-element-config/tool.ts +13 -3
- package/src/mcp/utils/element-data-util.ts +46 -0
- package/src/utils/after-render.ts +26 -0
- package/src/mcp/utils/generate-available-tags.ts +0 -23
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { getContainer, getSelectedElements, getWidgetsCache, type V1Element } from '@elementor/editor-elements';
|
|
2
|
+
import { type MCPRegistryEntry } from '@elementor/editor-mcp';
|
|
3
|
+
import {
|
|
4
|
+
__privateListenTo as listenTo,
|
|
5
|
+
commandEndEvent,
|
|
6
|
+
type CommandEvent,
|
|
7
|
+
type ListenerEvent,
|
|
8
|
+
} from '@elementor/editor-v1-adapters';
|
|
9
|
+
|
|
10
|
+
export const SELECTED_ELEMENT_URI = 'elementor://context/selected-element';
|
|
11
|
+
|
|
12
|
+
type WidgetVersion = 'v3' | 'v4';
|
|
13
|
+
|
|
14
|
+
type SelectionContainer = V1Element & {
|
|
15
|
+
type?: string;
|
|
16
|
+
label?: string;
|
|
17
|
+
model: V1Element[ 'model' ] & {
|
|
18
|
+
config?: { atomic?: boolean };
|
|
19
|
+
};
|
|
20
|
+
settings: V1Element[ 'settings' ] & {
|
|
21
|
+
toJSON?: () => Record< string, unknown >;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type SelectedElementPayload = {
|
|
26
|
+
elementDisplayName: string | null;
|
|
27
|
+
elementType: string | null;
|
|
28
|
+
properties: Record< string, unknown > | null;
|
|
29
|
+
selectedElementId: string | null;
|
|
30
|
+
selectedParentId: string | null;
|
|
31
|
+
version: WidgetVersion | null;
|
|
32
|
+
widgetType: string | null;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const initSelectedElementResource = ( reg: MCPRegistryEntry ) => {
|
|
36
|
+
const { resource, sendResourceUpdated } = reg;
|
|
37
|
+
|
|
38
|
+
let currentPayloadText: string | null = null;
|
|
39
|
+
|
|
40
|
+
const publishIfChanged = ( payload: SelectedElementPayload ) => {
|
|
41
|
+
const nextText = JSON.stringify( payload );
|
|
42
|
+
|
|
43
|
+
if ( nextText !== currentPayloadText ) {
|
|
44
|
+
currentPayloadText = nextText;
|
|
45
|
+
sendResourceUpdated( { uri: SELECTED_ELEMENT_URI } );
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const onCommand = ( e: ListenerEvent ) => {
|
|
50
|
+
if ( e.type !== 'command' ) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const commandEvent = e as CommandEvent< { container?: SelectionContainer } >;
|
|
55
|
+
|
|
56
|
+
if ( commandEvent.command === 'document/elements/deselect-all' ) {
|
|
57
|
+
publishIfChanged( createEmptySelectedElementPayload() );
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (
|
|
62
|
+
commandEvent.command !== 'document/elements/select' &&
|
|
63
|
+
commandEvent.command !== 'document/elements/settings'
|
|
64
|
+
) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const { container } = commandEvent.args || {};
|
|
69
|
+
|
|
70
|
+
if ( container?.id ) {
|
|
71
|
+
publishIfChanged( buildPayloadFromContainer( container ) );
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
publishIfChanged( readSelectionFromEditor() );
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
listenTo(
|
|
79
|
+
[
|
|
80
|
+
commandEndEvent( 'document/elements/select' ),
|
|
81
|
+
commandEndEvent( 'document/elements/deselect-all' ),
|
|
82
|
+
commandEndEvent( 'document/elements/settings' ),
|
|
83
|
+
],
|
|
84
|
+
onCommand
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
publishIfChanged( readSelectionFromEditor() );
|
|
88
|
+
|
|
89
|
+
resource(
|
|
90
|
+
'selected-element',
|
|
91
|
+
SELECTED_ELEMENT_URI,
|
|
92
|
+
{
|
|
93
|
+
description: 'Currently selected Elementor element context.',
|
|
94
|
+
},
|
|
95
|
+
async () => {
|
|
96
|
+
return {
|
|
97
|
+
contents: [
|
|
98
|
+
{
|
|
99
|
+
uri: SELECTED_ELEMENT_URI,
|
|
100
|
+
text: JSON.stringify( readSelectionFromEditor(), null, 2 ),
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
function createEmptySelectedElementPayload(): SelectedElementPayload {
|
|
109
|
+
return {
|
|
110
|
+
elementDisplayName: null,
|
|
111
|
+
elementType: null,
|
|
112
|
+
properties: null,
|
|
113
|
+
selectedElementId: null,
|
|
114
|
+
selectedParentId: null,
|
|
115
|
+
version: null,
|
|
116
|
+
widgetType: null,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function readSelectionFromEditor(): SelectedElementPayload {
|
|
121
|
+
const elements = getSelectedElements();
|
|
122
|
+
|
|
123
|
+
if ( elements.length !== 1 ) {
|
|
124
|
+
return createEmptySelectedElementPayload();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const container = getContainer( elements[ 0 ].id );
|
|
128
|
+
|
|
129
|
+
return buildPayloadFromContainer( container );
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function buildPayloadFromContainer( container: SelectionContainer | null ): SelectedElementPayload {
|
|
133
|
+
if ( ! container?.id ) {
|
|
134
|
+
return createEmptySelectedElementPayload();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const widgetType = container.model.get( 'widgetType' ) ?? null;
|
|
138
|
+
const elementType = container.type ?? 'widget';
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
elementDisplayName: getElementDisplayName( container ),
|
|
142
|
+
elementType,
|
|
143
|
+
properties: getElementProperties( container, widgetType ),
|
|
144
|
+
selectedElementId: container.id,
|
|
145
|
+
selectedParentId: container.parent?.id ?? null,
|
|
146
|
+
version: resolveElementVersion( container, widgetType ),
|
|
147
|
+
widgetType,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function resolveElementVersion( container: SelectionContainer, widgetType: string | null ): WidgetVersion {
|
|
152
|
+
if ( container.model?.config?.atomic ) {
|
|
153
|
+
return 'v4';
|
|
154
|
+
}
|
|
155
|
+
if ( widgetType && getWidgetsCache()?.[ widgetType ]?.atomic_props_schema ) {
|
|
156
|
+
return 'v4';
|
|
157
|
+
}
|
|
158
|
+
return 'v3';
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function getElementProperties(
|
|
162
|
+
container: SelectionContainer,
|
|
163
|
+
widgetType: string | null
|
|
164
|
+
): Record< string, unknown > | null {
|
|
165
|
+
const settings = container.settings?.toJSON?.();
|
|
166
|
+
if ( ! settings || typeof settings !== 'object' ) {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const widgetConfig = widgetType ? getWidgetsCache()?.[ widgetType ] : null;
|
|
171
|
+
const controls = widgetConfig?.controls as Record< string, { default?: unknown } > | undefined;
|
|
172
|
+
|
|
173
|
+
const filtered: Record< string, unknown > = {};
|
|
174
|
+
|
|
175
|
+
for ( const [ key, value ] of Object.entries( settings ) ) {
|
|
176
|
+
if ( value === undefined || value === null || value === '' ) {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const controlDefault = controls?.[ key ]?.default;
|
|
181
|
+
if ( controlDefault !== undefined && JSON.stringify( value ) === JSON.stringify( controlDefault ) ) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
filtered[ key ] = value;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return Object.keys( filtered ).length > 0 ? filtered : null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function getElementDisplayName( container: SelectionContainer ): string {
|
|
192
|
+
try {
|
|
193
|
+
if ( container.label ) {
|
|
194
|
+
return container.label;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const widgetType = container.model?.get?.( 'widgetType' );
|
|
198
|
+
|
|
199
|
+
if ( widgetType ) {
|
|
200
|
+
const capitalizedType = widgetType.charAt( 0 ).toUpperCase() + widgetType.slice( 1 );
|
|
201
|
+
|
|
202
|
+
return capitalizedType.replace( /-/g, ' ' );
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if ( container.type === 'container' ) {
|
|
206
|
+
return 'Container';
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if ( container.type === 'section' ) {
|
|
210
|
+
return 'Section';
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return `Element ${ container.id }`;
|
|
214
|
+
} catch {
|
|
215
|
+
return `Element ${ container.id }`;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
@@ -11,6 +11,50 @@ import {
|
|
|
11
11
|
} from '@elementor/editor-props';
|
|
12
12
|
import { getStylesSchema } from '@elementor/editor-styles';
|
|
13
13
|
|
|
14
|
+
import { hasV3Controls, isWidgetAvailableForLLM } from '../utils/element-data-util';
|
|
15
|
+
|
|
16
|
+
const V3_LAYOUT_CONTROL_TYPES = new Set( [ 'section', 'tab', 'tabs' ] );
|
|
17
|
+
|
|
18
|
+
type V3ControlMetadataEntry = {
|
|
19
|
+
default?: unknown;
|
|
20
|
+
type?: string;
|
|
21
|
+
options?: unknown;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function extractV3ControlsMetadata( controls: unknown ): Record< string, V3ControlMetadataEntry > {
|
|
25
|
+
if ( ! hasV3Controls( controls ) ) {
|
|
26
|
+
return {};
|
|
27
|
+
}
|
|
28
|
+
const result: Record< string, V3ControlMetadataEntry > = {};
|
|
29
|
+
for ( const [ controlKey, raw ] of Object.entries( controls as Record< string, unknown > ) ) {
|
|
30
|
+
if ( ! raw || typeof raw !== 'object' ) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const control = raw as Record< string, unknown >;
|
|
34
|
+
const controlType = typeof control.type === 'string' ? control.type : undefined;
|
|
35
|
+
if ( controlType && V3_LAYOUT_CONTROL_TYPES.has( controlType ) ) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const entry: V3ControlMetadataEntry = {};
|
|
39
|
+
if ( Object.prototype.hasOwnProperty.call( control, 'default' ) ) {
|
|
40
|
+
entry.default = control.default;
|
|
41
|
+
}
|
|
42
|
+
if ( controlType ) {
|
|
43
|
+
entry.type = controlType;
|
|
44
|
+
}
|
|
45
|
+
if ( Object.prototype.hasOwnProperty.call( control, 'options' ) && control.options !== undefined ) {
|
|
46
|
+
const options = control.options;
|
|
47
|
+
if ( options && typeof options === 'object' && ! Array.isArray( options ) ) {
|
|
48
|
+
entry.options = Object.keys( options as Record< string, unknown > );
|
|
49
|
+
} else {
|
|
50
|
+
entry.options = options;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
result[ controlKey ] = entry;
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
|
|
14
58
|
export const WIDGET_SCHEMA_URI = 'elementor://widgets/schema/{widgetType}';
|
|
15
59
|
export const STYLE_SCHEMA_URI = 'elementor://styles/schema/{category}';
|
|
16
60
|
export const BEST_PRACTICES_URI = 'elementor://styles/best-practices';
|
|
@@ -61,7 +105,7 @@ Variables from the user context ARE NOT SUPPORTED AND WILL RESOLVE IN ERROR.
|
|
|
61
105
|
},
|
|
62
106
|
} ),
|
|
63
107
|
{
|
|
64
|
-
description: 'Common styles schema for the specified category',
|
|
108
|
+
description: 'Common styles schema for the specified category (applicable for V4 elements only)',
|
|
65
109
|
},
|
|
66
110
|
async ( uri, variables ) => {
|
|
67
111
|
const category = typeof variables.category === 'string' ? variables.category : variables.category?.[ 0 ];
|
|
@@ -89,10 +133,10 @@ Variables from the user context ARE NOT SUPPORTED AND WILL RESOLVE IN ERROR.
|
|
|
89
133
|
new ResourceTemplate( WIDGET_SCHEMA_URI, {
|
|
90
134
|
list: () => {
|
|
91
135
|
const cache = getWidgetsCache() || {};
|
|
92
|
-
const availableWidgets = Object.keys( cache
|
|
93
|
-
( widgetType )
|
|
94
|
-
cache[ widgetType ]?.atomic_props_schema && cache[ widgetType ].meta?.llm_support !== false
|
|
136
|
+
const availableWidgets = Object.keys( cache ).filter( ( widgetType ) =>
|
|
137
|
+
isWidgetAvailableForLLM( cache[ widgetType ] )
|
|
95
138
|
);
|
|
139
|
+
|
|
96
140
|
return {
|
|
97
141
|
resources: availableWidgets.map( ( widgetType ) => ( {
|
|
98
142
|
uri: `elementor://widgets/schema/${ widgetType }`,
|
|
@@ -108,10 +152,31 @@ Variables from the user context ARE NOT SUPPORTED AND WILL RESOLVE IN ERROR.
|
|
|
108
152
|
const widgetType =
|
|
109
153
|
typeof variables.widgetType === 'string' ? variables.widgetType : variables.widgetType?.[ 0 ];
|
|
110
154
|
const widgetData = getWidgetsCache()?.[ widgetType ];
|
|
111
|
-
|
|
112
|
-
if ( ! propSchema || ! widgetData ) {
|
|
155
|
+
if ( ! widgetData ) {
|
|
113
156
|
throw new Error( `No prop schema found for element type: ${ widgetType }` );
|
|
114
157
|
}
|
|
158
|
+
const propSchema = widgetData.atomic_props_schema;
|
|
159
|
+
if ( ! propSchema ) {
|
|
160
|
+
if ( ! hasV3Controls( widgetData.controls ) ) {
|
|
161
|
+
throw new Error( `No prop schema found for element type: ${ widgetType }` );
|
|
162
|
+
}
|
|
163
|
+
const controlMetadata = extractV3ControlsMetadata( widgetData.controls );
|
|
164
|
+
return {
|
|
165
|
+
contents: [
|
|
166
|
+
{
|
|
167
|
+
uri: uri.toString(),
|
|
168
|
+
mimeType: 'application/json',
|
|
169
|
+
text: JSON.stringify( {
|
|
170
|
+
widget_version: 'v3',
|
|
171
|
+
message:
|
|
172
|
+
'This widget exists in the editor but has no atomic props schema (V4). Use control_metadata as non-authoritative hints from legacy controls.',
|
|
173
|
+
fields_note: 'All settings are optional; there is no JSON schema for this widget type.',
|
|
174
|
+
properties: controlMetadata,
|
|
175
|
+
} ),
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
};
|
|
179
|
+
}
|
|
115
180
|
const asJson = Object.fromEntries(
|
|
116
181
|
Object.entries( propSchema ).map( ( [ key, propType ] ) => [
|
|
117
182
|
key,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { toolPrompts } from '@elementor/editor-mcp';
|
|
2
2
|
|
|
3
|
+
import { AVAILABLE_WIDGETS_URI } from '../../resources/available-widgets-resource';
|
|
4
|
+
|
|
3
5
|
export const generatePrompt = () => {
|
|
4
6
|
const buildCompositionsToolPrompt = toolPrompts( 'build-compositions' );
|
|
5
7
|
|
|
@@ -7,6 +9,10 @@ export const generatePrompt = () => {
|
|
|
7
9
|
# RESOURCES (Read before use)
|
|
8
10
|
- [elementor://global-classes] - Check FIRST for reusable classes
|
|
9
11
|
- [elementor://global-variables] - ONLY use variables defined here
|
|
12
|
+
- [${ AVAILABLE_WIDGETS_URI }/v4]
|
|
13
|
+
|
|
14
|
+
# TOOL SUUPORT
|
|
15
|
+
This tool support v4 elements only
|
|
10
16
|
|
|
11
17
|
# WORKFLOW
|
|
12
18
|
1. Check/create global classes via "create-global-class" tool
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
import { type MCPRegistryEntry } from '@elementor/editor-mcp';
|
|
10
10
|
|
|
11
11
|
import { CompositionBuilder } from '../../../composition-builder/composition-builder';
|
|
12
|
+
import { AVAILABLE_WIDGETS_URI_V4 } from '../../resources/available-widgets-resource';
|
|
12
13
|
import { BEST_PRACTICES_URI, STYLE_SCHEMA_URI, WIDGET_SCHEMA_URI } from '../../resources/widgets-schema-resource';
|
|
13
14
|
import { doUpdateElementProperty } from '../../utils/do-update-element-property';
|
|
14
15
|
import { getCompositionTargetContainer } from '../../utils/get-composition-target-container';
|
|
@@ -28,12 +29,14 @@ export const initBuildCompositionsTool = ( reg: MCPRegistryEntry ) => {
|
|
|
28
29
|
{ description: 'Global Classes', uri: 'elementor://global-classes' },
|
|
29
30
|
{ description: 'Global Variables', uri: 'elementor://global-variables' },
|
|
30
31
|
{ description: 'Styles best practices', uri: BEST_PRACTICES_URI },
|
|
32
|
+
{ description: 'Available widgets for this tool', uri: AVAILABLE_WIDGETS_URI_V4 },
|
|
31
33
|
],
|
|
32
34
|
outputSchema,
|
|
33
35
|
modelPreferences: {
|
|
34
36
|
hints: [ { name: 'claude-sonnet-4-5' } ],
|
|
35
37
|
},
|
|
36
38
|
handler: async ( params ) => {
|
|
39
|
+
assertCompositionXmlUsesV4WidgetsOnly( params.xmlStructure );
|
|
37
40
|
const { xmlStructure, elementConfig, stylesConfig, customCSS } = params;
|
|
38
41
|
let generatedXML: string = '';
|
|
39
42
|
const errors: Error[] = [];
|
|
@@ -132,3 +135,26 @@ Remember: Global classes ensure design consistency and reusability. Don't skip a
|
|
|
132
135
|
},
|
|
133
136
|
} );
|
|
134
137
|
};
|
|
138
|
+
|
|
139
|
+
function assertCompositionXmlUsesV4WidgetsOnly( xmlStructure: string ) {
|
|
140
|
+
const doc = new DOMParser().parseFromString( xmlStructure, 'application/xml' );
|
|
141
|
+
if ( doc.querySelector( 'parsererror' ) ) {
|
|
142
|
+
throw new Error( 'Failed to parse XML string: ' + doc );
|
|
143
|
+
}
|
|
144
|
+
const widgetsCache = getWidgetsCache() ?? {};
|
|
145
|
+
for ( const node of doc.querySelectorAll( '*' ) ) {
|
|
146
|
+
const type = node.tagName;
|
|
147
|
+
const widgetData = widgetsCache[ type ];
|
|
148
|
+
if ( ! widgetData ) {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if ( widgetData.elType !== 'widget' ) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if ( ! widgetData.atomic_props_schema ) {
|
|
155
|
+
throw new Error(
|
|
156
|
+
`This tool does not support V3 elements. Please use the elementor-v3-mcp tools instead for element type: ${ type }`
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getWidgetsCache } from '@elementor/editor-elements';
|
|
1
2
|
import { type MCPRegistryEntry } from '@elementor/editor-mcp';
|
|
2
3
|
|
|
3
4
|
import { STYLE_SCHEMA_URI, WIDGET_SCHEMA_URI } from '../../resources/widgets-schema-resource';
|
|
@@ -24,6 +25,17 @@ export const initConfigureElementTool = ( reg: MCPRegistryEntry ) => {
|
|
|
24
25
|
speedPriority: 0.7,
|
|
25
26
|
},
|
|
26
27
|
handler: ( { elementId, propertiesToChange, elementType, stylePropertiesToChange } ) => {
|
|
28
|
+
const widgetData = getWidgetsCache()?.[ elementType ];
|
|
29
|
+
if ( ! widgetData ) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`Unknown element type: ${ elementType }. Check the available-widgets resource for valid types.`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
if ( ! widgetData.atomic_props_schema ) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
`This tool does not support V3 elements. Please use the elementor-v3-mcp tools instead for element type: ${ elementType }`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
27
39
|
const toUpdate = Object.entries( propertiesToChange );
|
|
28
40
|
const { valid, errors } = validateInput.validatePropSchema( elementType, propertiesToChange );
|
|
29
41
|
const { valid: stylesValid, errors: stylesErrors } = validateInput.validateStyles(
|
|
@@ -59,10 +59,20 @@ export const initGetElementConfigTool = ( reg: MCPRegistryEntry ) => {
|
|
|
59
59
|
if ( ! element ) {
|
|
60
60
|
throw new Error( `Element with ID ${ elementId } not found.` );
|
|
61
61
|
}
|
|
62
|
+
const elementType = element.model.get( 'widgetType' ) || element.model.get( 'elType' ) || '';
|
|
63
|
+
const widgetData = getWidgetsCache()?.[ elementType ];
|
|
64
|
+
if ( ! widgetData ) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`Unknown element type: ${ elementType }. Check the available-widgets resource for valid types.`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
if ( ! widgetData.atomic_props_schema ) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
`This tool does not support V3 elements. Please use the elementor-v3-mcp tools instead for element type: ${ elementType }`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
62
74
|
const elementRawSettings = element.settings;
|
|
63
|
-
const propSchema =
|
|
64
|
-
getWidgetsCache()?.[ element.model.get( 'widgetType' ) || element.model.get( 'elType' ) || '' ]
|
|
65
|
-
?.atomic_props_schema;
|
|
75
|
+
const propSchema = getWidgetsCache()?.[ elementType ]?.atomic_props_schema;
|
|
66
76
|
|
|
67
77
|
if ( ! elementRawSettings || ! propSchema ) {
|
|
68
78
|
throw new Error( `No settings or prop schema found for element ID: ${ elementId }` );
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { getWidgetsCache, type V1ElementConfig } from '@elementor/editor-elements';
|
|
2
|
+
|
|
3
|
+
export type AvailableWidget = {
|
|
4
|
+
type: string;
|
|
5
|
+
version: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export function hasV3Controls( controls: unknown ): boolean {
|
|
10
|
+
return typeof controls === 'object' && controls !== null && Object.keys( controls ).length > 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function isWidgetAvailableForLLM( config: V1ElementConfig | undefined ): boolean {
|
|
14
|
+
if ( ! config ) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
if ( config.meta?.llm_support === false ) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
if ( config.atomic_props_schema ) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
return hasV3Controls( config.controls );
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function getWidgetVersion( config: V1ElementConfig | undefined ): string {
|
|
27
|
+
return config?.atomic_props_schema ? 'v4' : 'v3';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function getAvailableWidgets(): AvailableWidget[] {
|
|
31
|
+
const cache = getWidgetsCache() ?? {};
|
|
32
|
+
|
|
33
|
+
return Object.keys( cache )
|
|
34
|
+
.filter( ( widgetType ) => isWidgetAvailableForLLM( cache[ widgetType ] ) )
|
|
35
|
+
.sort()
|
|
36
|
+
.map( ( widgetType ) => {
|
|
37
|
+
const config = cache[ widgetType ];
|
|
38
|
+
const description = typeof config?.meta?.description === 'string' ? config.meta.description : undefined;
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
type: widgetType,
|
|
42
|
+
version: getWidgetVersion( config ),
|
|
43
|
+
...( description && { description } ),
|
|
44
|
+
};
|
|
45
|
+
} );
|
|
46
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { getContainer } from '@elementor/editor-elements';
|
|
2
|
+
|
|
3
|
+
import { type TemplatedElementView } from '../legacy/types';
|
|
4
|
+
|
|
5
|
+
export function doAfterRender( elementIds: string[], callback: ( elementIds: string[] ) => void ): void {
|
|
6
|
+
const pending = elementIds
|
|
7
|
+
.map( ( elementId ) => {
|
|
8
|
+
const view = getContainer( elementId )?.view;
|
|
9
|
+
if ( ! view || ! hasDoAfterRender( view ) ) {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return new Promise< void >( ( resolve ) => view._doAfterRender( resolve ) );
|
|
14
|
+
} )
|
|
15
|
+
.filter( Boolean );
|
|
16
|
+
|
|
17
|
+
if ( pending.length > 0 ) {
|
|
18
|
+
Promise.all( pending ).then( () => callback( elementIds ) );
|
|
19
|
+
} else {
|
|
20
|
+
callback( elementIds );
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function hasDoAfterRender( view: unknown ): view is TemplatedElementView {
|
|
25
|
+
return typeof ( view as TemplatedElementView )?._doAfterRender === 'function';
|
|
26
|
+
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { getWidgetsCache, type V1ElementConfig } from '@elementor/editor-elements';
|
|
2
|
-
|
|
3
|
-
type ElTypedElementConfig = V1ElementConfig< {
|
|
4
|
-
elType?: string;
|
|
5
|
-
} >;
|
|
6
|
-
|
|
7
|
-
export const generateAvailableTags = () => {
|
|
8
|
-
const cache = getWidgetsCache< ElTypedElementConfig >();
|
|
9
|
-
if ( ! cache ) {
|
|
10
|
-
return [];
|
|
11
|
-
}
|
|
12
|
-
const customTags = Object.entries( cache )
|
|
13
|
-
.filter( ( [ , widgetData ] ) => !! widgetData.atomic_controls )
|
|
14
|
-
.map( ( [ widgetType, widgetData ] ) => {
|
|
15
|
-
const configurationSchema = widgetData; //getElementSchemaAsJsonSchema( widgetType );
|
|
16
|
-
return {
|
|
17
|
-
tag: `${ widgetType }`,
|
|
18
|
-
description: widgetData.title || widgetData.elType || `A ${ widgetType } element`,
|
|
19
|
-
configurationSchema: JSON.stringify( configurationSchema ),
|
|
20
|
-
};
|
|
21
|
-
} );
|
|
22
|
-
return customTags;
|
|
23
|
-
};
|