@elementor/editor-canvas 4.3.0-1059 → 4.3.0-1061
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.js +98 -21
- package/dist/index.mjs +98 -21
- package/package.json +20 -20
- package/src/init-settings-transformers.ts +2 -0
- package/src/mcp/tools/configure-element/prompt.ts +1 -1
- 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/dist/index.js
CHANGED
|
@@ -2107,6 +2107,102 @@ var timeRangeTransformer = createTransformer((value) => {
|
|
|
2107
2107
|
};
|
|
2108
2108
|
});
|
|
2109
2109
|
|
|
2110
|
+
// src/transformers/shared/process-svg-content.ts
|
|
2111
|
+
var import_dompurify2 = __toESM(require("dompurify"));
|
|
2112
|
+
var SVG_INLINE_STYLES = "width: 100%; height: 100%; overflow: unset;";
|
|
2113
|
+
function processSvgContent(svgText) {
|
|
2114
|
+
const sanitized = import_dompurify2.default.sanitize(svgText, {
|
|
2115
|
+
USE_PROFILES: { svg: true, svgFilters: true }
|
|
2116
|
+
});
|
|
2117
|
+
const parser = new DOMParser();
|
|
2118
|
+
const doc = parser.parseFromString(sanitized, "image/svg+xml");
|
|
2119
|
+
const svgElement = doc.querySelector("svg");
|
|
2120
|
+
if (!svgElement) {
|
|
2121
|
+
return null;
|
|
2122
|
+
}
|
|
2123
|
+
svgElement.setAttribute("fill", "currentColor");
|
|
2124
|
+
const existingStyle = svgElement.getAttribute("style") ?? "";
|
|
2125
|
+
const trimmed = existingStyle.trim();
|
|
2126
|
+
const merged = trimmed ? `${trimmed.replace(/;$/, "")}; ${SVG_INLINE_STYLES}` : SVG_INLINE_STYLES;
|
|
2127
|
+
svgElement.setAttribute("style", merged);
|
|
2128
|
+
return svgElement.outerHTML;
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
// src/transformers/shared/icon-transformer.ts
|
|
2132
|
+
var FONT_AWESOME_JSON = {
|
|
2133
|
+
width: 0,
|
|
2134
|
+
height: 1,
|
|
2135
|
+
path: 4
|
|
2136
|
+
};
|
|
2137
|
+
var fontAwesomeJsonCache = /* @__PURE__ */ new Map();
|
|
2138
|
+
var iconTransformer = createTransformer(async (value, { signal }) => {
|
|
2139
|
+
const iconValue = typeof value.value === "string" ? value.value : null;
|
|
2140
|
+
const library = typeof value.library === "string" ? value.library : null;
|
|
2141
|
+
if (!iconValue || !library) {
|
|
2142
|
+
return { html: null, url: null };
|
|
2143
|
+
}
|
|
2144
|
+
const iconName = getFontAwesomeIconName(iconValue);
|
|
2145
|
+
const jsonFileName = getFontAwesomeJsonFileName(library);
|
|
2146
|
+
if (!iconName || !jsonFileName) {
|
|
2147
|
+
return { html: null, url: null };
|
|
2148
|
+
}
|
|
2149
|
+
const icons = await fetchFontAwesomeIcons(jsonFileName, signal);
|
|
2150
|
+
const iconData = icons?.[iconName];
|
|
2151
|
+
if (!iconData) {
|
|
2152
|
+
return { html: null, url: null };
|
|
2153
|
+
}
|
|
2154
|
+
const svgText = buildFontAwesomeSvg(iconData);
|
|
2155
|
+
const html = processSvgContent(svgText);
|
|
2156
|
+
return { html, url: null };
|
|
2157
|
+
});
|
|
2158
|
+
function getFontAwesomeIconName(iconValue) {
|
|
2159
|
+
const match = iconValue.match(/^fa\S*\s+fa-(.+)$/);
|
|
2160
|
+
return match?.[1] ?? null;
|
|
2161
|
+
}
|
|
2162
|
+
function getFontAwesomeJsonFileName(library) {
|
|
2163
|
+
if (!library.startsWith("fa-")) {
|
|
2164
|
+
return null;
|
|
2165
|
+
}
|
|
2166
|
+
return library.replace(/^fa-/, "");
|
|
2167
|
+
}
|
|
2168
|
+
function getAssetsBaseUrl() {
|
|
2169
|
+
const assetsUrl = window.elementorCommon?.config?.urls?.assets;
|
|
2170
|
+
return typeof assetsUrl === "string" && assetsUrl !== "" ? assetsUrl : null;
|
|
2171
|
+
}
|
|
2172
|
+
async function fetchFontAwesomeIcons(jsonFileName, signal) {
|
|
2173
|
+
const cached = fontAwesomeJsonCache.get(jsonFileName);
|
|
2174
|
+
if (cached) {
|
|
2175
|
+
return cached;
|
|
2176
|
+
}
|
|
2177
|
+
const icons = await loadFontAwesomeIcons(jsonFileName, signal);
|
|
2178
|
+
if (icons) {
|
|
2179
|
+
fontAwesomeJsonCache.set(jsonFileName, icons);
|
|
2180
|
+
}
|
|
2181
|
+
return icons;
|
|
2182
|
+
}
|
|
2183
|
+
async function loadFontAwesomeIcons(jsonFileName, signal) {
|
|
2184
|
+
const assetsUrl = getAssetsBaseUrl();
|
|
2185
|
+
if (!assetsUrl) {
|
|
2186
|
+
return null;
|
|
2187
|
+
}
|
|
2188
|
+
try {
|
|
2189
|
+
const response = await fetch(`${assetsUrl}lib/font-awesome/json/${jsonFileName}.json`, { signal });
|
|
2190
|
+
if (!response.ok) {
|
|
2191
|
+
return null;
|
|
2192
|
+
}
|
|
2193
|
+
const data = await response.json();
|
|
2194
|
+
return data.icons ?? null;
|
|
2195
|
+
} catch {
|
|
2196
|
+
return null;
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
function buildFontAwesomeSvg(iconData) {
|
|
2200
|
+
const width = iconData[FONT_AWESOME_JSON.width];
|
|
2201
|
+
const height = iconData[FONT_AWESOME_JSON.height];
|
|
2202
|
+
const path = iconData[FONT_AWESOME_JSON.path];
|
|
2203
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}"><path d="${path}"></path></svg>`;
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2110
2206
|
// src/transformers/shared/image-src-transformer.ts
|
|
2111
2207
|
var imageSrcTransformer = createTransformer((value) => ({
|
|
2112
2208
|
id: value.id ?? null,
|
|
@@ -2148,26 +2244,7 @@ var plainTransformer = createTransformer((value) => {
|
|
|
2148
2244
|
});
|
|
2149
2245
|
|
|
2150
2246
|
// src/transformers/shared/svg-src-transformer.ts
|
|
2151
|
-
var import_dompurify2 = __toESM(require("dompurify"));
|
|
2152
2247
|
var import_wp_media2 = require("@elementor/wp-media");
|
|
2153
|
-
var SVG_INLINE_STYLES = "width: 100%; height: 100%; overflow: unset;";
|
|
2154
|
-
function processSvgContent(svgText) {
|
|
2155
|
-
const sanitized = import_dompurify2.default.sanitize(svgText, {
|
|
2156
|
-
USE_PROFILES: { svg: true, svgFilters: true }
|
|
2157
|
-
});
|
|
2158
|
-
const parser = new DOMParser();
|
|
2159
|
-
const doc = parser.parseFromString(sanitized, "image/svg+xml");
|
|
2160
|
-
const svgElement = doc.querySelector("svg");
|
|
2161
|
-
if (!svgElement) {
|
|
2162
|
-
return null;
|
|
2163
|
-
}
|
|
2164
|
-
svgElement.setAttribute("fill", "currentColor");
|
|
2165
|
-
const existingStyle = svgElement.getAttribute("style") ?? "";
|
|
2166
|
-
const trimmed = existingStyle.trim();
|
|
2167
|
-
const merged = trimmed ? `${trimmed.replace(/;$/, "")}; ${SVG_INLINE_STYLES}` : SVG_INLINE_STYLES;
|
|
2168
|
-
svgElement.setAttribute("style", merged);
|
|
2169
|
-
return svgElement.outerHTML;
|
|
2170
|
-
}
|
|
2171
2248
|
async function fetchSvgContent(url, signal) {
|
|
2172
2249
|
try {
|
|
2173
2250
|
const response = await fetch(url, { signal });
|
|
@@ -2223,7 +2300,7 @@ var videoSrcTransformer = createTransformer(async (value) => {
|
|
|
2223
2300
|
|
|
2224
2301
|
// src/init-settings-transformers.ts
|
|
2225
2302
|
function initSettingsTransformers() {
|
|
2226
|
-
settingsTransformersRegistry.register("classes", createClassesTransformer()).register("link", linkTransformer).register("query", queryTransformer).register("image", imageTransformer).register("image-src", imageSrcTransformer).register("svg-src", svgSrcTransformer).register("video-src", videoSrcTransformer).register("attributes", attributesTransformer).register("date-time", dateTimeTransformer).register("html-v2", htmlV2Transformer).register("html-v3", htmlV3Transformer).register("escaped-html", escapedHtmlTransformer).register("date-range", dateRangeTransformer).register("time-range", timeRangeTransformer).registerFallback(plainTransformer);
|
|
2303
|
+
settingsTransformersRegistry.register("classes", createClassesTransformer()).register("link", linkTransformer).register("query", queryTransformer).register("image", imageTransformer).register("image-src", imageSrcTransformer).register("svg-src", svgSrcTransformer).register("icon", iconTransformer).register("video-src", videoSrcTransformer).register("attributes", attributesTransformer).register("date-time", dateTimeTransformer).register("html-v2", htmlV2Transformer).register("html-v3", htmlV3Transformer).register("escaped-html", escapedHtmlTransformer).register("date-range", dateRangeTransformer).register("time-range", timeRangeTransformer).registerFallback(plainTransformer);
|
|
2227
2304
|
}
|
|
2228
2305
|
|
|
2229
2306
|
// src/transformers/styles/background-color-overlay-transformer.ts
|
|
@@ -4915,7 +4992,7 @@ Do NOT send "group" (it is resolved automatically). Use { "settings": {} } only
|
|
|
4915
4992
|
);
|
|
4916
4993
|
configureElementToolPrompt.parameter(
|
|
4917
4994
|
"style",
|
|
4918
|
-
'A flat map of raw CSS declarations (property \u2192 value), e.g. { "line-height": "1.25rem", "color": "var(--primary-text, #000)" }. Set a value to null to reset that property to its default. OPTIONAL.'
|
|
4995
|
+
'A flat map of raw CSS declarations (property \u2192 value), e.g. { "line-height": "1.25rem", "color": "var(--primary-text, #000)" }. font-family must be a single Google Font name or a var(--label) \u2014 no fallback stacks. Set a value to null to reset that property to its default. OPTIONAL.'
|
|
4919
4996
|
);
|
|
4920
4997
|
configureElementToolPrompt.example(`
|
|
4921
4998
|
\`\`\`json
|
package/dist/index.mjs
CHANGED
|
@@ -2059,6 +2059,102 @@ var timeRangeTransformer = createTransformer((value) => {
|
|
|
2059
2059
|
};
|
|
2060
2060
|
});
|
|
2061
2061
|
|
|
2062
|
+
// src/transformers/shared/process-svg-content.ts
|
|
2063
|
+
import DOMPurify2 from "dompurify";
|
|
2064
|
+
var SVG_INLINE_STYLES = "width: 100%; height: 100%; overflow: unset;";
|
|
2065
|
+
function processSvgContent(svgText) {
|
|
2066
|
+
const sanitized = DOMPurify2.sanitize(svgText, {
|
|
2067
|
+
USE_PROFILES: { svg: true, svgFilters: true }
|
|
2068
|
+
});
|
|
2069
|
+
const parser = new DOMParser();
|
|
2070
|
+
const doc = parser.parseFromString(sanitized, "image/svg+xml");
|
|
2071
|
+
const svgElement = doc.querySelector("svg");
|
|
2072
|
+
if (!svgElement) {
|
|
2073
|
+
return null;
|
|
2074
|
+
}
|
|
2075
|
+
svgElement.setAttribute("fill", "currentColor");
|
|
2076
|
+
const existingStyle = svgElement.getAttribute("style") ?? "";
|
|
2077
|
+
const trimmed = existingStyle.trim();
|
|
2078
|
+
const merged = trimmed ? `${trimmed.replace(/;$/, "")}; ${SVG_INLINE_STYLES}` : SVG_INLINE_STYLES;
|
|
2079
|
+
svgElement.setAttribute("style", merged);
|
|
2080
|
+
return svgElement.outerHTML;
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
// src/transformers/shared/icon-transformer.ts
|
|
2084
|
+
var FONT_AWESOME_JSON = {
|
|
2085
|
+
width: 0,
|
|
2086
|
+
height: 1,
|
|
2087
|
+
path: 4
|
|
2088
|
+
};
|
|
2089
|
+
var fontAwesomeJsonCache = /* @__PURE__ */ new Map();
|
|
2090
|
+
var iconTransformer = createTransformer(async (value, { signal }) => {
|
|
2091
|
+
const iconValue = typeof value.value === "string" ? value.value : null;
|
|
2092
|
+
const library = typeof value.library === "string" ? value.library : null;
|
|
2093
|
+
if (!iconValue || !library) {
|
|
2094
|
+
return { html: null, url: null };
|
|
2095
|
+
}
|
|
2096
|
+
const iconName = getFontAwesomeIconName(iconValue);
|
|
2097
|
+
const jsonFileName = getFontAwesomeJsonFileName(library);
|
|
2098
|
+
if (!iconName || !jsonFileName) {
|
|
2099
|
+
return { html: null, url: null };
|
|
2100
|
+
}
|
|
2101
|
+
const icons = await fetchFontAwesomeIcons(jsonFileName, signal);
|
|
2102
|
+
const iconData = icons?.[iconName];
|
|
2103
|
+
if (!iconData) {
|
|
2104
|
+
return { html: null, url: null };
|
|
2105
|
+
}
|
|
2106
|
+
const svgText = buildFontAwesomeSvg(iconData);
|
|
2107
|
+
const html = processSvgContent(svgText);
|
|
2108
|
+
return { html, url: null };
|
|
2109
|
+
});
|
|
2110
|
+
function getFontAwesomeIconName(iconValue) {
|
|
2111
|
+
const match = iconValue.match(/^fa\S*\s+fa-(.+)$/);
|
|
2112
|
+
return match?.[1] ?? null;
|
|
2113
|
+
}
|
|
2114
|
+
function getFontAwesomeJsonFileName(library) {
|
|
2115
|
+
if (!library.startsWith("fa-")) {
|
|
2116
|
+
return null;
|
|
2117
|
+
}
|
|
2118
|
+
return library.replace(/^fa-/, "");
|
|
2119
|
+
}
|
|
2120
|
+
function getAssetsBaseUrl() {
|
|
2121
|
+
const assetsUrl = window.elementorCommon?.config?.urls?.assets;
|
|
2122
|
+
return typeof assetsUrl === "string" && assetsUrl !== "" ? assetsUrl : null;
|
|
2123
|
+
}
|
|
2124
|
+
async function fetchFontAwesomeIcons(jsonFileName, signal) {
|
|
2125
|
+
const cached = fontAwesomeJsonCache.get(jsonFileName);
|
|
2126
|
+
if (cached) {
|
|
2127
|
+
return cached;
|
|
2128
|
+
}
|
|
2129
|
+
const icons = await loadFontAwesomeIcons(jsonFileName, signal);
|
|
2130
|
+
if (icons) {
|
|
2131
|
+
fontAwesomeJsonCache.set(jsonFileName, icons);
|
|
2132
|
+
}
|
|
2133
|
+
return icons;
|
|
2134
|
+
}
|
|
2135
|
+
async function loadFontAwesomeIcons(jsonFileName, signal) {
|
|
2136
|
+
const assetsUrl = getAssetsBaseUrl();
|
|
2137
|
+
if (!assetsUrl) {
|
|
2138
|
+
return null;
|
|
2139
|
+
}
|
|
2140
|
+
try {
|
|
2141
|
+
const response = await fetch(`${assetsUrl}lib/font-awesome/json/${jsonFileName}.json`, { signal });
|
|
2142
|
+
if (!response.ok) {
|
|
2143
|
+
return null;
|
|
2144
|
+
}
|
|
2145
|
+
const data = await response.json();
|
|
2146
|
+
return data.icons ?? null;
|
|
2147
|
+
} catch {
|
|
2148
|
+
return null;
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
function buildFontAwesomeSvg(iconData) {
|
|
2152
|
+
const width = iconData[FONT_AWESOME_JSON.width];
|
|
2153
|
+
const height = iconData[FONT_AWESOME_JSON.height];
|
|
2154
|
+
const path = iconData[FONT_AWESOME_JSON.path];
|
|
2155
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}"><path d="${path}"></path></svg>`;
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2062
2158
|
// src/transformers/shared/image-src-transformer.ts
|
|
2063
2159
|
var imageSrcTransformer = createTransformer((value) => ({
|
|
2064
2160
|
id: value.id ?? null,
|
|
@@ -2100,26 +2196,7 @@ var plainTransformer = createTransformer((value) => {
|
|
|
2100
2196
|
});
|
|
2101
2197
|
|
|
2102
2198
|
// src/transformers/shared/svg-src-transformer.ts
|
|
2103
|
-
import DOMPurify2 from "dompurify";
|
|
2104
2199
|
import { getMediaAttachment as getMediaAttachment2 } from "@elementor/wp-media";
|
|
2105
|
-
var SVG_INLINE_STYLES = "width: 100%; height: 100%; overflow: unset;";
|
|
2106
|
-
function processSvgContent(svgText) {
|
|
2107
|
-
const sanitized = DOMPurify2.sanitize(svgText, {
|
|
2108
|
-
USE_PROFILES: { svg: true, svgFilters: true }
|
|
2109
|
-
});
|
|
2110
|
-
const parser = new DOMParser();
|
|
2111
|
-
const doc = parser.parseFromString(sanitized, "image/svg+xml");
|
|
2112
|
-
const svgElement = doc.querySelector("svg");
|
|
2113
|
-
if (!svgElement) {
|
|
2114
|
-
return null;
|
|
2115
|
-
}
|
|
2116
|
-
svgElement.setAttribute("fill", "currentColor");
|
|
2117
|
-
const existingStyle = svgElement.getAttribute("style") ?? "";
|
|
2118
|
-
const trimmed = existingStyle.trim();
|
|
2119
|
-
const merged = trimmed ? `${trimmed.replace(/;$/, "")}; ${SVG_INLINE_STYLES}` : SVG_INLINE_STYLES;
|
|
2120
|
-
svgElement.setAttribute("style", merged);
|
|
2121
|
-
return svgElement.outerHTML;
|
|
2122
|
-
}
|
|
2123
2200
|
async function fetchSvgContent(url, signal) {
|
|
2124
2201
|
try {
|
|
2125
2202
|
const response = await fetch(url, { signal });
|
|
@@ -2175,7 +2252,7 @@ var videoSrcTransformer = createTransformer(async (value) => {
|
|
|
2175
2252
|
|
|
2176
2253
|
// src/init-settings-transformers.ts
|
|
2177
2254
|
function initSettingsTransformers() {
|
|
2178
|
-
settingsTransformersRegistry.register("classes", createClassesTransformer()).register("link", linkTransformer).register("query", queryTransformer).register("image", imageTransformer).register("image-src", imageSrcTransformer).register("svg-src", svgSrcTransformer).register("video-src", videoSrcTransformer).register("attributes", attributesTransformer).register("date-time", dateTimeTransformer).register("html-v2", htmlV2Transformer).register("html-v3", htmlV3Transformer).register("escaped-html", escapedHtmlTransformer).register("date-range", dateRangeTransformer).register("time-range", timeRangeTransformer).registerFallback(plainTransformer);
|
|
2255
|
+
settingsTransformersRegistry.register("classes", createClassesTransformer()).register("link", linkTransformer).register("query", queryTransformer).register("image", imageTransformer).register("image-src", imageSrcTransformer).register("svg-src", svgSrcTransformer).register("icon", iconTransformer).register("video-src", videoSrcTransformer).register("attributes", attributesTransformer).register("date-time", dateTimeTransformer).register("html-v2", htmlV2Transformer).register("html-v3", htmlV3Transformer).register("escaped-html", escapedHtmlTransformer).register("date-range", dateRangeTransformer).register("time-range", timeRangeTransformer).registerFallback(plainTransformer);
|
|
2179
2256
|
}
|
|
2180
2257
|
|
|
2181
2258
|
// src/transformers/styles/background-color-overlay-transformer.ts
|
|
@@ -4891,7 +4968,7 @@ Do NOT send "group" (it is resolved automatically). Use { "settings": {} } only
|
|
|
4891
4968
|
);
|
|
4892
4969
|
configureElementToolPrompt.parameter(
|
|
4893
4970
|
"style",
|
|
4894
|
-
'A flat map of raw CSS declarations (property \u2192 value), e.g. { "line-height": "1.25rem", "color": "var(--primary-text, #000)" }. Set a value to null to reset that property to its default. OPTIONAL.'
|
|
4971
|
+
'A flat map of raw CSS declarations (property \u2192 value), e.g. { "line-height": "1.25rem", "color": "var(--primary-text, #000)" }. font-family must be a single Google Font name or a var(--label) \u2014 no fallback stacks. Set a value to null to reset that property to its default. OPTIONAL.'
|
|
4895
4972
|
);
|
|
4896
4973
|
configureElementToolPrompt.example(`
|
|
4897
4974
|
\`\`\`json
|
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-1061",
|
|
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-1061",
|
|
41
|
+
"@elementor/editor-controls": "4.3.0-1061",
|
|
42
|
+
"@elementor/editor-documents": "4.3.0-1061",
|
|
43
|
+
"@elementor/editor-elements": "4.3.0-1061",
|
|
44
|
+
"@elementor/editor-interactions": "4.3.0-1061",
|
|
45
|
+
"@elementor/editor-mcp": "4.3.0-1061",
|
|
46
|
+
"@elementor/editor-notifications": "4.3.0-1061",
|
|
47
|
+
"@elementor/editor-props": "4.3.0-1061",
|
|
48
|
+
"@elementor/editor-responsive": "4.3.0-1061",
|
|
49
|
+
"@elementor/editor-styles": "4.3.0-1061",
|
|
50
|
+
"@elementor/editor-styles-repository": "4.3.0-1061",
|
|
51
|
+
"@elementor/editor-ui": "4.3.0-1061",
|
|
52
|
+
"@elementor/editor-v1-adapters": "4.3.0-1061",
|
|
53
|
+
"@elementor/events": "4.3.0-1061",
|
|
54
|
+
"@elementor/http-client": "4.3.0-1061",
|
|
55
|
+
"@elementor/schema": "4.3.0-1061",
|
|
56
|
+
"@elementor/twing": "4.3.0-1061",
|
|
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-1061",
|
|
59
|
+
"@elementor/wp-media": "4.3.0-1061",
|
|
60
60
|
"@floating-ui/react": "^0.27.5",
|
|
61
61
|
"@wordpress/i18n": "^5.13.0",
|
|
62
62
|
"dompurify": "^3.2.6"
|
|
@@ -9,6 +9,7 @@ import { htmlV3Transformer } from './transformers/settings/html-v3-transformer';
|
|
|
9
9
|
import { linkTransformer } from './transformers/settings/link-transformer';
|
|
10
10
|
import { queryTransformer } from './transformers/settings/query-transformer';
|
|
11
11
|
import { timeRangeTransformer } from './transformers/settings/time-range-transformer';
|
|
12
|
+
import { iconTransformer } from './transformers/shared/icon-transformer';
|
|
12
13
|
import { imageSrcTransformer } from './transformers/shared/image-src-transformer';
|
|
13
14
|
import { imageTransformer } from './transformers/shared/image-transformer';
|
|
14
15
|
import { plainTransformer } from './transformers/shared/plain-transformer';
|
|
@@ -23,6 +24,7 @@ export function initSettingsTransformers() {
|
|
|
23
24
|
.register( 'image', imageTransformer )
|
|
24
25
|
.register( 'image-src', imageSrcTransformer )
|
|
25
26
|
.register( 'svg-src', svgSrcTransformer )
|
|
27
|
+
.register( 'icon', iconTransformer )
|
|
26
28
|
.register( 'video-src', videoSrcTransformer )
|
|
27
29
|
.register( 'attributes', attributesTransformer )
|
|
28
30
|
.register( 'date-time', dateTimeTransformer )
|
|
@@ -80,7 +80,7 @@ Do NOT send "group" (it is resolved automatically). Use { "settings": {} } only
|
|
|
80
80
|
|
|
81
81
|
configureElementToolPrompt.parameter(
|
|
82
82
|
'style',
|
|
83
|
-
'A flat map of raw CSS declarations (property → value), e.g. { "line-height": "1.25rem", "color": "var(--primary-text, #000)" }. Set a value to null to reset that property to its default. OPTIONAL.'
|
|
83
|
+
'A flat map of raw CSS declarations (property → value), e.g. { "line-height": "1.25rem", "color": "var(--primary-text, #000)" }. font-family must be a single Google Font name or a var(--label) — no fallback stacks. Set a value to null to reset that property to its default. OPTIONAL.'
|
|
84
84
|
);
|
|
85
85
|
|
|
86
86
|
configureElementToolPrompt.example( `
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { iconTransformer, resetFontAwesomeIconsCache } from '../icon-transformer';
|
|
2
|
+
|
|
3
|
+
const STAR_PATH = 'M0 0h100v100H0z';
|
|
4
|
+
const STAR_WIDTH = 512;
|
|
5
|
+
const STAR_HEIGHT = 512;
|
|
6
|
+
|
|
7
|
+
const mockFetch = ( body: unknown, ok = true ) => {
|
|
8
|
+
global.fetch = jest.fn().mockResolvedValue( {
|
|
9
|
+
ok,
|
|
10
|
+
json: () => Promise.resolve( body ),
|
|
11
|
+
} );
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
describe( 'iconTransformer', () => {
|
|
15
|
+
const originalElementorCommon = window.elementorCommon;
|
|
16
|
+
|
|
17
|
+
beforeEach( () => {
|
|
18
|
+
jest.clearAllMocks();
|
|
19
|
+
resetFontAwesomeIconsCache();
|
|
20
|
+
window.elementorCommon = {
|
|
21
|
+
config: {
|
|
22
|
+
urls: {
|
|
23
|
+
assets: 'https://example.com/assets/',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
} );
|
|
28
|
+
|
|
29
|
+
afterEach( () => {
|
|
30
|
+
window.elementorCommon = originalElementorCommon;
|
|
31
|
+
jest.restoreAllMocks();
|
|
32
|
+
} );
|
|
33
|
+
|
|
34
|
+
it( 'returns processed inline svg for a font awesome icon', async () => {
|
|
35
|
+
// Arrange.
|
|
36
|
+
mockFetch( {
|
|
37
|
+
icons: {
|
|
38
|
+
star: [ STAR_WIDTH, STAR_HEIGHT, [], 'f005', STAR_PATH ],
|
|
39
|
+
},
|
|
40
|
+
} );
|
|
41
|
+
|
|
42
|
+
// Act.
|
|
43
|
+
const result = await iconTransformer( { value: 'fas fa-star', library: 'fa-solid' }, { key: 'svg' } );
|
|
44
|
+
|
|
45
|
+
// Assert.
|
|
46
|
+
expect( global.fetch ).toHaveBeenCalledWith( 'https://example.com/assets/lib/font-awesome/json/solid.json', {
|
|
47
|
+
signal: undefined,
|
|
48
|
+
} );
|
|
49
|
+
expect( result ).toEqual( {
|
|
50
|
+
html: expect.stringContaining( STAR_PATH ),
|
|
51
|
+
url: null,
|
|
52
|
+
} );
|
|
53
|
+
expect( ( result as { html: string } ).html ).toContain( 'fill="currentColor"' );
|
|
54
|
+
} );
|
|
55
|
+
|
|
56
|
+
it( 'returns null html when the icon cannot be resolved', async () => {
|
|
57
|
+
// Arrange.
|
|
58
|
+
mockFetch( { icons: {} } );
|
|
59
|
+
|
|
60
|
+
// Act.
|
|
61
|
+
const result = await iconTransformer( { value: 'fas fa-missing', library: 'fa-solid' }, { key: 'svg' } );
|
|
62
|
+
|
|
63
|
+
// Assert.
|
|
64
|
+
expect( result ).toEqual( { html: null, url: null } );
|
|
65
|
+
} );
|
|
66
|
+
|
|
67
|
+
it( 'returns null html when value or library is missing', async () => {
|
|
68
|
+
// Act.
|
|
69
|
+
const result = await iconTransformer( { value: 'fas fa-star' }, { key: 'svg' } );
|
|
70
|
+
|
|
71
|
+
// Assert.
|
|
72
|
+
expect( result ).toEqual( { html: null, url: null } );
|
|
73
|
+
} );
|
|
74
|
+
} );
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { createTransformer } from '../create-transformer';
|
|
2
|
+
import type { TransformerOptions } from '../types';
|
|
3
|
+
import { processSvgContent } from './process-svg-content';
|
|
4
|
+
|
|
5
|
+
type IconValue = {
|
|
6
|
+
value?: unknown;
|
|
7
|
+
library?: unknown;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
type FontAwesomeIconJson = [ number, number, unknown, unknown, string ];
|
|
11
|
+
|
|
12
|
+
const FONT_AWESOME_JSON = {
|
|
13
|
+
width: 0,
|
|
14
|
+
height: 1,
|
|
15
|
+
path: 4,
|
|
16
|
+
} as const;
|
|
17
|
+
|
|
18
|
+
const fontAwesomeJsonCache = new Map< string, Record< string, FontAwesomeIconJson > >();
|
|
19
|
+
|
|
20
|
+
export const iconTransformer = createTransformer( async ( value: IconValue, { signal }: TransformerOptions ) => {
|
|
21
|
+
const iconValue = typeof value.value === 'string' ? value.value : null;
|
|
22
|
+
const library = typeof value.library === 'string' ? value.library : null;
|
|
23
|
+
|
|
24
|
+
if ( ! iconValue || ! library ) {
|
|
25
|
+
return { html: null, url: null };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const iconName = getFontAwesomeIconName( iconValue );
|
|
29
|
+
const jsonFileName = getFontAwesomeJsonFileName( library );
|
|
30
|
+
|
|
31
|
+
if ( ! iconName || ! jsonFileName ) {
|
|
32
|
+
return { html: null, url: null };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const icons = await fetchFontAwesomeIcons( jsonFileName, signal );
|
|
36
|
+
const iconData = icons?.[ iconName ];
|
|
37
|
+
|
|
38
|
+
if ( ! iconData ) {
|
|
39
|
+
return { html: null, url: null };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const svgText = buildFontAwesomeSvg( iconData );
|
|
43
|
+
const html = processSvgContent( svgText );
|
|
44
|
+
|
|
45
|
+
return { html, url: null };
|
|
46
|
+
} );
|
|
47
|
+
|
|
48
|
+
function getFontAwesomeIconName( iconValue: string ): string | null {
|
|
49
|
+
const match = iconValue.match( /^fa\S*\s+fa-(.+)$/ );
|
|
50
|
+
|
|
51
|
+
return match?.[ 1 ] ?? null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getFontAwesomeJsonFileName( library: string ): string | null {
|
|
55
|
+
if ( ! library.startsWith( 'fa-' ) ) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return library.replace( /^fa-/, '' );
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function getAssetsBaseUrl(): string | null {
|
|
63
|
+
const assetsUrl = window.elementorCommon?.config?.urls?.assets;
|
|
64
|
+
|
|
65
|
+
return typeof assetsUrl === 'string' && assetsUrl !== '' ? assetsUrl : null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function fetchFontAwesomeIcons(
|
|
69
|
+
jsonFileName: string,
|
|
70
|
+
signal?: AbortSignal
|
|
71
|
+
): Promise< Record< string, FontAwesomeIconJson > | null > {
|
|
72
|
+
const cached = fontAwesomeJsonCache.get( jsonFileName );
|
|
73
|
+
|
|
74
|
+
if ( cached ) {
|
|
75
|
+
return cached;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const icons = await loadFontAwesomeIcons( jsonFileName, signal );
|
|
79
|
+
|
|
80
|
+
if ( icons ) {
|
|
81
|
+
fontAwesomeJsonCache.set( jsonFileName, icons );
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return icons;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function loadFontAwesomeIcons(
|
|
88
|
+
jsonFileName: string,
|
|
89
|
+
signal?: AbortSignal
|
|
90
|
+
): Promise< Record< string, FontAwesomeIconJson > | null > {
|
|
91
|
+
const assetsUrl = getAssetsBaseUrl();
|
|
92
|
+
|
|
93
|
+
if ( ! assetsUrl ) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const response = await fetch( `${ assetsUrl }lib/font-awesome/json/${ jsonFileName }.json`, { signal } );
|
|
99
|
+
|
|
100
|
+
if ( ! response.ok ) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const data = ( await response.json() ) as { icons?: Record< string, FontAwesomeIconJson > };
|
|
105
|
+
|
|
106
|
+
return data.icons ?? null;
|
|
107
|
+
} catch {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function buildFontAwesomeSvg( iconData: FontAwesomeIconJson ): string {
|
|
113
|
+
const width = iconData[ FONT_AWESOME_JSON.width ];
|
|
114
|
+
const height = iconData[ FONT_AWESOME_JSON.height ];
|
|
115
|
+
const path = iconData[ FONT_AWESOME_JSON.path ];
|
|
116
|
+
|
|
117
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${ width } ${ height }"><path d="${ path }"></path></svg>`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function resetFontAwesomeIconsCache() {
|
|
121
|
+
fontAwesomeJsonCache.clear();
|
|
122
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import DOMPurify from 'dompurify';
|
|
2
|
+
|
|
3
|
+
const SVG_INLINE_STYLES = 'width: 100%; height: 100%; overflow: unset;';
|
|
4
|
+
|
|
5
|
+
export function processSvgContent( svgText: string ): string | null {
|
|
6
|
+
const sanitized = DOMPurify.sanitize( svgText, {
|
|
7
|
+
USE_PROFILES: { svg: true, svgFilters: true },
|
|
8
|
+
} );
|
|
9
|
+
|
|
10
|
+
const parser = new DOMParser();
|
|
11
|
+
const doc = parser.parseFromString( sanitized, 'image/svg+xml' );
|
|
12
|
+
const svgElement = doc.querySelector( 'svg' );
|
|
13
|
+
|
|
14
|
+
if ( ! svgElement ) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
svgElement.setAttribute( 'fill', 'currentColor' );
|
|
19
|
+
|
|
20
|
+
const existingStyle = svgElement.getAttribute( 'style' ) ?? '';
|
|
21
|
+
const trimmed = existingStyle.trim();
|
|
22
|
+
const merged = trimmed ? `${ trimmed.replace( /;$/, '' ) }; ${ SVG_INLINE_STYLES }` : SVG_INLINE_STYLES;
|
|
23
|
+
svgElement.setAttribute( 'style', merged );
|
|
24
|
+
|
|
25
|
+
return svgElement.outerHTML;
|
|
26
|
+
}
|
|
@@ -1,39 +1,14 @@
|
|
|
1
|
-
import DOMPurify from 'dompurify';
|
|
2
1
|
import { getMediaAttachment } from '@elementor/wp-media';
|
|
3
2
|
|
|
4
3
|
import { createTransformer } from '../create-transformer';
|
|
5
4
|
import type { TransformerOptions } from '../types';
|
|
5
|
+
import { processSvgContent } from './process-svg-content';
|
|
6
6
|
|
|
7
7
|
type SvgSrc = {
|
|
8
8
|
id?: unknown;
|
|
9
9
|
url?: unknown;
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
const SVG_INLINE_STYLES = 'width: 100%; height: 100%; overflow: unset;';
|
|
13
|
-
|
|
14
|
-
function processSvgContent( svgText: string ): string | null {
|
|
15
|
-
const sanitized = DOMPurify.sanitize( svgText, {
|
|
16
|
-
USE_PROFILES: { svg: true, svgFilters: true },
|
|
17
|
-
} );
|
|
18
|
-
|
|
19
|
-
const parser = new DOMParser();
|
|
20
|
-
const doc = parser.parseFromString( sanitized, 'image/svg+xml' );
|
|
21
|
-
const svgElement = doc.querySelector( 'svg' );
|
|
22
|
-
|
|
23
|
-
if ( ! svgElement ) {
|
|
24
|
-
return null;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
svgElement.setAttribute( 'fill', 'currentColor' );
|
|
28
|
-
|
|
29
|
-
const existingStyle = svgElement.getAttribute( 'style' ) ?? '';
|
|
30
|
-
const trimmed = existingStyle.trim();
|
|
31
|
-
const merged = trimmed ? `${ trimmed.replace( /;$/, '' ) }; ${ SVG_INLINE_STYLES }` : SVG_INLINE_STYLES;
|
|
32
|
-
svgElement.setAttribute( 'style', merged );
|
|
33
|
-
|
|
34
|
-
return svgElement.outerHTML;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
12
|
async function fetchSvgContent( url: string, signal?: AbortSignal ): Promise< string | null > {
|
|
38
13
|
try {
|
|
39
14
|
const response = await fetch( url, { signal } );
|