@elementor/editor-canvas 4.2.0-beta2 → 4.3.0-1001
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 +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +449 -1319
- package/dist/index.mjs +314 -1197
- package/package.json +22 -22
- package/src/index.ts +1 -1
- package/src/legacy/__tests__/init-legacy-views.test.ts +1 -1
- package/src/mcp/canvas-mcp.ts +4 -2
- package/src/mcp/resources/__tests__/available-widgets-resource.test.ts +85 -0
- package/src/mcp/resources/__tests__/dynamic-tags-resource.test.ts +27 -37
- package/src/mcp/resources/__tests__/widgets-schema-resource.test.ts +82 -74
- package/src/mcp/resources/available-widgets-resource.ts +35 -41
- package/src/mcp/resources/best-practices-resource.ts +34 -0
- package/src/mcp/resources/dynamic-tags-resource.ts +28 -29
- package/src/mcp/resources/widgets-schema-resource.ts +37 -177
- package/src/mcp/tools/build-composition/tool.ts +107 -218
- package/src/sync/element-added-event.ts +8 -0
- package/src/utils/__tests__/grid-outline-utils.test.ts +39 -0
- package/src/utils/grid-outline-utils.ts +13 -2
- package/src/composition-builder/__tests__/composition-builder.test.ts +0 -352
- package/src/composition-builder/composition-builder.ts +0 -350
- package/src/composition-builder/utils/__tests__/required-children-enforcer.test.ts +0 -79
- package/src/composition-builder/utils/__tests__/required-default-child-tags.test.ts +0 -35
- package/src/composition-builder/utils/required-children-enforcer.ts +0 -56
- package/src/composition-builder/utils/required-default-child-tags.ts +0 -22
- package/src/mcp/resources/best-practices.ts +0 -159
- package/src/mcp/resources/build-llm-guidance.ts +0 -110
- package/src/mcp/tools/build-composition/__tests__/xml-leaf-wrapper.test.ts +0 -117
- package/src/mcp/tools/build-composition/prompt.ts +0 -167
- package/src/mcp/tools/build-composition/schema.ts +0 -42
- package/src/mcp/tools/build-composition/xml-leaf-wrapper.ts +0 -68
- package/src/mcp/utils/__tests__/get-composition-target-container.test.ts +0 -59
- package/src/mcp/utils/get-composition-target-container.ts +0 -15
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elementor/editor-canvas",
|
|
3
3
|
"description": "Elementor Editor Canvas",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.3.0-1001",
|
|
5
5
|
"private": false,
|
|
6
6
|
"author": "Elementor Team",
|
|
7
7
|
"homepage": "https://elementor.com/",
|
|
@@ -37,29 +37,29 @@
|
|
|
37
37
|
"react-dom": "^18.3.1"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@elementor/editor": "4.
|
|
41
|
-
"
|
|
42
|
-
"@elementor/editor-
|
|
43
|
-
"@elementor/editor-
|
|
44
|
-
"@elementor/editor-
|
|
45
|
-
"@elementor/editor-
|
|
46
|
-
"@elementor/editor-
|
|
47
|
-
"@elementor/editor-
|
|
48
|
-
"@elementor/editor-
|
|
49
|
-
"@elementor/editor-
|
|
50
|
-
"@elementor/editor-styles": "4.
|
|
51
|
-
"@elementor/editor-
|
|
52
|
-
"@elementor/editor-
|
|
53
|
-
"@elementor/
|
|
54
|
-
"@elementor/
|
|
55
|
-
"@elementor/
|
|
56
|
-
"@elementor/
|
|
57
|
-
"@elementor/twing": "4.2.0-beta2",
|
|
40
|
+
"@elementor/editor": "4.3.0-1001",
|
|
41
|
+
"@elementor/editor-controls": "4.3.0-1001",
|
|
42
|
+
"@elementor/editor-documents": "4.3.0-1001",
|
|
43
|
+
"@elementor/editor-elements": "4.3.0-1001",
|
|
44
|
+
"@elementor/editor-interactions": "4.3.0-1001",
|
|
45
|
+
"@elementor/editor-mcp": "4.3.0-1001",
|
|
46
|
+
"@elementor/editor-notifications": "4.3.0-1001",
|
|
47
|
+
"@elementor/editor-props": "4.3.0-1001",
|
|
48
|
+
"@elementor/editor-responsive": "4.3.0-1001",
|
|
49
|
+
"@elementor/editor-styles": "4.3.0-1001",
|
|
50
|
+
"@elementor/editor-styles-repository": "4.3.0-1001",
|
|
51
|
+
"@elementor/editor-ui": "4.3.0-1001",
|
|
52
|
+
"@elementor/editor-v1-adapters": "4.3.0-1001",
|
|
53
|
+
"@elementor/events": "4.3.0-1001",
|
|
54
|
+
"@elementor/http-client": "4.3.0-1001",
|
|
55
|
+
"@elementor/schema": "4.3.0-1001",
|
|
56
|
+
"@elementor/twing": "4.3.0-1001",
|
|
58
57
|
"@elementor/ui": "1.37.5",
|
|
59
|
-
"@elementor/utils": "4.
|
|
60
|
-
"@elementor/wp-media": "4.
|
|
58
|
+
"@elementor/utils": "4.3.0-1001",
|
|
59
|
+
"@elementor/wp-media": "4.3.0-1001",
|
|
61
60
|
"@floating-ui/react": "^0.27.5",
|
|
62
|
-
"@wordpress/i18n": "^5.13.0"
|
|
61
|
+
"@wordpress/i18n": "^5.13.0",
|
|
62
|
+
"dompurify": "^3.2.6"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"tsup": "^8.3.5"
|
package/src/index.ts
CHANGED
|
@@ -44,4 +44,4 @@ export { UnknownStyleTypeError, UnknownStyleStateError } from './renderers/error
|
|
|
44
44
|
export { useCanvasDocument } from './hooks/use-canvas-document';
|
|
45
45
|
export { useEscapeOnCanvas } from './hooks/use-escape-on-canvas';
|
|
46
46
|
export { doAfterRender } from './utils/after-render';
|
|
47
|
-
export { ELEMENT_ADDED_EVENT, type ElementAddedEvent } from './
|
|
47
|
+
export { ELEMENT_ADDED_EVENT, type ElementAddedEvent } from './sync/element-added-event';
|
|
@@ -45,7 +45,7 @@ describe( 'initLegacyViews', () => {
|
|
|
45
45
|
mockLegacyElementor();
|
|
46
46
|
} );
|
|
47
47
|
|
|
48
|
-
it( 'should register a late element type via the legacy manager without leaking
|
|
48
|
+
it( 'should register a late element type via the legacy manager without leaking state', () => {
|
|
49
49
|
// Arrange
|
|
50
50
|
const elementsManager = createMockElementsManager();
|
|
51
51
|
attachMockElementsManager( elementsManager );
|
package/src/mcp/canvas-mcp.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { type MCPRegistryEntry } from '@elementor/editor-mcp';
|
|
|
2
2
|
import { Schema } from '@elementor/editor-props';
|
|
3
3
|
|
|
4
4
|
import { initAvailableWidgetsResource } from './resources/available-widgets-resource';
|
|
5
|
+
import { initBestPracticesResource } from './resources/best-practices-resource';
|
|
5
6
|
import { initBreakpointsResource } from './resources/breakpoints-resource';
|
|
6
7
|
import { initDocumentStructureResource } from './resources/document-structure-resource';
|
|
7
8
|
import { initDynamicTagsResource } from './resources/dynamic-tags-resource';
|
|
@@ -9,7 +10,7 @@ import { initEditorStateResource } from './resources/editor-state-resource';
|
|
|
9
10
|
import { initGeneralContextResource } from './resources/general-context-resource';
|
|
10
11
|
import { initSelectedElementResource } from './resources/selected-element-resource';
|
|
11
12
|
import { initWidgetsSchemaResource } from './resources/widgets-schema-resource';
|
|
12
|
-
import {
|
|
13
|
+
import { initBuildCompositionTool } from './tools/build-composition/tool';
|
|
13
14
|
import { initConfigureElementTool } from './tools/configure-element/tool';
|
|
14
15
|
import { initGetElementConfigTool } from './tools/get-element-config/tool';
|
|
15
16
|
import { getDynamicTagNamesByCategories } from './utils/resolve-dynamic-tag';
|
|
@@ -26,8 +27,9 @@ export const initCanvasMcp = ( reg: MCPRegistryEntry ) => {
|
|
|
26
27
|
initSelectedElementResource( reg );
|
|
27
28
|
initEditorStateResource( reg );
|
|
28
29
|
initGeneralContextResource( reg );
|
|
29
|
-
|
|
30
|
+
initBestPracticesResource( reg );
|
|
30
31
|
initGetElementConfigTool( reg );
|
|
31
32
|
initConfigureElementTool( reg );
|
|
33
|
+
initBuildCompositionTool( reg );
|
|
32
34
|
initBreakpointsResource( reg );
|
|
33
35
|
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { httpService } from '@elementor/http-client';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
AVAILABLE_WIDGETS_URI,
|
|
5
|
+
AVAILABLE_WIDGETS_URI_V4,
|
|
6
|
+
initAvailableWidgetsResource,
|
|
7
|
+
} from '../available-widgets-resource';
|
|
8
|
+
|
|
9
|
+
jest.mock( '@elementor/http-client', () => ( {
|
|
10
|
+
httpService: jest.fn(),
|
|
11
|
+
} ) );
|
|
12
|
+
|
|
13
|
+
const mockedHttpService = httpService as jest.MockedFunction< typeof httpService >;
|
|
14
|
+
|
|
15
|
+
type ResourceHandler = () => Promise< { contents: { text: string }[] } >;
|
|
16
|
+
|
|
17
|
+
const captureHandlers = () => {
|
|
18
|
+
const resource = jest.fn();
|
|
19
|
+
initAvailableWidgetsResource( { resource } as never );
|
|
20
|
+
return {
|
|
21
|
+
v4Handler: resource.mock.calls[ 0 ][ 3 ] as ResourceHandler,
|
|
22
|
+
allHandler: resource.mock.calls[ 1 ][ 3 ] as ResourceHandler,
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
describe( 'available-widgets-resource', () => {
|
|
27
|
+
beforeEach( () => {
|
|
28
|
+
jest.clearAllMocks();
|
|
29
|
+
} );
|
|
30
|
+
|
|
31
|
+
it( 'fetches all widgets via list-widget-schemas with summary=true', async () => {
|
|
32
|
+
// Arrange
|
|
33
|
+
const post = jest.fn().mockResolvedValue( {
|
|
34
|
+
data: {
|
|
35
|
+
data: {
|
|
36
|
+
widgets: [
|
|
37
|
+
{ type: 'e-heading', description: 'A heading widget' },
|
|
38
|
+
{ type: 'e-button', description: 'A button widget' },
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
} );
|
|
43
|
+
mockedHttpService.mockReturnValue( { post } as never );
|
|
44
|
+
const { allHandler } = captureHandlers();
|
|
45
|
+
|
|
46
|
+
// Act
|
|
47
|
+
const result = await allHandler();
|
|
48
|
+
|
|
49
|
+
// Assert
|
|
50
|
+
expect( post ).toHaveBeenCalledWith( 'elementor/v1/mcp-proxy', {
|
|
51
|
+
tool: 'list-widget-schemas',
|
|
52
|
+
input: { summary: true },
|
|
53
|
+
} );
|
|
54
|
+
expect( JSON.parse( result.contents[ 0 ].text ) ).toEqual( [
|
|
55
|
+
{ type: 'e-heading', description: 'A heading widget' },
|
|
56
|
+
{ type: 'e-button', description: 'A button widget' },
|
|
57
|
+
] );
|
|
58
|
+
} );
|
|
59
|
+
|
|
60
|
+
it( 'fetches v4 widgets via list-widget-schemas with summary=true', async () => {
|
|
61
|
+
// Arrange
|
|
62
|
+
const post = jest.fn().mockResolvedValue( {
|
|
63
|
+
data: { data: { widgets: [ { type: 'e-heading', description: 'A heading widget' } ] } },
|
|
64
|
+
} );
|
|
65
|
+
mockedHttpService.mockReturnValue( { post } as never );
|
|
66
|
+
const { v4Handler } = captureHandlers();
|
|
67
|
+
|
|
68
|
+
// Act
|
|
69
|
+
const result = await v4Handler();
|
|
70
|
+
|
|
71
|
+
// Assert
|
|
72
|
+
expect( post ).toHaveBeenCalledWith( 'elementor/v1/mcp-proxy', {
|
|
73
|
+
tool: 'list-widget-schemas',
|
|
74
|
+
input: { summary: true },
|
|
75
|
+
} );
|
|
76
|
+
expect( JSON.parse( result.contents[ 0 ].text ) ).toEqual( [
|
|
77
|
+
{ type: 'e-heading', description: 'A heading widget' },
|
|
78
|
+
] );
|
|
79
|
+
} );
|
|
80
|
+
|
|
81
|
+
it( 'exposes the expected resource URIs', () => {
|
|
82
|
+
expect( AVAILABLE_WIDGETS_URI ).toBe( 'elementor://context/available-widgets' );
|
|
83
|
+
expect( AVAILABLE_WIDGETS_URI_V4 ).toBe( 'elementor://context/available-widgets/v4' );
|
|
84
|
+
} );
|
|
85
|
+
} );
|
|
@@ -1,19 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { getElementorConfig } from '@elementor/editor-v1-adapters';
|
|
1
|
+
import { httpService } from '@elementor/http-client';
|
|
3
2
|
|
|
4
3
|
import { DYNAMIC_TAGS_URI, initDynamicTagsResource } from '../dynamic-tags-resource';
|
|
5
4
|
|
|
6
|
-
jest.mock( '@elementor/
|
|
7
|
-
|
|
8
|
-
propTypeToJsonSchema: jest.fn( () => ( { mocked: true } ) ),
|
|
9
|
-
},
|
|
5
|
+
jest.mock( '@elementor/http-client', () => ( {
|
|
6
|
+
httpService: jest.fn(),
|
|
10
7
|
} ) );
|
|
11
8
|
|
|
12
|
-
jest.
|
|
13
|
-
getElementorConfig: jest.fn(),
|
|
14
|
-
} ) );
|
|
15
|
-
|
|
16
|
-
const mockedGetElementorConfig = getElementorConfig as jest.MockedFunction< typeof getElementorConfig >;
|
|
9
|
+
const mockedHttpService = httpService as jest.MockedFunction< typeof httpService >;
|
|
17
10
|
|
|
18
11
|
type ResourceHandler = ( uri: URL ) => Promise< { contents: { text: string }[] } >;
|
|
19
12
|
|
|
@@ -34,52 +27,49 @@ describe( 'dynamic-tags-resource', () => {
|
|
|
34
27
|
jest.clearAllMocks();
|
|
35
28
|
} );
|
|
36
29
|
|
|
37
|
-
it( 'returns
|
|
30
|
+
it( 'returns the flat tag list fetched from the server', async () => {
|
|
38
31
|
// Arrange
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
32
|
+
const post = jest.fn().mockResolvedValue( {
|
|
33
|
+
data: {
|
|
34
|
+
data: [
|
|
35
|
+
{
|
|
43
36
|
name: 'post-custom-field',
|
|
44
37
|
label: 'Post Custom Field',
|
|
45
|
-
group: 'post',
|
|
46
38
|
categories: [ 'text', 'url' ],
|
|
47
|
-
|
|
48
|
-
key: { kind: 'string', key: 'string' },
|
|
49
|
-
before: { kind: 'string', key: 'string' },
|
|
50
|
-
fallback: { kind: 'string', key: 'string' },
|
|
51
|
-
},
|
|
39
|
+
settings: { key: { mocked: true }, before: { mocked: true } },
|
|
52
40
|
},
|
|
53
|
-
|
|
54
|
-
groups: { post: { title: 'Post' } },
|
|
41
|
+
],
|
|
55
42
|
},
|
|
56
|
-
}
|
|
43
|
+
} );
|
|
44
|
+
mockedHttpService.mockReturnValue( { post } as never );
|
|
57
45
|
|
|
58
46
|
// Act
|
|
59
47
|
const catalog = await readCatalog();
|
|
60
48
|
|
|
61
49
|
// Assert
|
|
62
|
-
expect(
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
name: 'post-custom-field',
|
|
66
|
-
label: 'Post Custom Field',
|
|
67
|
-
categories: [ 'text', 'url' ],
|
|
50
|
+
expect( post ).toHaveBeenCalledWith( 'elementor/v1/mcp-proxy', {
|
|
51
|
+
tool: 'list-dynamic-tags',
|
|
52
|
+
input: {},
|
|
68
53
|
} );
|
|
69
|
-
expect( catalog
|
|
70
|
-
|
|
71
|
-
|
|
54
|
+
expect( catalog ).toEqual( [
|
|
55
|
+
{
|
|
56
|
+
name: 'post-custom-field',
|
|
57
|
+
label: 'Post Custom Field',
|
|
58
|
+
categories: [ 'text', 'url' ],
|
|
59
|
+
settings: { key: { mocked: true }, before: { mocked: true } },
|
|
60
|
+
},
|
|
61
|
+
] );
|
|
72
62
|
} );
|
|
73
63
|
|
|
74
|
-
it( 'returns an empty list when
|
|
64
|
+
it( 'returns an empty list when the server returns no data', async () => {
|
|
75
65
|
// Arrange
|
|
76
|
-
|
|
66
|
+
const post = jest.fn().mockResolvedValue( { data: {} } );
|
|
67
|
+
mockedHttpService.mockReturnValue( { post } as never );
|
|
77
68
|
|
|
78
69
|
// Act
|
|
79
70
|
const catalog = await readCatalog();
|
|
80
71
|
|
|
81
72
|
// Assert
|
|
82
73
|
expect( catalog ).toEqual( [] );
|
|
83
|
-
expect( Schema.propTypeToJsonSchema ).not.toHaveBeenCalled();
|
|
84
74
|
} );
|
|
85
75
|
} );
|
|
@@ -1,95 +1,103 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { httpService } from '@elementor/http-client';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
jest.mock( '@elementor/http-client', () => ( {
|
|
4
|
+
httpService: jest.fn(),
|
|
5
|
+
} ) );
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
message: { $$type: 'string', value: '[all-fields]' },
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
};
|
|
7
|
+
jest.mock( '@elementor/editor-mcp', () => {
|
|
8
|
+
class MockResourceTemplate {
|
|
9
|
+
callbacks: { list?: () => Promise< { resources: unknown[] } > };
|
|
10
|
+
constructor( _uriTemplate: string, callbacks: { list?: () => Promise< { resources: unknown[] } > } ) {
|
|
11
|
+
this.callbacks = callbacks;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return { ResourceTemplate: MockResourceTemplate };
|
|
15
|
+
} );
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
title: 'Form',
|
|
21
|
-
controls: {},
|
|
22
|
-
elType: 'widget',
|
|
23
|
-
meta: { is_container: true },
|
|
24
|
-
base_settings: mockEmailBaseSettings,
|
|
25
|
-
atomic_props_schema: {
|
|
26
|
-
email: { kind: 'object', key: 'emails' },
|
|
27
|
-
'form-name': { kind: 'string', key: 'string' },
|
|
28
|
-
},
|
|
29
|
-
} as unknown as V1ElementConfig;
|
|
17
|
+
import { initWidgetsSchemaResource, WIDGET_SCHEMA_URI } from '../widgets-schema-resource';
|
|
30
18
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
19
|
+
const mockedHttpService = httpService as jest.MockedFunction< typeof httpService >;
|
|
20
|
+
|
|
21
|
+
type ResourceTemplateHandler = (
|
|
22
|
+
uri: URL,
|
|
23
|
+
variables: Record< string, string >
|
|
24
|
+
) => Promise< { contents: { text: string }[] } >;
|
|
25
|
+
type ResourceTemplateLike = { callbacks: { list?: () => Promise< { resources: unknown[] } > } };
|
|
36
26
|
|
|
37
|
-
|
|
38
|
-
|
|
27
|
+
const captureHandlers = () => {
|
|
28
|
+
const resource = jest.fn();
|
|
29
|
+
initWidgetsSchemaResource( { resource } as never );
|
|
30
|
+
const call = resource.mock.calls[ 0 ];
|
|
31
|
+
const template = call[ 1 ] as ResourceTemplateLike;
|
|
32
|
+
return {
|
|
33
|
+
list: template.callbacks.list as () => Promise< { resources: unknown[] } >,
|
|
34
|
+
readHandler: call[ 3 ] as ResourceTemplateHandler,
|
|
35
|
+
};
|
|
36
|
+
};
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
describe( 'widgets-schema-resource', () => {
|
|
39
|
+
beforeEach( () => {
|
|
40
|
+
jest.clearAllMocks();
|
|
43
41
|
} );
|
|
44
42
|
|
|
45
|
-
it( '
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
{
|
|
52
|
-
|
|
53
|
-
display: { $$type: 'string', value: 'flex' },
|
|
54
|
-
},
|
|
55
|
-
},
|
|
43
|
+
it( 'lists widget types fetched from the server via list-widget-schemas with summary=true', async () => {
|
|
44
|
+
// Arrange
|
|
45
|
+
const post = jest.fn().mockResolvedValue( {
|
|
46
|
+
data: {
|
|
47
|
+
data: {
|
|
48
|
+
widgets: [
|
|
49
|
+
{ type: 'e-heading', description: 'A heading widget' },
|
|
50
|
+
{ type: 'e-button', description: 'A button widget' },
|
|
56
51
|
],
|
|
57
52
|
},
|
|
58
53
|
},
|
|
59
|
-
}
|
|
54
|
+
} );
|
|
55
|
+
mockedHttpService.mockReturnValue( { post } as never );
|
|
56
|
+
const { list } = captureHandlers();
|
|
60
57
|
|
|
61
|
-
|
|
58
|
+
// Act
|
|
59
|
+
const result = await list();
|
|
62
60
|
|
|
63
|
-
|
|
64
|
-
expect(
|
|
65
|
-
|
|
66
|
-
|
|
61
|
+
// Assert
|
|
62
|
+
expect( post ).toHaveBeenCalledWith( 'elementor/v1/mcp-proxy', {
|
|
63
|
+
tool: 'list-widget-schemas',
|
|
64
|
+
input: { summary: true },
|
|
65
|
+
} );
|
|
66
|
+
expect( result.resources ).toEqual( [
|
|
67
|
+
{ uri: 'elementor://widgets/schema/e-heading', name: 'Widget schema for e-heading' },
|
|
68
|
+
{ uri: 'elementor://widgets/schema/e-button', name: 'Widget schema for e-button' },
|
|
69
|
+
] );
|
|
67
70
|
} );
|
|
68
71
|
|
|
69
|
-
it( '
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
);
|
|
72
|
+
it( 'reads a single widget schema via the get-widget-schema tool', async () => {
|
|
73
|
+
// Arrange
|
|
74
|
+
const post = jest.fn().mockResolvedValue( {
|
|
75
|
+
data: { data: { type: 'object', properties: { text: { mocked: true } } } },
|
|
76
|
+
} );
|
|
77
|
+
mockedHttpService.mockReturnValue( { post } as never );
|
|
78
|
+
const { readHandler } = captureHandlers();
|
|
79
|
+
const uri = new URL( 'elementor://widgets/schema/e-heading' );
|
|
80
|
+
|
|
81
|
+
// Act
|
|
82
|
+
const result = await readHandler( uri, { widgetType: 'e-heading' } );
|
|
77
83
|
|
|
78
|
-
|
|
79
|
-
expect(
|
|
84
|
+
// Assert
|
|
85
|
+
expect( post ).toHaveBeenCalledWith( 'elementor/v1/mcp-proxy', {
|
|
86
|
+
tool: 'get-widget-schema',
|
|
87
|
+
input: { widget_type: 'e-heading' },
|
|
88
|
+
} );
|
|
89
|
+
expect( JSON.parse( result.contents[ 0 ].text ) ).toEqual( {
|
|
90
|
+
type: 'object',
|
|
91
|
+
properties: { text: { mocked: true } },
|
|
92
|
+
} );
|
|
80
93
|
} );
|
|
81
|
-
} );
|
|
82
94
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
email: { type: 'object', properties: {} },
|
|
88
|
-
},
|
|
89
|
-
Object.keys( mockEmailBaseSettings )
|
|
90
|
-
);
|
|
95
|
+
it( 'throws when no widget type variable is provided', async () => {
|
|
96
|
+
// Arrange
|
|
97
|
+
const { readHandler } = captureHandlers();
|
|
98
|
+
const uri = new URL( WIDGET_SCHEMA_URI.replace( '{widgetType}', '' ) );
|
|
91
99
|
|
|
92
|
-
|
|
93
|
-
expect(
|
|
100
|
+
// Act & Assert
|
|
101
|
+
await expect( readHandler( uri, {} ) ).rejects.toThrow( 'No widget type provided.' );
|
|
94
102
|
} );
|
|
95
103
|
} );
|
|
@@ -1,37 +1,45 @@
|
|
|
1
1
|
import { type MCPRegistryEntry } from '@elementor/editor-mcp';
|
|
2
|
-
import {
|
|
2
|
+
import { type HttpResponse, httpService } from '@elementor/http-client';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
const MCP_PROXY_URL = 'elementor/v1/mcp-proxy';
|
|
5
5
|
|
|
6
6
|
export const AVAILABLE_WIDGETS_URI = 'elementor://context/available-widgets';
|
|
7
7
|
export const AVAILABLE_WIDGETS_URI_V4 = 'elementor://context/available-widgets/v4';
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
type WidgetSummary = {
|
|
10
|
+
type: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
};
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
14
|
+
type WidgetSummaryResponse = {
|
|
15
|
+
widgets: WidgetSummary[];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const fetchWidgets = async (): Promise< WidgetSummary[] > => {
|
|
19
|
+
const { data } = await httpService().post< HttpResponse< WidgetSummaryResponse > >( MCP_PROXY_URL, {
|
|
20
|
+
tool: 'list-widget-schemas',
|
|
21
|
+
input: { summary: true },
|
|
22
|
+
} );
|
|
23
|
+
|
|
24
|
+
return data.data?.widgets ?? [];
|
|
25
|
+
};
|
|
24
26
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
const buildContents = async ( uri: string ) => {
|
|
28
|
+
const widgets = await fetchWidgets();
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
contents: [
|
|
32
|
+
{
|
|
33
|
+
uri,
|
|
34
|
+
mimeType: 'application/json',
|
|
35
|
+
text: JSON.stringify( widgets, null, 2 ),
|
|
36
|
+
},
|
|
37
|
+
],
|
|
34
38
|
};
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const initAvailableWidgetsResource = ( reg: MCPRegistryEntry ) => {
|
|
42
|
+
const { resource } = reg;
|
|
35
43
|
|
|
36
44
|
resource(
|
|
37
45
|
'available-widgets-v4',
|
|
@@ -39,29 +47,15 @@ export const initAvailableWidgetsResource = ( reg: MCPRegistryEntry ) => {
|
|
|
39
47
|
{
|
|
40
48
|
description: 'All registered v4 version widgets',
|
|
41
49
|
},
|
|
42
|
-
async () => buildContents( AVAILABLE_WIDGETS_URI_V4
|
|
50
|
+
async () => buildContents( AVAILABLE_WIDGETS_URI_V4 )
|
|
43
51
|
);
|
|
44
52
|
|
|
45
53
|
resource(
|
|
46
54
|
'available-widgets',
|
|
47
55
|
AVAILABLE_WIDGETS_URI,
|
|
48
56
|
{
|
|
49
|
-
description: 'All registered widget types with
|
|
57
|
+
description: 'All registered v4 widget types with description.',
|
|
50
58
|
},
|
|
51
59
|
async () => buildContents( AVAILABLE_WIDGETS_URI )
|
|
52
60
|
);
|
|
53
|
-
|
|
54
|
-
const eventName = v1ReadyEvent().name;
|
|
55
|
-
|
|
56
|
-
const onV1Ready = () => {
|
|
57
|
-
const widgets = getAvailableWidgets();
|
|
58
|
-
if ( widgets.length === 0 ) {
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
window.removeEventListener( eventName, onV1Ready );
|
|
62
|
-
notifyResourcesUpdated();
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
window.addEventListener( eventName, onV1Ready );
|
|
66
|
-
onV1Ready();
|
|
67
61
|
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type MCPRegistryEntry } from '@elementor/editor-mcp';
|
|
2
|
+
import { type HttpResponse, httpService } from '@elementor/http-client';
|
|
3
|
+
|
|
4
|
+
const MCP_PROXY_URL = 'elementor/v1/mcp-proxy';
|
|
5
|
+
export const BEST_PRACTICES_URI = 'elementor://style/best-practices';
|
|
6
|
+
|
|
7
|
+
export const initBestPracticesResource = ( reg: MCPRegistryEntry ) => {
|
|
8
|
+
const { resource } = reg;
|
|
9
|
+
|
|
10
|
+
resource(
|
|
11
|
+
'style-best-practices',
|
|
12
|
+
BEST_PRACTICES_URI,
|
|
13
|
+
{
|
|
14
|
+
description:
|
|
15
|
+
'Design quality guidelines for avoiding generic AI output: typography, color strategy, spacing, motion, and visual hierarchy best practices.',
|
|
16
|
+
mimeType: 'text/markdown',
|
|
17
|
+
},
|
|
18
|
+
async ( uri: URL ) => {
|
|
19
|
+
const { data } = await httpService().get< HttpResponse< string > >( MCP_PROXY_URL, {
|
|
20
|
+
params: { uri: uri.href },
|
|
21
|
+
} );
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
contents: [
|
|
25
|
+
{
|
|
26
|
+
uri: uri.href,
|
|
27
|
+
mimeType: 'text/markdown',
|
|
28
|
+
text: data.data,
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
};
|