@elementor/editor-canvas 4.3.0-999 → 4.3.0-beta1
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 +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +457 -371
- package/dist/index.mjs +438 -348
- package/package.json +20 -20
- package/src/init-settings-transformers.ts +4 -0
- package/src/init-style-transformers.ts +2 -2
- package/src/init.tsx +5 -4
- package/src/legacy/__tests__/create-templated-element-type.test.ts +50 -0
- package/src/legacy/__tests__/list-type.test.ts +89 -0
- package/src/legacy/create-nested-templated-element-type.ts +3 -1
- package/src/legacy/create-templated-element-type.ts +5 -2
- package/src/legacy/list-type.ts +63 -0
- package/src/legacy/replacements/inline-editing/__tests__/inline-editing-eligibility.test.ts +7 -7
- package/src/legacy/replacements/inline-editing/inline-editing-elements.tsx +22 -9
- package/src/legacy/replacements/inline-editing/inline-editing-eligibility.ts +12 -3
- package/src/legacy/tabs-model-extensions.ts +2 -5
- package/src/legacy/twig-rendering-utils.ts +11 -2
- package/src/legacy/types.ts +4 -0
- package/src/mcp/canvas-mcp.ts +2 -4
- package/src/mcp/mcp-description.ts +18 -30
- package/src/mcp/resources/__tests__/available-widgets-resource.test.ts +22 -13
- package/src/mcp/resources/__tests__/dynamic-tags-resource.test.ts +22 -30
- package/src/mcp/resources/__tests__/widgets-schema-resource.test.ts +11 -6
- package/src/mcp/resources/available-widgets-resource.ts +13 -10
- package/src/mcp/resources/dynamic-tags-resource.ts +6 -15
- package/src/mcp/resources/widgets-schema-resource.ts +8 -5
- package/src/mcp/tools/configure-element/__tests__/tool.test.ts +130 -0
- package/src/mcp/tools/configure-element/prompt.ts +3 -6
- package/src/mcp/tools/configure-element/schema.ts +6 -0
- package/src/mcp/tools/configure-element/tool.ts +11 -1
- package/src/mcp/tools/get-page-structure/tool.ts +73 -0
- package/src/mcp/utils/__tests__/do-update-element-property.test.ts +27 -1
- package/src/mcp/utils/do-update-element-property.ts +18 -6
- package/src/mcp/utils/get-mcp-error-message.ts +16 -0
- package/src/renderers/__tests__/compute-html-tag.test.ts +55 -0
- package/src/renderers/__tests__/create-dom-renderer.test.ts +60 -0
- package/src/renderers/__tests__/fixtures/html-tag-computer-cases.json +87 -0
- package/src/renderers/compute-html-tag.ts +73 -0
- package/src/renderers/create-dom-renderer.ts +13 -22
- package/src/transformers/settings/escaped-html-transformer.ts +6 -0
- package/src/transformers/shared/__tests__/icon-transformer.test.ts +74 -0
- package/src/transformers/shared/icon-transformer.ts +122 -0
- package/src/transformers/shared/process-svg-content.ts +26 -0
- package/src/transformers/shared/svg-src-transformer.ts +1 -26
- package/src/utils/__tests__/sanitize-escaped-html.test.ts +105 -0
- package/src/utils/sanitize-escaped-html.ts +32 -0
- package/src/mcp/tools/build-composition/tool.ts +0 -133
- package/src/mcp/tools/get-element-config/tool.ts +0 -114
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.3.0-
|
|
4
|
+
"version": "4.3.0-beta1",
|
|
5
5
|
"private": false,
|
|
6
6
|
"author": "Elementor Team",
|
|
7
7
|
"homepage": "https://elementor.com/",
|
|
@@ -37,26 +37,26 @@
|
|
|
37
37
|
"react-dom": "^18.3.1"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@elementor/editor": "4.3.0-
|
|
41
|
-
"@elementor/editor-controls": "4.3.0-
|
|
42
|
-
"@elementor/editor-documents": "4.3.0-
|
|
43
|
-
"@elementor/editor-elements": "4.3.0-
|
|
44
|
-
"@elementor/editor-interactions": "4.3.0-
|
|
45
|
-
"@elementor/editor-mcp": "4.3.0-
|
|
46
|
-
"@elementor/editor-notifications": "4.3.0-
|
|
47
|
-
"@elementor/editor-props": "4.3.0-
|
|
48
|
-
"@elementor/editor-responsive": "4.3.0-
|
|
49
|
-
"@elementor/editor-styles": "4.3.0-
|
|
50
|
-
"@elementor/editor-styles-repository": "4.3.0-
|
|
51
|
-
"@elementor/editor-ui": "4.3.0-
|
|
52
|
-
"@elementor/editor-v1-adapters": "4.3.0-
|
|
53
|
-
"@elementor/events": "4.3.0-
|
|
54
|
-
"@elementor/http-client": "4.3.0-
|
|
55
|
-
"@elementor/schema": "4.3.0-
|
|
56
|
-
"@elementor/twing": "4.3.0-
|
|
40
|
+
"@elementor/editor": "4.3.0-beta1",
|
|
41
|
+
"@elementor/editor-controls": "4.3.0-beta1",
|
|
42
|
+
"@elementor/editor-documents": "4.3.0-beta1",
|
|
43
|
+
"@elementor/editor-elements": "4.3.0-beta1",
|
|
44
|
+
"@elementor/editor-interactions": "4.3.0-beta1",
|
|
45
|
+
"@elementor/editor-mcp": "4.3.0-beta1",
|
|
46
|
+
"@elementor/editor-notifications": "4.3.0-beta1",
|
|
47
|
+
"@elementor/editor-props": "4.3.0-beta1",
|
|
48
|
+
"@elementor/editor-responsive": "4.3.0-beta1",
|
|
49
|
+
"@elementor/editor-styles": "4.3.0-beta1",
|
|
50
|
+
"@elementor/editor-styles-repository": "4.3.0-beta1",
|
|
51
|
+
"@elementor/editor-ui": "4.3.0-beta1",
|
|
52
|
+
"@elementor/editor-v1-adapters": "4.3.0-beta1",
|
|
53
|
+
"@elementor/events": "4.3.0-beta1",
|
|
54
|
+
"@elementor/http-client": "4.3.0-beta1",
|
|
55
|
+
"@elementor/schema": "4.3.0-beta1",
|
|
56
|
+
"@elementor/twing": "4.3.0-beta1",
|
|
57
57
|
"@elementor/ui": "1.37.5",
|
|
58
|
-
"@elementor/utils": "4.3.0-
|
|
59
|
-
"@elementor/wp-media": "4.3.0-
|
|
58
|
+
"@elementor/utils": "4.3.0-beta1",
|
|
59
|
+
"@elementor/wp-media": "4.3.0-beta1",
|
|
60
60
|
"@floating-ui/react": "^0.27.5",
|
|
61
61
|
"@wordpress/i18n": "^5.13.0",
|
|
62
62
|
"dompurify": "^3.2.6"
|
|
@@ -3,11 +3,13 @@ import { attributesTransformer } from './transformers/settings/attributes-transf
|
|
|
3
3
|
import { createClassesTransformer } from './transformers/settings/classes-transformer';
|
|
4
4
|
import { dateRangeTransformer } from './transformers/settings/date-range-transformer';
|
|
5
5
|
import { dateTimeTransformer } from './transformers/settings/date-time-transformer';
|
|
6
|
+
import { escapedHtmlTransformer } from './transformers/settings/escaped-html-transformer';
|
|
6
7
|
import { htmlV2Transformer } from './transformers/settings/html-v2-transformer';
|
|
7
8
|
import { htmlV3Transformer } from './transformers/settings/html-v3-transformer';
|
|
8
9
|
import { linkTransformer } from './transformers/settings/link-transformer';
|
|
9
10
|
import { queryTransformer } from './transformers/settings/query-transformer';
|
|
10
11
|
import { timeRangeTransformer } from './transformers/settings/time-range-transformer';
|
|
12
|
+
import { iconTransformer } from './transformers/shared/icon-transformer';
|
|
11
13
|
import { imageSrcTransformer } from './transformers/shared/image-src-transformer';
|
|
12
14
|
import { imageTransformer } from './transformers/shared/image-transformer';
|
|
13
15
|
import { plainTransformer } from './transformers/shared/plain-transformer';
|
|
@@ -22,11 +24,13 @@ export function initSettingsTransformers() {
|
|
|
22
24
|
.register( 'image', imageTransformer )
|
|
23
25
|
.register( 'image-src', imageSrcTransformer )
|
|
24
26
|
.register( 'svg-src', svgSrcTransformer )
|
|
27
|
+
.register( 'icon', iconTransformer )
|
|
25
28
|
.register( 'video-src', videoSrcTransformer )
|
|
26
29
|
.register( 'attributes', attributesTransformer )
|
|
27
30
|
.register( 'date-time', dateTimeTransformer )
|
|
28
31
|
.register( 'html-v2', htmlV2Transformer )
|
|
29
32
|
.register( 'html-v3', htmlV3Transformer )
|
|
33
|
+
.register( 'escaped-html', escapedHtmlTransformer )
|
|
30
34
|
.register( 'date-range', dateRangeTransformer )
|
|
31
35
|
.register( 'time-range', timeRangeTransformer )
|
|
32
36
|
.registerFallback( plainTransformer );
|
|
@@ -80,14 +80,14 @@ export function initStyleTransformers() {
|
|
|
80
80
|
)
|
|
81
81
|
.register( 'flex', flexTransformer )
|
|
82
82
|
.register(
|
|
83
|
-
'border-width',
|
|
83
|
+
'border-width-v2',
|
|
84
84
|
createMultiPropsTransformer(
|
|
85
85
|
[ 'block-start', 'block-end', 'inline-start', 'inline-end' ],
|
|
86
86
|
( { key } ) => `border-${ key }-width`
|
|
87
87
|
)
|
|
88
88
|
)
|
|
89
89
|
.register(
|
|
90
|
-
'border-radius',
|
|
90
|
+
'border-radius-v2',
|
|
91
91
|
createMultiPropsTransformer(
|
|
92
92
|
[ 'start-start', 'start-end', 'end-start', 'end-end' ],
|
|
93
93
|
( { key } ) => `border-${ key }-radius`
|
package/src/init.tsx
CHANGED
|
@@ -10,6 +10,7 @@ import { initFormNestingPrevention } from './form-structure/prevent-form-nesting
|
|
|
10
10
|
import { initSettingsTransformers } from './init-settings-transformers';
|
|
11
11
|
import { initStyleTransformers } from './init-style-transformers';
|
|
12
12
|
import { initLegacyViews } from './legacy/init-legacy-views';
|
|
13
|
+
import { initListType } from './legacy/list-type';
|
|
13
14
|
import { initViewReplacements } from './legacy/replacements/manager';
|
|
14
15
|
import { initTabsModelExtensions } from './legacy/tabs-model-extensions';
|
|
15
16
|
import { initCanvasMcp } from './mcp/canvas-mcp';
|
|
@@ -54,14 +55,14 @@ export function init() {
|
|
|
54
55
|
initCanvasMcp(
|
|
55
56
|
getMCPByDomain( 'canvas', {
|
|
56
57
|
instructions: `Everything related to V4 ( Atomic ) canvas.
|
|
57
|
-
# Canvas workflow
|
|
58
|
-
- Configure
|
|
59
|
-
-
|
|
60
|
-
- Get and retrieve element configuration values
|
|
58
|
+
# Canvas workflow
|
|
59
|
+
- Configure element settings and styles with configure-element
|
|
60
|
+
- Get page structure and element configuration values
|
|
61
61
|
`,
|
|
62
62
|
docs: mcpDescription,
|
|
63
63
|
} )
|
|
64
64
|
);
|
|
65
65
|
|
|
66
66
|
initTabsModelExtensions();
|
|
67
|
+
initListType();
|
|
67
68
|
}
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
createTemplatedElementType,
|
|
6
6
|
createTemplatedElementView,
|
|
7
7
|
} from '../create-templated-element-type';
|
|
8
|
+
import { type ElementView } from '../types';
|
|
8
9
|
|
|
9
10
|
const MOCK_ELEMENT_TYPE = 'test-element';
|
|
10
11
|
const MOCK_HTML = '<div>Element</div>';
|
|
@@ -19,6 +20,8 @@ const createMockElementConfig = () => ( {
|
|
|
19
20
|
twig_main_template: 'main',
|
|
20
21
|
atomic_props_schema: {},
|
|
21
22
|
base_styles_dictionary: {},
|
|
23
|
+
default_html_tag: 'div',
|
|
24
|
+
html_tag_follows_link: true,
|
|
22
25
|
} );
|
|
23
26
|
|
|
24
27
|
describe( 'createTemplatedElementType', () => {
|
|
@@ -116,6 +119,53 @@ describe( 'createTemplatedElementView', () => {
|
|
|
116
119
|
expect( utils.register ).toHaveBeenCalledWith( 'template2', '<div>Template 2</div>' );
|
|
117
120
|
} );
|
|
118
121
|
} );
|
|
122
|
+
|
|
123
|
+
describe( 'render context', () => {
|
|
124
|
+
it( 'should inject computed tag into the twig render context', async () => {
|
|
125
|
+
// Arrange
|
|
126
|
+
const utils = createMockRenderer();
|
|
127
|
+
const ViewClass = createTemplatedElementView( {
|
|
128
|
+
type: MOCK_ELEMENT_TYPE,
|
|
129
|
+
renderer: utils,
|
|
130
|
+
element: {
|
|
131
|
+
...createMockElementConfig(),
|
|
132
|
+
twig_templates: {
|
|
133
|
+
main: '<{{ tag | e("html_tag") }}></{{ tag | e("html_tag") }}>',
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
} );
|
|
137
|
+
|
|
138
|
+
const view = new ViewClass() as unknown as ElementView & {
|
|
139
|
+
_abortController: AbortController;
|
|
140
|
+
};
|
|
141
|
+
view.model = {
|
|
142
|
+
get: ( key: string ) => {
|
|
143
|
+
if ( key === 'id' ) {
|
|
144
|
+
return 'test-id';
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if ( key === 'settings' ) {
|
|
148
|
+
return { toJSON: () => ( {} ) };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return undefined;
|
|
152
|
+
},
|
|
153
|
+
} as unknown as ElementView[ 'model' ];
|
|
154
|
+
view.isRendered = false;
|
|
155
|
+
view._abortController = new AbortController();
|
|
156
|
+
view.triggerMethod = jest.fn();
|
|
157
|
+
view.bindUIElements = jest.fn();
|
|
158
|
+
view.$el = { html: jest.fn() } as unknown as ElementView[ '$el' ];
|
|
159
|
+
view._ensureViewIsIntact = jest.fn();
|
|
160
|
+
view.resetChildViewContainer = jest.fn();
|
|
161
|
+
|
|
162
|
+
// Act
|
|
163
|
+
await view._renderTemplate();
|
|
164
|
+
|
|
165
|
+
// Assert
|
|
166
|
+
expect( utils.render ).toHaveBeenCalledWith( 'main', expect.objectContaining( { tag: 'div' } ) );
|
|
167
|
+
} );
|
|
168
|
+
} );
|
|
119
169
|
} );
|
|
120
170
|
|
|
121
171
|
describe( 'canBeTemplated', () => {
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createNestedTemplatedElementType,
|
|
3
|
+
createNestedTemplatedElementView,
|
|
4
|
+
} from '../create-nested-templated-element-type';
|
|
5
|
+
import { elementsLegacyTypes } from '../init-legacy-views';
|
|
6
|
+
import { initListType } from '../list-type';
|
|
7
|
+
import type { ElementType, NestedTemplatedElementViewClass } from '../types';
|
|
8
|
+
|
|
9
|
+
jest.mock( '../create-nested-templated-element-type', () => ( {
|
|
10
|
+
createNestedTemplatedElementType: jest.fn(),
|
|
11
|
+
createNestedTemplatedElementView: jest.fn(),
|
|
12
|
+
} ) );
|
|
13
|
+
|
|
14
|
+
const LIST_TYPE = 'e-list';
|
|
15
|
+
const resetListType = () => Reflect.deleteProperty( elementsLegacyTypes, LIST_TYPE );
|
|
16
|
+
|
|
17
|
+
const createMockBaseType = (): typeof ElementType => {
|
|
18
|
+
const MockBaseType = function MockBaseType() {};
|
|
19
|
+
|
|
20
|
+
MockBaseType.prototype.getType = () => LIST_TYPE;
|
|
21
|
+
MockBaseType.prototype.getView = () => function MockBaseView() {};
|
|
22
|
+
|
|
23
|
+
return MockBaseType as unknown as typeof ElementType;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const createMockBaseView = ( generatedViewIdRef: { current: number } ): NestedTemplatedElementViewClass => {
|
|
27
|
+
const MockBaseView = Object.assign( function BaseViewMock() {}, {
|
|
28
|
+
extend() {
|
|
29
|
+
return Object.assign( function ExtendedViewMock() {}, {
|
|
30
|
+
viewId: ++generatedViewIdRef.current,
|
|
31
|
+
} );
|
|
32
|
+
},
|
|
33
|
+
} );
|
|
34
|
+
|
|
35
|
+
return MockBaseView as unknown as NestedTemplatedElementViewClass;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const createMockOptions = () => ( {
|
|
39
|
+
type: LIST_TYPE,
|
|
40
|
+
renderer: {
|
|
41
|
+
register: jest.fn(),
|
|
42
|
+
render: jest.fn(),
|
|
43
|
+
},
|
|
44
|
+
element: {
|
|
45
|
+
twig_templates: {},
|
|
46
|
+
twig_main_template: 'main',
|
|
47
|
+
atomic_props_schema: {},
|
|
48
|
+
base_styles_dictionary: {},
|
|
49
|
+
default_html_tag: 'ul',
|
|
50
|
+
html_tag_follows_link: false,
|
|
51
|
+
support_nesting: true,
|
|
52
|
+
},
|
|
53
|
+
} );
|
|
54
|
+
|
|
55
|
+
describe( 'initListType', () => {
|
|
56
|
+
beforeEach( () => {
|
|
57
|
+
const generatedViewIdRef = { current: 0 };
|
|
58
|
+
|
|
59
|
+
jest.clearAllMocks();
|
|
60
|
+
resetListType();
|
|
61
|
+
|
|
62
|
+
jest.mocked( createNestedTemplatedElementType ).mockImplementation( () => createMockBaseType() );
|
|
63
|
+
|
|
64
|
+
jest.mocked( createNestedTemplatedElementView ).mockImplementation( () =>
|
|
65
|
+
createMockBaseView( generatedViewIdRef )
|
|
66
|
+
);
|
|
67
|
+
} );
|
|
68
|
+
|
|
69
|
+
afterEach( () => {
|
|
70
|
+
resetListType();
|
|
71
|
+
} );
|
|
72
|
+
|
|
73
|
+
it( 'should reuse the same view class for multiple list type instances', () => {
|
|
74
|
+
// Arrange
|
|
75
|
+
initListType();
|
|
76
|
+
const createListType = elementsLegacyTypes[ LIST_TYPE ];
|
|
77
|
+
const ElementType = createListType( createMockOptions() );
|
|
78
|
+
|
|
79
|
+
// Act
|
|
80
|
+
const typeInstance1 = new ElementType();
|
|
81
|
+
const typeInstance2 = new ElementType();
|
|
82
|
+
const viewClass1 = typeInstance1.getView();
|
|
83
|
+
const viewClass2 = typeInstance2.getView();
|
|
84
|
+
|
|
85
|
+
// Assert
|
|
86
|
+
expect( viewClass1 ).toBe( viewClass2 );
|
|
87
|
+
expect( createNestedTemplatedElementView ).toHaveBeenCalledTimes( 1 );
|
|
88
|
+
} );
|
|
89
|
+
} );
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ELEMENT_STYLE_CHANGE_EVENT, type V1ElementModelProps } from '@elementor/editor-elements';
|
|
2
2
|
|
|
3
|
+
import { computeHtmlTag } from '../renderers/compute-html-tag';
|
|
3
4
|
import { type DomRenderer } from '../renderers/create-dom-renderer';
|
|
4
5
|
import { signalizedProcess } from '../utils/signalized-process';
|
|
5
6
|
import { createPendingElement } from './create-pending-element';
|
|
@@ -93,7 +94,7 @@ export function createNestedTemplatedElementView( {
|
|
|
93
94
|
}: CreateNestedTemplatedElementViewOptions ): NestedTemplatedElementViewClass {
|
|
94
95
|
const legacyWindow = window as unknown as LegacyWindow;
|
|
95
96
|
|
|
96
|
-
const { templateKey, baseStylesDictionary, resolveProps } = setupTwigRenderer( {
|
|
97
|
+
const { templateKey, baseStylesDictionary, resolveProps, defaultHtmlTag, htmlTagFollowsLink } = setupTwigRenderer( {
|
|
97
98
|
type,
|
|
98
99
|
renderer,
|
|
99
100
|
element,
|
|
@@ -198,6 +199,7 @@ export function createNestedTemplatedElementView( {
|
|
|
198
199
|
interaction_id: this.getInteractionId(),
|
|
199
200
|
type,
|
|
200
201
|
settings: resolvedSettings,
|
|
202
|
+
tag: computeHtmlTag( resolvedSettings, defaultHtmlTag, { followLink: htmlTagFollowsLink } ),
|
|
201
203
|
base_styles: baseStylesDictionary,
|
|
202
204
|
editor_attributes: buildEditorAttributes( model ),
|
|
203
205
|
editor_classes: buildEditorClasses( model ),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type V1ElementConfig } from '@elementor/editor-elements';
|
|
2
2
|
|
|
3
|
+
import { computeHtmlTag } from '../renderers/compute-html-tag';
|
|
3
4
|
import { type DomRenderer } from '../renderers/create-dom-renderer';
|
|
4
5
|
import { signalizedProcess } from '../utils/signalized-process';
|
|
5
6
|
import { createElementViewClassDeclaration } from './create-element-type';
|
|
@@ -27,7 +28,8 @@ export type CreateTemplatedElementTypeOptions = {
|
|
|
27
28
|
|
|
28
29
|
export type TemplatedElementConfig = Required<
|
|
29
30
|
Pick< V1ElementConfig, 'twig_templates' | 'twig_main_template' | 'atomic_props_schema' | 'base_styles_dictionary' >
|
|
30
|
-
|
|
31
|
+
> &
|
|
32
|
+
Pick< V1ElementConfig, 'default_html_tag' | 'html_tag_follows_link' >;
|
|
31
33
|
|
|
32
34
|
export function createTemplatedElementType( {
|
|
33
35
|
type,
|
|
@@ -69,7 +71,7 @@ export function createTemplatedElementView( {
|
|
|
69
71
|
}: CreateTemplatedElementTypeOptions ): typeof TemplatedElementView {
|
|
70
72
|
const BaseView = createElementViewClassDeclaration();
|
|
71
73
|
|
|
72
|
-
const { templateKey, baseStylesDictionary, resolveProps } = setupTwigRenderer( {
|
|
74
|
+
const { templateKey, baseStylesDictionary, resolveProps, defaultHtmlTag, htmlTagFollowsLink } = setupTwigRenderer( {
|
|
73
75
|
type,
|
|
74
76
|
renderer,
|
|
75
77
|
element,
|
|
@@ -165,6 +167,7 @@ export function createTemplatedElementView( {
|
|
|
165
167
|
interaction_id: this.getInteractionId(),
|
|
166
168
|
type,
|
|
167
169
|
settings,
|
|
170
|
+
tag: computeHtmlTag( settings, defaultHtmlTag, { followLink: htmlTagFollowsLink } ),
|
|
168
171
|
base_styles: baseStylesDictionary,
|
|
169
172
|
...( this.getResolverRenderContext?.() ?? {} ),
|
|
170
173
|
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createNestedTemplatedElementType,
|
|
3
|
+
type CreateNestedTemplatedElementTypeOptions,
|
|
4
|
+
createNestedTemplatedElementView,
|
|
5
|
+
} from './create-nested-templated-element-type';
|
|
6
|
+
import { registerElementType } from './init-legacy-views';
|
|
7
|
+
import type { ElementType, RenderContext } from './types';
|
|
8
|
+
|
|
9
|
+
const LIST_TYPE = 'e-list';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Initialize the e-list element type with custom render context.
|
|
13
|
+
*/
|
|
14
|
+
export function initListType() {
|
|
15
|
+
registerElementType( LIST_TYPE, ( options ) =>
|
|
16
|
+
createListType( options as CreateNestedTemplatedElementTypeOptions )
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function createListType( options: CreateNestedTemplatedElementTypeOptions ): typeof ElementType {
|
|
21
|
+
const BaseType = createNestedTemplatedElementType( options );
|
|
22
|
+
let ListView: ReturnType< typeof createListView > | null = null;
|
|
23
|
+
|
|
24
|
+
return class extends BaseType {
|
|
25
|
+
getView() {
|
|
26
|
+
if ( ! ListView ) {
|
|
27
|
+
ListView = createListView( options );
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return ListView;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function createListView( options: CreateNestedTemplatedElementTypeOptions ) {
|
|
36
|
+
const BaseView = createNestedTemplatedElementView( options );
|
|
37
|
+
|
|
38
|
+
return BaseView.extend( {
|
|
39
|
+
getRenderContext(): RenderContext | undefined {
|
|
40
|
+
const parentContext = this._parent?.getRenderContext?.();
|
|
41
|
+
const settings = this.model.get( 'settings' );
|
|
42
|
+
const showMarkersProp = settings?.get?.( 'show_markers' ) as unknown;
|
|
43
|
+
const showMarkers = ( showMarkersProp as { value?: boolean } )?.value ?? showMarkersProp ?? true;
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
...parentContext,
|
|
47
|
+
show_markers: showMarkers,
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
getResolverRenderContext(): RenderContext | undefined {
|
|
52
|
+
const parentContext = this._parent?.getResolverRenderContext?.();
|
|
53
|
+
const settings = this.model.get( 'settings' );
|
|
54
|
+
const showMarkersProp = settings?.get?.( 'show_markers' ) as unknown;
|
|
55
|
+
const showMarkers = ( showMarkersProp as { value?: boolean } )?.value ?? showMarkersProp ?? true;
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
...parentContext,
|
|
59
|
+
show_markers: showMarkers,
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
} );
|
|
63
|
+
}
|
|
@@ -13,10 +13,10 @@ const createUnionPropType = ( keys: string[] ): PropType =>
|
|
|
13
13
|
} ) as PropType;
|
|
14
14
|
|
|
15
15
|
describe( 'isInlineEditingAllowed', () => {
|
|
16
|
-
it( 'should allow inline editing for html
|
|
16
|
+
it( 'should allow inline editing for escaped-html prop values', () => {
|
|
17
17
|
expect(
|
|
18
18
|
isInlineEditingAllowed( {
|
|
19
|
-
rawValue: { $$type: 'html
|
|
19
|
+
rawValue: { $$type: 'escaped-html', value: 'Hello' },
|
|
20
20
|
propTypeFromSchema: null,
|
|
21
21
|
} )
|
|
22
22
|
).toBe( true );
|
|
@@ -40,11 +40,11 @@ describe( 'isInlineEditingAllowed', () => {
|
|
|
40
40
|
).toBe( false );
|
|
41
41
|
} );
|
|
42
42
|
|
|
43
|
-
it( 'should allow when value is unset but schema key is html
|
|
43
|
+
it( 'should allow when value is unset but schema key is escaped-html', () => {
|
|
44
44
|
expect(
|
|
45
45
|
isInlineEditingAllowed( {
|
|
46
46
|
rawValue: undefined,
|
|
47
|
-
propTypeFromSchema: createPlainPropType( 'html
|
|
47
|
+
propTypeFromSchema: createPlainPropType( 'escaped-html' ),
|
|
48
48
|
} )
|
|
49
49
|
).toBe( true );
|
|
50
50
|
} );
|
|
@@ -67,16 +67,16 @@ describe( 'isInlineEditingAllowed', () => {
|
|
|
67
67
|
).toBe( false );
|
|
68
68
|
} );
|
|
69
69
|
|
|
70
|
-
it( 'should allow when value is unset and union schema includes html
|
|
70
|
+
it( 'should allow when value is unset and union schema includes escaped-html', () => {
|
|
71
71
|
expect(
|
|
72
72
|
isInlineEditingAllowed( {
|
|
73
73
|
rawValue: undefined,
|
|
74
|
-
propTypeFromSchema: createUnionPropType( [ 'dynamic', 'html
|
|
74
|
+
propTypeFromSchema: createUnionPropType( [ 'dynamic', 'escaped-html' ] ),
|
|
75
75
|
} )
|
|
76
76
|
).toBe( true );
|
|
77
77
|
} );
|
|
78
78
|
|
|
79
|
-
it( 'should disallow when value is unset and union schema does not include html-v3/string', () => {
|
|
79
|
+
it( 'should disallow when value is unset and union schema does not include escaped-html/html-v3/string', () => {
|
|
80
80
|
expect(
|
|
81
81
|
isInlineEditingAllowed( {
|
|
82
82
|
rawValue: undefined,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { getContainer, getElementLabel, getElementType } from '@elementor/editor-elements';
|
|
3
3
|
import {
|
|
4
|
+
escapedHtmlPropTypeUtil,
|
|
4
5
|
htmlV3PropTypeUtil,
|
|
5
|
-
parseHtmlChildren,
|
|
6
6
|
type PropType,
|
|
7
7
|
type PropValue,
|
|
8
8
|
stringPropTypeUtil,
|
|
@@ -126,20 +126,33 @@ export default class InlineEditingReplacement extends ReplacementBase {
|
|
|
126
126
|
|
|
127
127
|
getExtractedContentValue() {
|
|
128
128
|
const propValue = this.getInlineEditablePropValue();
|
|
129
|
+
|
|
130
|
+
if ( escapedHtmlPropTypeUtil.isValid( propValue ) ) {
|
|
131
|
+
return escapedHtmlPropTypeUtil.extract( propValue ) ?? '';
|
|
132
|
+
}
|
|
133
|
+
|
|
129
134
|
const extracted = htmlV3PropTypeUtil.extract( propValue );
|
|
130
135
|
|
|
131
136
|
return stringPropTypeUtil.extract( extracted?.content ?? null ) ?? '';
|
|
132
137
|
}
|
|
133
138
|
|
|
139
|
+
createContentPropValue( value: string | null ): PropValue {
|
|
140
|
+
const content = value || '';
|
|
141
|
+
const propTypeKey = this.getInlineEditablePropTypeKey();
|
|
142
|
+
|
|
143
|
+
if ( propTypeKey === htmlV3PropTypeUtil.key ) {
|
|
144
|
+
return htmlV3PropTypeUtil.create( {
|
|
145
|
+
content: stringPropTypeUtil.create( content ),
|
|
146
|
+
children: [],
|
|
147
|
+
} );
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return escapedHtmlPropTypeUtil.create( content );
|
|
151
|
+
}
|
|
152
|
+
|
|
134
153
|
setContentValue( value: string | null ) {
|
|
135
154
|
const settingKey = this.getInlineEditablePropertyName();
|
|
136
|
-
const
|
|
137
|
-
const parsed = parseHtmlChildren( html );
|
|
138
|
-
|
|
139
|
-
const valueToSave = htmlV3PropTypeUtil.create( {
|
|
140
|
-
content: parsed.content ? stringPropTypeUtil.create( parsed.content ) : null,
|
|
141
|
-
children: parsed.children,
|
|
142
|
-
} );
|
|
155
|
+
const valueToSave = this.createContentPropValue( value );
|
|
143
156
|
|
|
144
157
|
undoable(
|
|
145
158
|
{
|
|
@@ -174,7 +187,7 @@ export default class InlineEditingReplacement extends ReplacementBase {
|
|
|
174
187
|
}
|
|
175
188
|
|
|
176
189
|
if ( propType.kind === 'union' ) {
|
|
177
|
-
const textKeys = [ htmlV3PropTypeUtil.key, stringPropTypeUtil.key ];
|
|
190
|
+
const textKeys = [ escapedHtmlPropTypeUtil.key, htmlV3PropTypeUtil.key, stringPropTypeUtil.key ];
|
|
178
191
|
|
|
179
192
|
for ( const key of textKeys ) {
|
|
180
193
|
if ( propType.prop_types[ key ] ) {
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
escapedHtmlPropTypeUtil,
|
|
3
|
+
htmlV3PropTypeUtil,
|
|
4
|
+
type PropType,
|
|
5
|
+
stringPropTypeUtil,
|
|
6
|
+
} from '@elementor/editor-props';
|
|
2
7
|
|
|
3
8
|
type InlineEditingEligibilityArgs = {
|
|
4
9
|
rawValue: unknown;
|
|
@@ -9,7 +14,7 @@ const hasKey = ( propType: PropType ): propType is PropType & { key: unknown } =
|
|
|
9
14
|
return 'key' in propType;
|
|
10
15
|
};
|
|
11
16
|
|
|
12
|
-
const TEXT_PROP_TYPE_KEYS = new Set( [ htmlV3PropTypeUtil.key, stringPropTypeUtil.key ] );
|
|
17
|
+
const TEXT_PROP_TYPE_KEYS = new Set( [ escapedHtmlPropTypeUtil.key, htmlV3PropTypeUtil.key, stringPropTypeUtil.key ] );
|
|
13
18
|
|
|
14
19
|
const isCoreTextPropTypeKey = ( key: unknown ): boolean => {
|
|
15
20
|
return ( TEXT_PROP_TYPE_KEYS as Set< unknown > ).has( key );
|
|
@@ -36,5 +41,9 @@ export const isInlineEditingAllowed = ( { rawValue, propTypeFromSchema }: Inline
|
|
|
36
41
|
return isAllowedBySchema( propTypeFromSchema );
|
|
37
42
|
}
|
|
38
43
|
|
|
39
|
-
return
|
|
44
|
+
return (
|
|
45
|
+
escapedHtmlPropTypeUtil.isValid( rawValue ) ||
|
|
46
|
+
htmlV3PropTypeUtil.isValid( rawValue ) ||
|
|
47
|
+
stringPropTypeUtil.isValid( rawValue )
|
|
48
|
+
);
|
|
40
49
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { escapedHtmlPropTypeUtil } from '@elementor/editor-props';
|
|
2
2
|
|
|
3
3
|
import { type ModelExtensions } from './create-nested-templated-element-type';
|
|
4
4
|
import { registerModelExtensions } from './init-legacy-views';
|
|
@@ -23,10 +23,7 @@ const tabModelExtensions: ModelExtensions = {
|
|
|
23
23
|
...paragraphElement,
|
|
24
24
|
settings: {
|
|
25
25
|
...paragraphElement.settings,
|
|
26
|
-
paragraph:
|
|
27
|
-
content: stringPropTypeUtil.create( `Tab ${ position }` ),
|
|
28
|
-
children: [],
|
|
29
|
-
} ),
|
|
26
|
+
paragraph: escapedHtmlPropTypeUtil.create( `Tab ${ position }` ),
|
|
30
27
|
},
|
|
31
28
|
};
|
|
32
29
|
|
|
@@ -7,7 +7,8 @@ import { type ElementView, type RenderContext } from './types';
|
|
|
7
7
|
|
|
8
8
|
export type TwigElementConfig = Required<
|
|
9
9
|
Pick< V1ElementConfig, 'twig_templates' | 'twig_main_template' | 'atomic_props_schema' | 'base_styles_dictionary' >
|
|
10
|
-
|
|
10
|
+
> &
|
|
11
|
+
Pick< V1ElementConfig, 'default_html_tag' | 'html_tag_follows_link' >;
|
|
11
12
|
|
|
12
13
|
export type SetupTwigRendererOptions = {
|
|
13
14
|
type: string;
|
|
@@ -19,6 +20,8 @@ export type SetupTwigRendererResult = {
|
|
|
19
20
|
templateKey: string;
|
|
20
21
|
baseStylesDictionary: Record< string, unknown >;
|
|
21
22
|
resolveProps: PropsResolver;
|
|
23
|
+
defaultHtmlTag: string;
|
|
24
|
+
htmlTagFollowsLink: boolean;
|
|
22
25
|
};
|
|
23
26
|
|
|
24
27
|
export function setupTwigRenderer( { renderer, element }: SetupTwigRendererOptions ): SetupTwigRendererResult {
|
|
@@ -34,7 +37,13 @@ export function setupTwigRenderer( { renderer, element }: SetupTwigRendererOptio
|
|
|
34
37
|
schema: element.atomic_props_schema,
|
|
35
38
|
} );
|
|
36
39
|
|
|
37
|
-
return {
|
|
40
|
+
return {
|
|
41
|
+
templateKey,
|
|
42
|
+
baseStylesDictionary,
|
|
43
|
+
resolveProps,
|
|
44
|
+
defaultHtmlTag: element.default_html_tag ?? 'div',
|
|
45
|
+
htmlTagFollowsLink: element.html_tag_follows_link ?? true,
|
|
46
|
+
};
|
|
38
47
|
}
|
|
39
48
|
|
|
40
49
|
export interface TwigViewInterface extends Omit< ElementView, 'getResolverRenderContext' > {
|
package/src/legacy/types.ts
CHANGED
|
@@ -174,6 +174,10 @@ export declare class ElementView {
|
|
|
174
174
|
once: ( event: string, callback: () => void ) => void;
|
|
175
175
|
|
|
176
176
|
getContainer(): V1Element;
|
|
177
|
+
|
|
178
|
+
initialize?( ...args: unknown[] ): void;
|
|
179
|
+
|
|
180
|
+
listenTo( target: unknown, event: string, callback: () => void ): void;
|
|
177
181
|
}
|
|
178
182
|
|
|
179
183
|
export declare class TemplatedElementView extends ElementView {
|
package/src/mcp/canvas-mcp.ts
CHANGED
|
@@ -10,9 +10,8 @@ import { initEditorStateResource } from './resources/editor-state-resource';
|
|
|
10
10
|
import { initGeneralContextResource } from './resources/general-context-resource';
|
|
11
11
|
import { initSelectedElementResource } from './resources/selected-element-resource';
|
|
12
12
|
import { initWidgetsSchemaResource } from './resources/widgets-schema-resource';
|
|
13
|
-
import { initBuildCompositionTool } from './tools/build-composition/tool';
|
|
14
13
|
import { initConfigureElementTool } from './tools/configure-element/tool';
|
|
15
|
-
import {
|
|
14
|
+
import { initGetPageStructureTool } from './tools/get-page-structure/tool';
|
|
16
15
|
import { getDynamicTagNamesByCategories } from './utils/resolve-dynamic-tag';
|
|
17
16
|
|
|
18
17
|
export const initCanvasMcp = ( reg: MCPRegistryEntry ) => {
|
|
@@ -28,8 +27,7 @@ export const initCanvasMcp = ( reg: MCPRegistryEntry ) => {
|
|
|
28
27
|
initEditorStateResource( reg );
|
|
29
28
|
initGeneralContextResource( reg );
|
|
30
29
|
initBestPracticesResource( reg );
|
|
31
|
-
initGetElementConfigTool( reg );
|
|
32
30
|
initConfigureElementTool( reg );
|
|
33
|
-
|
|
31
|
+
initGetPageStructureTool( reg );
|
|
34
32
|
initBreakpointsResource( reg );
|
|
35
33
|
};
|