@elementor/editor-canvas 4.3.3 → 4.3.5
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 +310 -114
- package/dist/index.mjs +268 -69
- package/package.json +21 -21
- package/src/legacy/__tests__/build-editor-attributes.test.ts +41 -0
- package/src/legacy/__tests__/create-pending-element.test.ts +128 -0
- package/src/legacy/__tests__/create-templated-element-type.test.ts +50 -14
- package/src/legacy/create-nested-templated-element-type.ts +4 -2
- package/src/legacy/create-pending-element.ts +9 -5
- package/src/legacy/create-templated-element-type.ts +2 -1
- package/src/mcp/canvas-mcp.ts +5 -0
- package/src/mcp/icon-library-experiment.ts +5 -0
- package/src/mcp/tools/configure-element/prompt.ts +47 -1
- package/src/mcp/tools/find-icons/__tests__/tool.test.ts +143 -0
- package/src/mcp/tools/find-icons/prompt.ts +43 -0
- package/src/mcp/tools/find-icons/tool.ts +153 -0
- package/src/transformers/shared/__tests__/icon-transformer.test.ts +39 -0
- package/src/transformers/shared/icon-transformer.ts +56 -12
package/dist/index.mjs
CHANGED
|
@@ -2062,6 +2062,8 @@ var timeRangeTransformer = createTransformer((value) => {
|
|
|
2062
2062
|
// src/transformers/shared/icon-transformer.ts
|
|
2063
2063
|
import {
|
|
2064
2064
|
getFontAwesome7IconName,
|
|
2065
|
+
isDeletedCustomIconLibrary,
|
|
2066
|
+
resolveCustomIconSvg,
|
|
2065
2067
|
resolveFontAwesome7Icon
|
|
2066
2068
|
} from "@elementor/editor-controls";
|
|
2067
2069
|
|
|
@@ -2086,32 +2088,101 @@ function processSvgContent(svgText) {
|
|
|
2086
2088
|
return svgElement.outerHTML;
|
|
2087
2089
|
}
|
|
2088
2090
|
|
|
2091
|
+
// src/transformers/shared/svg-src-transformer.ts
|
|
2092
|
+
import { getMediaAttachment } from "@elementor/wp-media";
|
|
2093
|
+
async function fetchSvgContent(url, signal) {
|
|
2094
|
+
try {
|
|
2095
|
+
const response = await fetch(url, { signal });
|
|
2096
|
+
if (!response.ok) {
|
|
2097
|
+
return null;
|
|
2098
|
+
}
|
|
2099
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
2100
|
+
const isSvg = contentType.includes("svg") || contentType.includes("xml") || url.endsWith(".svg");
|
|
2101
|
+
if (!isSvg) {
|
|
2102
|
+
return null;
|
|
2103
|
+
}
|
|
2104
|
+
return await response.text();
|
|
2105
|
+
} catch {
|
|
2106
|
+
return null;
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
function resolveSvgSrcId(id) {
|
|
2110
|
+
if (typeof id !== "number" || id <= 0) {
|
|
2111
|
+
return null;
|
|
2112
|
+
}
|
|
2113
|
+
return id;
|
|
2114
|
+
}
|
|
2115
|
+
var svgSrcTransformer = createTransformer(async (value, { signal }) => {
|
|
2116
|
+
const id = resolveSvgSrcId(value.id);
|
|
2117
|
+
const urlFromValue = typeof value.url === "string" ? value.url : null;
|
|
2118
|
+
let url = urlFromValue;
|
|
2119
|
+
if (id && !urlFromValue) {
|
|
2120
|
+
const attachment = await getMediaAttachment({ id });
|
|
2121
|
+
url = attachment?.url ?? null;
|
|
2122
|
+
}
|
|
2123
|
+
const resolvedUrl = typeof url === "string" ? url : null;
|
|
2124
|
+
if (!resolvedUrl) {
|
|
2125
|
+
return { html: null, url: null };
|
|
2126
|
+
}
|
|
2127
|
+
const svgText = await fetchSvgContent(resolvedUrl, signal);
|
|
2128
|
+
const html = svgText ? processSvgContent(svgText) : null;
|
|
2129
|
+
return { html, url: resolvedUrl };
|
|
2130
|
+
});
|
|
2131
|
+
|
|
2089
2132
|
// src/transformers/shared/icon-transformer.ts
|
|
2090
2133
|
var EMPTY_ICON_RESULT = { html: null, url: null };
|
|
2091
2134
|
var ICON_SVG_SIZE = "100%";
|
|
2092
2135
|
var ICON_SVG_OVERFLOW = "visible";
|
|
2136
|
+
var DEFAULT_SVG_RELATIVE_PATH = "images/default-svg.svg";
|
|
2093
2137
|
var iconTransformer = createTransformer(async (value, { signal }) => {
|
|
2094
2138
|
const iconValue = typeof value.value === "string" ? value.value : null;
|
|
2095
2139
|
const library = typeof value.library === "string" ? value.library : null;
|
|
2096
2140
|
if (!iconValue || !library) {
|
|
2097
2141
|
return EMPTY_ICON_RESULT;
|
|
2098
2142
|
}
|
|
2143
|
+
const fontAwesomeHtml = await resolveFontAwesomeSvg(library, iconValue, signal);
|
|
2144
|
+
if (fontAwesomeHtml) {
|
|
2145
|
+
return { html: fontAwesomeHtml, url: null };
|
|
2146
|
+
}
|
|
2147
|
+
if (isDeletedCustomIconLibrary(library, iconValue)) {
|
|
2148
|
+
return resolveDefaultSvg(signal);
|
|
2149
|
+
}
|
|
2150
|
+
const customHtml = await resolveCustomSvg(library, iconValue, signal);
|
|
2151
|
+
if (customHtml) {
|
|
2152
|
+
return { html: customHtml, url: null };
|
|
2153
|
+
}
|
|
2154
|
+
return EMPTY_ICON_RESULT;
|
|
2155
|
+
});
|
|
2156
|
+
async function resolveDefaultSvg(signal) {
|
|
2157
|
+
const assetsUrl = window.elementorCommon?.config?.urls?.assets;
|
|
2158
|
+
if (typeof assetsUrl !== "string" || assetsUrl === "") {
|
|
2159
|
+
return EMPTY_ICON_RESULT;
|
|
2160
|
+
}
|
|
2161
|
+
return svgSrcTransformer(
|
|
2162
|
+
{
|
|
2163
|
+
id: null,
|
|
2164
|
+
url: `${assetsUrl}${DEFAULT_SVG_RELATIVE_PATH}`
|
|
2165
|
+
},
|
|
2166
|
+
{ key: "svg", signal }
|
|
2167
|
+
);
|
|
2168
|
+
}
|
|
2169
|
+
async function resolveCustomSvg(library, iconValue, signal) {
|
|
2170
|
+
const markup = await resolveCustomIconSvg(library, iconValue, signal);
|
|
2171
|
+
return markup ? processIconSvgContent(markup) : null;
|
|
2172
|
+
}
|
|
2173
|
+
async function resolveFontAwesomeSvg(library, iconValue, signal) {
|
|
2099
2174
|
const iconName = getFontAwesome7IconName(iconValue);
|
|
2100
2175
|
if (!iconName) {
|
|
2101
|
-
return
|
|
2176
|
+
return null;
|
|
2102
2177
|
}
|
|
2103
2178
|
const iconData = await resolveFontAwesome7Icon(library, iconName, signal);
|
|
2104
2179
|
if (!iconData) {
|
|
2105
|
-
return
|
|
2106
|
-
}
|
|
2107
|
-
const svgText = buildFontAwesomeSvg(iconData);
|
|
2108
|
-
if (!svgText) {
|
|
2109
|
-
return EMPTY_ICON_RESULT;
|
|
2180
|
+
return null;
|
|
2110
2181
|
}
|
|
2111
|
-
const
|
|
2112
|
-
return
|
|
2113
|
-
}
|
|
2114
|
-
function
|
|
2182
|
+
const svgText = buildPathSvg(iconData);
|
|
2183
|
+
return svgText ? processIconSvgContent(svgText) : null;
|
|
2184
|
+
}
|
|
2185
|
+
function buildPathSvg(iconData) {
|
|
2115
2186
|
if (iconData.paths.length === 0) {
|
|
2116
2187
|
return null;
|
|
2117
2188
|
}
|
|
@@ -2147,13 +2218,13 @@ var imageSrcTransformer = createTransformer((value) => ({
|
|
|
2147
2218
|
}));
|
|
2148
2219
|
|
|
2149
2220
|
// src/transformers/shared/image-transformer.ts
|
|
2150
|
-
import { getMediaAttachment } from "@elementor/wp-media";
|
|
2221
|
+
import { getMediaAttachment as getMediaAttachment2 } from "@elementor/wp-media";
|
|
2151
2222
|
var imageTransformer = createTransformer(async (value) => {
|
|
2152
2223
|
const { src, size: size2 } = value;
|
|
2153
2224
|
if (!src?.id) {
|
|
2154
2225
|
return src?.url ? { src: src.url, alt: src.alt ?? "" } : null;
|
|
2155
2226
|
}
|
|
2156
|
-
const attachment = await
|
|
2227
|
+
const attachment = await getMediaAttachment2({ id: src.id });
|
|
2157
2228
|
const sizedAttachment = attachment?.sizes?.[size2 ?? ""];
|
|
2158
2229
|
if (sizedAttachment) {
|
|
2159
2230
|
return {
|
|
@@ -2179,47 +2250,6 @@ var plainTransformer = createTransformer((value) => {
|
|
|
2179
2250
|
return value;
|
|
2180
2251
|
});
|
|
2181
2252
|
|
|
2182
|
-
// src/transformers/shared/svg-src-transformer.ts
|
|
2183
|
-
import { getMediaAttachment as getMediaAttachment2 } from "@elementor/wp-media";
|
|
2184
|
-
async function fetchSvgContent(url, signal) {
|
|
2185
|
-
try {
|
|
2186
|
-
const response = await fetch(url, { signal });
|
|
2187
|
-
if (!response.ok) {
|
|
2188
|
-
return null;
|
|
2189
|
-
}
|
|
2190
|
-
const contentType = response.headers.get("content-type") ?? "";
|
|
2191
|
-
const isSvg = contentType.includes("svg") || contentType.includes("xml") || url.endsWith(".svg");
|
|
2192
|
-
if (!isSvg) {
|
|
2193
|
-
return null;
|
|
2194
|
-
}
|
|
2195
|
-
return await response.text();
|
|
2196
|
-
} catch {
|
|
2197
|
-
return null;
|
|
2198
|
-
}
|
|
2199
|
-
}
|
|
2200
|
-
function resolveSvgSrcId(id) {
|
|
2201
|
-
if (typeof id !== "number" || id <= 0) {
|
|
2202
|
-
return null;
|
|
2203
|
-
}
|
|
2204
|
-
return id;
|
|
2205
|
-
}
|
|
2206
|
-
var svgSrcTransformer = createTransformer(async (value, { signal }) => {
|
|
2207
|
-
const id = resolveSvgSrcId(value.id);
|
|
2208
|
-
const urlFromValue = typeof value.url === "string" ? value.url : null;
|
|
2209
|
-
let url = urlFromValue;
|
|
2210
|
-
if (id && !urlFromValue) {
|
|
2211
|
-
const attachment = await getMediaAttachment2({ id });
|
|
2212
|
-
url = attachment?.url ?? null;
|
|
2213
|
-
}
|
|
2214
|
-
const resolvedUrl = typeof url === "string" ? url : null;
|
|
2215
|
-
if (!resolvedUrl) {
|
|
2216
|
-
return { html: null, url: null };
|
|
2217
|
-
}
|
|
2218
|
-
const svgText = await fetchSvgContent(resolvedUrl, signal);
|
|
2219
|
-
const html = svgText ? processSvgContent(svgText) : null;
|
|
2220
|
-
return { html, url: resolvedUrl };
|
|
2221
|
-
});
|
|
2222
|
-
|
|
2223
2253
|
// src/transformers/shared/video-src-transformer.ts
|
|
2224
2254
|
import { getMediaAttachment as getMediaAttachment3 } from "@elementor/wp-media";
|
|
2225
2255
|
var videoSrcTransformer = createTransformer(async (value) => {
|
|
@@ -2499,11 +2529,11 @@ function getVal2(val) {
|
|
|
2499
2529
|
var transformOriginTransformer = createTransformer((value) => {
|
|
2500
2530
|
const x = getVal2(value.x);
|
|
2501
2531
|
const y = getVal2(value.y);
|
|
2502
|
-
const
|
|
2503
|
-
if (x === DEFAULT_XY && y === DEFAULT_XY &&
|
|
2532
|
+
const z4 = getVal2(value.z);
|
|
2533
|
+
if (x === DEFAULT_XY && y === DEFAULT_XY && z4 === DEFAULT_Z) {
|
|
2504
2534
|
return null;
|
|
2505
2535
|
}
|
|
2506
|
-
return `${x} ${y} ${
|
|
2536
|
+
return `${x} ${y} ${z4}`;
|
|
2507
2537
|
});
|
|
2508
2538
|
|
|
2509
2539
|
// src/transformers/styles/transform-rotate-transformer.ts
|
|
@@ -2761,20 +2791,24 @@ function isRecord(value) {
|
|
|
2761
2791
|
// src/legacy/create-pending-element.ts
|
|
2762
2792
|
import {
|
|
2763
2793
|
addModelToParent,
|
|
2794
|
+
cloneElementTree,
|
|
2764
2795
|
findModelInDocument,
|
|
2765
2796
|
generateElementId,
|
|
2766
2797
|
getContainer
|
|
2767
2798
|
} from "@elementor/editor-elements";
|
|
2768
2799
|
function createPendingElement(wrapperView, data, options = {}) {
|
|
2769
2800
|
const parentContainer = wrapperView.getContainer();
|
|
2770
|
-
const model = { ...data };
|
|
2771
|
-
if (!model.id) {
|
|
2801
|
+
const model = options.clone ? cloneElementTree(data) : { ...data };
|
|
2802
|
+
if (!options.clone && !model.id) {
|
|
2772
2803
|
model.id = generateElementId();
|
|
2773
2804
|
}
|
|
2774
2805
|
if (!model.elements) {
|
|
2775
2806
|
model.elements = [];
|
|
2776
2807
|
}
|
|
2777
|
-
const added = addModelToParent(parentContainer.id, model,
|
|
2808
|
+
const added = addModelToParent(parentContainer.id, model, {
|
|
2809
|
+
...options,
|
|
2810
|
+
clone: false
|
|
2811
|
+
});
|
|
2778
2812
|
if (!added) {
|
|
2779
2813
|
return void 0;
|
|
2780
2814
|
}
|
|
@@ -2935,6 +2969,7 @@ function createTemplatedElementView({
|
|
|
2935
2969
|
this._domUpdateWasSkipped = false;
|
|
2936
2970
|
this._lastResolvedSettingsHash = settingsHash;
|
|
2937
2971
|
const context = {
|
|
2972
|
+
...element.template_context ?? {},
|
|
2938
2973
|
id: this.model.get("id"),
|
|
2939
2974
|
interaction_id: this.getInteractionId(),
|
|
2940
2975
|
type,
|
|
@@ -3013,10 +3048,12 @@ function buildEditorAttributes(model) {
|
|
|
3013
3048
|
const id = model.get("id");
|
|
3014
3049
|
const originId = model.get("originId");
|
|
3015
3050
|
const cid = model.cid ?? "";
|
|
3051
|
+
const isDecorative = model.get("editor_settings")?.decorative === true;
|
|
3016
3052
|
const attrs = {
|
|
3017
3053
|
"data-model-cid": cid,
|
|
3018
3054
|
"data-interaction-id": originId ?? id,
|
|
3019
|
-
"x-ignore": "true"
|
|
3055
|
+
"x-ignore": "true",
|
|
3056
|
+
...isDecorative ? { "data-e-decorative": "true" } : {}
|
|
3020
3057
|
};
|
|
3021
3058
|
return Object.entries(attrs).map(([key, value]) => `${key}="${value}"`).join(" ");
|
|
3022
3059
|
}
|
|
@@ -4119,6 +4156,11 @@ function initTabsModelExtensions() {
|
|
|
4119
4156
|
// src/mcp/canvas-mcp.ts
|
|
4120
4157
|
import { Schema as Schema3 } from "@elementor/editor-props";
|
|
4121
4158
|
|
|
4159
|
+
// src/mcp/icon-library-experiment.ts
|
|
4160
|
+
import { isExperimentActive } from "@elementor/editor-v1-adapters";
|
|
4161
|
+
var ICON_LIBRARY_EXPERIMENT = "e_svg_library";
|
|
4162
|
+
var isIconLibraryActive = () => isExperimentActive(ICON_LIBRARY_EXPERIMENT);
|
|
4163
|
+
|
|
4122
4164
|
// src/mcp/resources/available-widgets-resource.ts
|
|
4123
4165
|
import { httpService as httpService3 } from "@elementor/http-client";
|
|
4124
4166
|
var MCP_PROXY_URL2 = "elementor/v1/mcp-proxy";
|
|
@@ -4932,8 +4974,46 @@ Expected Schema: ${jsonSchema}`
|
|
|
4932
4974
|
|
|
4933
4975
|
// src/mcp/tools/configure-element/prompt.ts
|
|
4934
4976
|
import { toolPrompts } from "@elementor/editor-mcp";
|
|
4977
|
+
var ICONS_SECTION = `# Icons (the \`svg\` property on e-svg)
|
|
4978
|
+
The \`svg\` property accepts two variants. Prefer a library icon for iconography (carts, arrows, social marks, check marks) \u2014 it needs no upload and inherits \`color\` via \`currentColor\`. Use an uploaded file only for custom artwork such as a brand logo.
|
|
4979
|
+
|
|
4980
|
+
Resolve the icon with the \`find-icons\` tool FIRST, then copy its \`value\` and \`library\` verbatim. Never assemble those strings yourself: Font Awesome renamed many icons between major versions (\`shopping-cart\` became \`cart-shopping\`), so a plausible guess renders nothing. \`find-icons\` matches on names, aliases, human search terms, and categories, so describe the intent \u2014 "add to cart" finds \`cart-shopping\`. It takes a batch of \`queries\`, so resolve every icon you need in one call.
|
|
4981
|
+
|
|
4982
|
+
Library icon (note the nested PropValues \u2014 \`value\` and \`library\` are each a \`string\` PropValue):
|
|
4983
|
+
{ "$$type": "icon", "value": { "value": { "$$type": "string", "value": "fa-solid fa-cart-shopping" }, "library": { "$$type": "string", "value": "fa-solid" } } }
|
|
4984
|
+
|
|
4985
|
+
Uploaded SVG file:
|
|
4986
|
+
{ "$$type": "svg-src", "value": { "id": { "$$type": "image-attachment-id", "value": 123 } } }
|
|
4987
|
+
|
|
4988
|
+
Size and colour an e-svg through \`style\` (\`width\`, \`height\`, \`color\`), never through \`propertiesToChange\`.
|
|
4989
|
+
`;
|
|
4990
|
+
var ICON_EXAMPLE = `
|
|
4991
|
+
Setting a Font Awesome icon on an e-svg, after resolving it with \`find-icons\`:
|
|
4992
|
+
|
|
4993
|
+
\`\`\`json
|
|
4994
|
+
{
|
|
4995
|
+
propertiesToChange: {
|
|
4996
|
+
svg: {
|
|
4997
|
+
$$type: 'icon',
|
|
4998
|
+
value: {
|
|
4999
|
+
value: { $$type: 'string', value: 'fa-solid fa-cart-shopping' },
|
|
5000
|
+
library: { $$type: 'string', value: 'fa-solid' }
|
|
5001
|
+
}
|
|
5002
|
+
}
|
|
5003
|
+
},
|
|
5004
|
+
style: {
|
|
5005
|
+
'width': '1.25rem',
|
|
5006
|
+
'height': '1.25rem',
|
|
5007
|
+
'color': '#1a1a1a'
|
|
5008
|
+
},
|
|
5009
|
+
elementId: 'element-id',
|
|
5010
|
+
elementType: 'e-svg'
|
|
5011
|
+
};
|
|
5012
|
+
\`\`\`
|
|
5013
|
+
`;
|
|
4935
5014
|
var CONFIGURE_ELEMENT_GUIDE_URI = "elementor://canvas/tools/configure-element-guide";
|
|
4936
5015
|
var generatePrompt = () => {
|
|
5016
|
+
const iconLibraryActive = isIconLibraryActive();
|
|
4937
5017
|
const configureElementToolPrompt = toolPrompts("configure-element");
|
|
4938
5018
|
configureElementToolPrompt.description(`
|
|
4939
5019
|
Configure an existing element on the page.
|
|
@@ -4991,7 +5071,8 @@ Put the dynamic object EXACTLY at the node whose schema offers the "dynamic" var
|
|
|
4991
5071
|
}
|
|
4992
5072
|
Image example: { "$$type": "image", "value": { "src": { "$$type": "dynamic", "value": { "name": "<image tag>", "settings": { ... } } } } }
|
|
4993
5073
|
Do NOT send "group" (it is resolved automatically). Use { "settings": {} } only when the tag has no settings.
|
|
4994
|
-
|
|
5074
|
+
|
|
5075
|
+
${iconLibraryActive ? ICONS_SECTION : ""}`);
|
|
4995
5076
|
configureElementToolPrompt.parameter("elementId", "The ID of the element to configure. MANDATORY.");
|
|
4996
5077
|
configureElementToolPrompt.parameter(
|
|
4997
5078
|
"elementType",
|
|
@@ -5028,6 +5109,9 @@ Do NOT send "group" (it is resolved automatically). Use { "settings": {} } only
|
|
|
5028
5109
|
};
|
|
5029
5110
|
\`\`\`
|
|
5030
5111
|
`);
|
|
5112
|
+
if (iconLibraryActive) {
|
|
5113
|
+
configureElementToolPrompt.example(ICON_EXAMPLE);
|
|
5114
|
+
}
|
|
5031
5115
|
configureElementToolPrompt.instruction(
|
|
5032
5116
|
"The $$type property is MANDATORY for every value in propertiesToChange; it is not used in the style parameter (raw CSS only)."
|
|
5033
5117
|
);
|
|
@@ -5201,8 +5285,7 @@ Provide styling as raw CSS via the "style" parameter (a flat map of CSS property
|
|
|
5201
5285
|
}`;
|
|
5202
5286
|
}
|
|
5203
5287
|
|
|
5204
|
-
// src/mcp/tools/
|
|
5205
|
-
import { getCurrentDocument } from "@elementor/editor-documents";
|
|
5288
|
+
// src/mcp/tools/find-icons/tool.ts
|
|
5206
5289
|
import { httpService as httpService6 } from "@elementor/http-client";
|
|
5207
5290
|
import { z as z2 } from "@elementor/schema";
|
|
5208
5291
|
|
|
@@ -5221,24 +5304,137 @@ function getMcpErrorMessage(error, toolName) {
|
|
|
5221
5304
|
return `${toolName} failed with an unknown error.`;
|
|
5222
5305
|
}
|
|
5223
5306
|
|
|
5224
|
-
// src/mcp/tools/
|
|
5307
|
+
// src/mcp/tools/find-icons/prompt.ts
|
|
5308
|
+
import { toolPrompts as toolPrompts2 } from "@elementor/editor-mcp";
|
|
5309
|
+
var findIconsToolPrompt = toolPrompts2("find-icons");
|
|
5310
|
+
findIconsToolPrompt.description(
|
|
5311
|
+
"Finds icons for the `svg` property of an `e-svg` element, across Font Awesome and any icon packs uploaded to this site. Returns the exact `value` and `library` pair to write - never assemble those strings yourself, and never guess an icon name. Call this before placing or configuring an `e-svg`. Nothing has to be uploaded to the Media Library for an icon to work."
|
|
5312
|
+
);
|
|
5313
|
+
findIconsToolPrompt.instruction(
|
|
5314
|
+
'Call it when the user names an icon ("use the cart-shopping icon") and take the first exact `name` match, and also when the user describes a page and you decide icons belong in it ("create a product page") - in that case pass one query per icon you intend to place, in a single call.'
|
|
5315
|
+
);
|
|
5316
|
+
findIconsToolPrompt.instruction(
|
|
5317
|
+
'Write queries the way a person would describe the icon ("shopping cart", "credit card", "phone"). Icons are matched on their name, aliases, Font Awesome search terms and category, so plain language works better than guessed icon names.'
|
|
5318
|
+
);
|
|
5319
|
+
findIconsToolPrompt.instruction(
|
|
5320
|
+
'Results are ordered best first; `matched_on: "name"` is a stronger signal than `matched_on: "term"`. Check each result\'s `license` and only use `free` icons; skip any `pro` entry. Prefer `fa-solid` for UI iconography and keep one style across a page; brand marks only exist in `fa-brands`. An empty `matches` array means no match was found: broaden the phrase instead of inventing a value.'
|
|
5321
|
+
);
|
|
5322
|
+
findIconsToolPrompt.instruction(
|
|
5323
|
+
'Pass the chosen pair to `configure-element` as the `svg` property: { "$$type": "icon", "value": { "value": { "$$type": "string", "value": "fa-solid fa-cart-shopping" }, "library": { "$$type": "string", "value": "fa-solid" } } }. An `e-svg` has no colour or size of its own, so set `width`, `height` and `color` in the same `configure-element` call.'
|
|
5324
|
+
);
|
|
5325
|
+
findIconsToolPrompt.example(`Icon named by the user:
|
|
5326
|
+
{ "queries": ["cart-shopping"] }`);
|
|
5327
|
+
findIconsToolPrompt.example(`Every icon a product page needs, in one call:
|
|
5328
|
+
{ "queries": ["add to cart", "free shipping", "secure payment", "instagram"] }`);
|
|
5329
|
+
findIconsToolPrompt.example(`Browse a category, one style only:
|
|
5330
|
+
{ "category": "shopping", "library": "fa-solid", "perPage": 25 }`);
|
|
5331
|
+
|
|
5332
|
+
// src/mcp/tools/find-icons/tool.ts
|
|
5225
5333
|
var MCP_PROXY_URL5 = "elementor/v1/mcp-proxy";
|
|
5334
|
+
var MAX_QUERIES = 10;
|
|
5335
|
+
var MAX_PER_PAGE = 25;
|
|
5336
|
+
var initFindIconsTool = (reg) => {
|
|
5337
|
+
const { addTool } = reg;
|
|
5338
|
+
addTool({
|
|
5339
|
+
name: "find-icons",
|
|
5340
|
+
description: findIconsToolPrompt.prompt(),
|
|
5341
|
+
schema: {
|
|
5342
|
+
queries: z2.array(z2.string()).max(MAX_QUERIES).optional().describe(
|
|
5343
|
+
'One plain-language phrase per icon you need, for example ["add to cart", "free shipping"]. Batch every icon a page needs into a single call.'
|
|
5344
|
+
),
|
|
5345
|
+
library: z2.string().optional().describe(
|
|
5346
|
+
'Restrict results to one library key reported in "libraries", for example "fa-solid". Omit to search every library installed on the site.'
|
|
5347
|
+
),
|
|
5348
|
+
category: z2.string().optional().describe(
|
|
5349
|
+
'Restrict results to one Font Awesome category reported in "categories", for example "shopping". May be used without a query to browse that category.'
|
|
5350
|
+
),
|
|
5351
|
+
page: z2.number().int().min(1).optional(),
|
|
5352
|
+
perPage: z2.number().int().min(1).max(MAX_PER_PAGE).optional()
|
|
5353
|
+
},
|
|
5354
|
+
outputSchema: {
|
|
5355
|
+
results: z2.array(
|
|
5356
|
+
z2.object({
|
|
5357
|
+
query: z2.string(),
|
|
5358
|
+
matches: z2.array(
|
|
5359
|
+
z2.object({
|
|
5360
|
+
value: z2.string(),
|
|
5361
|
+
library: z2.string(),
|
|
5362
|
+
name: z2.string(),
|
|
5363
|
+
label: z2.string(),
|
|
5364
|
+
matched_on: z2.enum(["name", "alias", "term", "category", "library"]),
|
|
5365
|
+
license: z2.enum(["free", "pro"])
|
|
5366
|
+
})
|
|
5367
|
+
),
|
|
5368
|
+
total: z2.number(),
|
|
5369
|
+
page: z2.number(),
|
|
5370
|
+
per_page: z2.number(),
|
|
5371
|
+
truncated: z2.boolean()
|
|
5372
|
+
})
|
|
5373
|
+
).describe(
|
|
5374
|
+
'One entry per query, each with ranked "matches". Every match carries the "value" and "library" pair to write on the e-svg icon prop, plus "matched_on" and "license".'
|
|
5375
|
+
),
|
|
5376
|
+
libraries: z2.array(z2.string()).optional().describe(
|
|
5377
|
+
"Icon library keys installed on this site. Only sent when nothing matched or when browsing."
|
|
5378
|
+
),
|
|
5379
|
+
customLibraries: z2.record(z2.string(), z2.string()).optional().describe(
|
|
5380
|
+
"Custom (uploaded) icon libraries keyed by library. Only sent when nothing matched or when browsing."
|
|
5381
|
+
),
|
|
5382
|
+
categories: z2.array(z2.string()).optional().describe(
|
|
5383
|
+
'Icon categories for the "category" filter. Only sent when nothing matched or when browsing.'
|
|
5384
|
+
),
|
|
5385
|
+
truncatedCustomLibraries: z2.array(z2.string()).optional().describe("Custom libraries with more than 250 icons; only their first 250 icons are searchable."),
|
|
5386
|
+
fontAwesomeVersion: z2.string().nullable().describe("Font Awesome version the site renders icons from."),
|
|
5387
|
+
llmInstructions: z2.string().optional().describe("Present only when nothing matched, explaining what to do next.")
|
|
5388
|
+
},
|
|
5389
|
+
handler: async ({ queries, library, category, page, perPage }) => {
|
|
5390
|
+
try {
|
|
5391
|
+
const { data } = await httpService6().post(MCP_PROXY_URL5, {
|
|
5392
|
+
tool: "find-icons",
|
|
5393
|
+
input: {
|
|
5394
|
+
...queries ? { queries } : {},
|
|
5395
|
+
...library ? { library } : {},
|
|
5396
|
+
...category ? { category } : {},
|
|
5397
|
+
...page ? { page } : {},
|
|
5398
|
+
...perPage ? { per_page: perPage } : {}
|
|
5399
|
+
}
|
|
5400
|
+
});
|
|
5401
|
+
return {
|
|
5402
|
+
results: data.data.results,
|
|
5403
|
+
...data.data.libraries ? { libraries: data.data.libraries } : {},
|
|
5404
|
+
...data.data.custom_libraries ? { customLibraries: data.data.custom_libraries } : {},
|
|
5405
|
+
...data.data.categories ? { categories: data.data.categories } : {},
|
|
5406
|
+
...data.data.truncated_custom_libraries ? { truncatedCustomLibraries: data.data.truncated_custom_libraries } : {},
|
|
5407
|
+
fontAwesomeVersion: data.data.font_awesome_version,
|
|
5408
|
+
...data.data.llm_instructions ? { llmInstructions: data.data.llm_instructions } : {}
|
|
5409
|
+
};
|
|
5410
|
+
} catch (error) {
|
|
5411
|
+
throw new Error(getMcpErrorMessage(error, "find-icons"));
|
|
5412
|
+
}
|
|
5413
|
+
}
|
|
5414
|
+
});
|
|
5415
|
+
};
|
|
5416
|
+
|
|
5417
|
+
// src/mcp/tools/get-page-structure/tool.ts
|
|
5418
|
+
import { getCurrentDocument } from "@elementor/editor-documents";
|
|
5419
|
+
import { httpService as httpService7 } from "@elementor/http-client";
|
|
5420
|
+
import { z as z3 } from "@elementor/schema";
|
|
5421
|
+
var MCP_PROXY_URL6 = "elementor/v1/mcp-proxy";
|
|
5226
5422
|
var initGetPageStructureTool = (reg) => {
|
|
5227
5423
|
const { addTool } = reg;
|
|
5228
5424
|
addTool({
|
|
5229
5425
|
name: "get-page-structure",
|
|
5230
5426
|
description: "Returns a lean Elementor element tree skeleton (id, elType, widgetType, title, nested elements) for a post or page. If no postId is provided, uses the currently open document. Optionally scope to a subtree with elementId. Set includeContent=true (requires elementId) to also return each node's settings and styles.",
|
|
5231
5427
|
schema: {
|
|
5232
|
-
postId:
|
|
5428
|
+
postId: z3.number().optional().describe(
|
|
5233
5429
|
"WordPress post ID of the Elementor document. If omitted, uses the currently open document."
|
|
5234
5430
|
),
|
|
5235
|
-
elementId:
|
|
5236
|
-
includeContent:
|
|
5431
|
+
elementId: z3.string().optional().describe("If provided, returns only the subtree rooted at that element id."),
|
|
5432
|
+
includeContent: z3.boolean().optional().describe(
|
|
5237
5433
|
"If true, includes each node's settings and styles (same shape build-composition accepts as input). Requires elementId."
|
|
5238
5434
|
)
|
|
5239
5435
|
},
|
|
5240
5436
|
outputSchema: {
|
|
5241
|
-
elements:
|
|
5437
|
+
elements: z3.array(z3.any()).describe(
|
|
5242
5438
|
"Skeleton of Elementor elements (id, elType, widgetType, title, nested elements). When includeContent is true, each node also includes settings and styles."
|
|
5243
5439
|
)
|
|
5244
5440
|
},
|
|
@@ -5248,7 +5444,7 @@ var initGetPageStructureTool = (reg) => {
|
|
|
5248
5444
|
throw new Error("No post ID provided and no active document found.");
|
|
5249
5445
|
}
|
|
5250
5446
|
try {
|
|
5251
|
-
const { data } = await
|
|
5447
|
+
const { data } = await httpService7().post(MCP_PROXY_URL6, {
|
|
5252
5448
|
tool: "get-page-structure",
|
|
5253
5449
|
input: {
|
|
5254
5450
|
post_id: resolvedPostId,
|
|
@@ -5278,6 +5474,9 @@ var initCanvasMcp = (reg) => {
|
|
|
5278
5474
|
initGeneralContextResource(reg);
|
|
5279
5475
|
initBestPracticesResource(reg);
|
|
5280
5476
|
initConfigureElementTool(reg);
|
|
5477
|
+
if (isIconLibraryActive()) {
|
|
5478
|
+
initFindIconsTool(reg);
|
|
5479
|
+
}
|
|
5281
5480
|
initGetPageStructureTool(reg);
|
|
5282
5481
|
initBreakpointsResource(reg);
|
|
5283
5482
|
};
|
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.
|
|
4
|
+
"version": "4.3.5",
|
|
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.
|
|
41
|
-
"@elementor/editor-controls": "4.3.
|
|
42
|
-
"@elementor/editor-documents": "4.3.
|
|
43
|
-
"@elementor/editor-elements": "4.3.
|
|
44
|
-
"@elementor/editor-interactions": "4.3.
|
|
45
|
-
"@elementor/editor-mcp": "4.3.
|
|
46
|
-
"@elementor/editor-notifications": "4.3.
|
|
47
|
-
"@elementor/editor-props": "4.3.
|
|
48
|
-
"@elementor/editor-responsive": "4.3.
|
|
49
|
-
"@elementor/editor-styles": "4.3.
|
|
50
|
-
"@elementor/editor-styles-repository": "4.3.
|
|
51
|
-
"@elementor/editor-ui": "4.3.
|
|
52
|
-
"@elementor/editor-v1-adapters": "4.3.
|
|
53
|
-
"@elementor/events": "4.3.
|
|
54
|
-
"@elementor/http-client": "4.3.
|
|
55
|
-
"@elementor/schema": "4.3.
|
|
56
|
-
"@elementor/twing": "4.3.
|
|
57
|
-
"@elementor/ui": "1.
|
|
58
|
-
"@elementor/utils": "4.3.
|
|
59
|
-
"@elementor/wp-media": "4.3.
|
|
40
|
+
"@elementor/editor": "4.3.5",
|
|
41
|
+
"@elementor/editor-controls": "4.3.5",
|
|
42
|
+
"@elementor/editor-documents": "4.3.5",
|
|
43
|
+
"@elementor/editor-elements": "4.3.5",
|
|
44
|
+
"@elementor/editor-interactions": "4.3.5",
|
|
45
|
+
"@elementor/editor-mcp": "4.3.5",
|
|
46
|
+
"@elementor/editor-notifications": "4.3.5",
|
|
47
|
+
"@elementor/editor-props": "4.3.5",
|
|
48
|
+
"@elementor/editor-responsive": "4.3.5",
|
|
49
|
+
"@elementor/editor-styles": "4.3.5",
|
|
50
|
+
"@elementor/editor-styles-repository": "4.3.5",
|
|
51
|
+
"@elementor/editor-ui": "4.3.5",
|
|
52
|
+
"@elementor/editor-v1-adapters": "4.3.5",
|
|
53
|
+
"@elementor/events": "4.3.5",
|
|
54
|
+
"@elementor/http-client": "4.3.5",
|
|
55
|
+
"@elementor/schema": "4.3.5",
|
|
56
|
+
"@elementor/twing": "4.3.5",
|
|
57
|
+
"@elementor/ui": "1.41.1",
|
|
58
|
+
"@elementor/utils": "4.3.5",
|
|
59
|
+
"@elementor/wp-media": "4.3.5",
|
|
60
60
|
"@floating-ui/react": "^0.27.5",
|
|
61
61
|
"@wordpress/i18n": "^5.13.0",
|
|
62
62
|
"dompurify": "^3.2.6"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { buildEditorAttributes } from '../create-nested-templated-element-type';
|
|
2
|
+
import { type ElementView } from '../types';
|
|
3
|
+
|
|
4
|
+
const createModel = ( editorSettings?: Record< string, unknown > ) =>
|
|
5
|
+
( {
|
|
6
|
+
cid: 'c1',
|
|
7
|
+
get: ( key: string ) =>
|
|
8
|
+
( {
|
|
9
|
+
id: 'abc123',
|
|
10
|
+
editor_settings: editorSettings,
|
|
11
|
+
} )[ key ],
|
|
12
|
+
} ) as unknown as ElementView[ 'model' ];
|
|
13
|
+
|
|
14
|
+
describe( 'buildEditorAttributes', () => {
|
|
15
|
+
it( 'should mark decorative elements', () => {
|
|
16
|
+
// Arrange
|
|
17
|
+
const model = createModel( { decorative: true } );
|
|
18
|
+
|
|
19
|
+
// Act
|
|
20
|
+
const attributes = buildEditorAttributes( model );
|
|
21
|
+
|
|
22
|
+
// Assert
|
|
23
|
+
expect( attributes ).toContain( 'data-e-decorative="true"' );
|
|
24
|
+
} );
|
|
25
|
+
|
|
26
|
+
it.each( [
|
|
27
|
+
[ 'no editor settings', undefined ],
|
|
28
|
+
[ 'decorative disabled', { decorative: false } ],
|
|
29
|
+
[ 'other editor settings only', { title: 'Hero' } ],
|
|
30
|
+
] )( 'should not mark the element when it has %s', ( _, editorSettings ) => {
|
|
31
|
+
// Arrange
|
|
32
|
+
const model = createModel( editorSettings );
|
|
33
|
+
|
|
34
|
+
// Act
|
|
35
|
+
const attributes = buildEditorAttributes( model );
|
|
36
|
+
|
|
37
|
+
// Assert
|
|
38
|
+
expect( attributes ).not.toContain( 'data-e-decorative' );
|
|
39
|
+
expect( attributes ).toContain( 'data-interaction-id="abc123"' );
|
|
40
|
+
} );
|
|
41
|
+
} );
|