@elementor/editor-canvas 4.4.0-1070 → 4.4.0-1073
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
CHANGED
|
@@ -2132,6 +2132,8 @@ function processSvgContent(svgText) {
|
|
|
2132
2132
|
var FONT_AWESOME_JSON = {
|
|
2133
2133
|
width: 0,
|
|
2134
2134
|
height: 1,
|
|
2135
|
+
aliases: 2,
|
|
2136
|
+
unicode: 3,
|
|
2135
2137
|
path: 4
|
|
2136
2138
|
};
|
|
2137
2139
|
var fontAwesomeJsonCache = /* @__PURE__ */ new Map();
|
|
@@ -2152,7 +2154,10 @@ var iconTransformer = createTransformer(async (value, { signal }) => {
|
|
|
2152
2154
|
return { html: null, url: null };
|
|
2153
2155
|
}
|
|
2154
2156
|
const svgText = buildFontAwesomeSvg(iconData);
|
|
2155
|
-
|
|
2157
|
+
if (!svgText) {
|
|
2158
|
+
return { html: null, url: null };
|
|
2159
|
+
}
|
|
2160
|
+
const html = processIconSvgContent(svgText);
|
|
2156
2161
|
return { html, url: null };
|
|
2157
2162
|
});
|
|
2158
2163
|
function getFontAwesomeIconName(iconValue) {
|
|
@@ -2160,14 +2165,22 @@ function getFontAwesomeIconName(iconValue) {
|
|
|
2160
2165
|
return match?.[1] ?? null;
|
|
2161
2166
|
}
|
|
2162
2167
|
function getFontAwesomeJsonFileName(library) {
|
|
2163
|
-
|
|
2168
|
+
const fileName = library.replace(/^fa-/, "");
|
|
2169
|
+
const config = getFontAwesome7EditorConfig();
|
|
2170
|
+
if (!config?.jsonFiles.includes(fileName)) {
|
|
2164
2171
|
return null;
|
|
2165
2172
|
}
|
|
2166
|
-
return
|
|
2173
|
+
return fileName;
|
|
2167
2174
|
}
|
|
2168
|
-
function
|
|
2169
|
-
const
|
|
2170
|
-
|
|
2175
|
+
function getFontAwesome7EditorConfig() {
|
|
2176
|
+
const config = window.elementorCommon?.config?.fontAwesome?.v7;
|
|
2177
|
+
if (!config || !Array.isArray(config.jsonFiles) || typeof config.jsonBaseUrl !== "string" || config.jsonBaseUrl === "") {
|
|
2178
|
+
return null;
|
|
2179
|
+
}
|
|
2180
|
+
return {
|
|
2181
|
+
jsonFiles: config.jsonFiles,
|
|
2182
|
+
jsonBaseUrl: config.jsonBaseUrl
|
|
2183
|
+
};
|
|
2171
2184
|
}
|
|
2172
2185
|
async function fetchFontAwesomeIcons(jsonFileName, signal) {
|
|
2173
2186
|
const cached = fontAwesomeJsonCache.get(jsonFileName);
|
|
@@ -2181,26 +2194,83 @@ async function fetchFontAwesomeIcons(jsonFileName, signal) {
|
|
|
2181
2194
|
return icons;
|
|
2182
2195
|
}
|
|
2183
2196
|
async function loadFontAwesomeIcons(jsonFileName, signal) {
|
|
2184
|
-
const
|
|
2185
|
-
if (!
|
|
2197
|
+
const config = getFontAwesome7EditorConfig();
|
|
2198
|
+
if (!config?.jsonFiles.includes(jsonFileName)) {
|
|
2186
2199
|
return null;
|
|
2187
2200
|
}
|
|
2188
2201
|
try {
|
|
2189
|
-
const response = await fetch(`${
|
|
2202
|
+
const response = await fetch(`${config.jsonBaseUrl}${jsonFileName}.json`, {
|
|
2203
|
+
signal
|
|
2204
|
+
});
|
|
2190
2205
|
if (!response.ok) {
|
|
2191
2206
|
return null;
|
|
2192
2207
|
}
|
|
2193
2208
|
const data = await response.json();
|
|
2194
|
-
|
|
2209
|
+
const icons = data.icons;
|
|
2210
|
+
if (!icons || typeof icons !== "object") {
|
|
2211
|
+
return null;
|
|
2212
|
+
}
|
|
2213
|
+
return indexFontAwesomeIcons(icons);
|
|
2195
2214
|
} catch {
|
|
2196
2215
|
return null;
|
|
2197
2216
|
}
|
|
2198
2217
|
}
|
|
2218
|
+
function indexFontAwesomeIcons(icons) {
|
|
2219
|
+
const index = {};
|
|
2220
|
+
for (const [name, iconData] of Object.entries(icons)) {
|
|
2221
|
+
if (!isValidIconTuple(iconData)) {
|
|
2222
|
+
continue;
|
|
2223
|
+
}
|
|
2224
|
+
index[name] = iconData;
|
|
2225
|
+
for (const alias of iconData[FONT_AWESOME_JSON.aliases]) {
|
|
2226
|
+
if (typeof alias === "string" && alias !== "" && !index[alias]) {
|
|
2227
|
+
index[alias] = iconData;
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
return index;
|
|
2232
|
+
}
|
|
2233
|
+
function isValidIconTuple(iconData) {
|
|
2234
|
+
return Array.isArray(iconData) && iconData.length >= 5 && typeof iconData[FONT_AWESOME_JSON.width] === "number" && typeof iconData[FONT_AWESOME_JSON.height] === "number" && Array.isArray(iconData[FONT_AWESOME_JSON.aliases]);
|
|
2235
|
+
}
|
|
2199
2236
|
function buildFontAwesomeSvg(iconData) {
|
|
2200
2237
|
const width = iconData[FONT_AWESOME_JSON.width];
|
|
2201
2238
|
const height = iconData[FONT_AWESOME_JSON.height];
|
|
2202
|
-
const
|
|
2203
|
-
|
|
2239
|
+
const paths = normalizePaths(iconData[FONT_AWESOME_JSON.path]);
|
|
2240
|
+
if (paths.length === 0) {
|
|
2241
|
+
return null;
|
|
2242
|
+
}
|
|
2243
|
+
const pathMarkup = paths.map((path) => `<path d="${escapeSvgPath(path)}"></path>`).join("");
|
|
2244
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}">${pathMarkup}</svg>`;
|
|
2245
|
+
}
|
|
2246
|
+
function normalizePaths(pathData) {
|
|
2247
|
+
if (typeof pathData === "string" && pathData !== "") {
|
|
2248
|
+
return [pathData];
|
|
2249
|
+
}
|
|
2250
|
+
if (!Array.isArray(pathData)) {
|
|
2251
|
+
return [];
|
|
2252
|
+
}
|
|
2253
|
+
return pathData.filter((path) => typeof path === "string" && path !== "");
|
|
2254
|
+
}
|
|
2255
|
+
function escapeSvgPath(path) {
|
|
2256
|
+
return path.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
2257
|
+
}
|
|
2258
|
+
function processIconSvgContent(svgText) {
|
|
2259
|
+
const html = processSvgContent(svgText);
|
|
2260
|
+
if (!html) {
|
|
2261
|
+
return null;
|
|
2262
|
+
}
|
|
2263
|
+
const parser = new DOMParser();
|
|
2264
|
+
const doc = parser.parseFromString(html, "image/svg+xml");
|
|
2265
|
+
const svgElement = doc.querySelector("svg");
|
|
2266
|
+
if (!svgElement) {
|
|
2267
|
+
return null;
|
|
2268
|
+
}
|
|
2269
|
+
svgElement.setAttribute("aria-hidden", "true");
|
|
2270
|
+
svgElement.style.setProperty("width", "100%");
|
|
2271
|
+
svgElement.style.setProperty("height", "100%");
|
|
2272
|
+
svgElement.style.setProperty("overflow", "visible");
|
|
2273
|
+
return svgElement.outerHTML;
|
|
2204
2274
|
}
|
|
2205
2275
|
|
|
2206
2276
|
// src/transformers/shared/image-src-transformer.ts
|
package/dist/index.mjs
CHANGED
|
@@ -2084,6 +2084,8 @@ function processSvgContent(svgText) {
|
|
|
2084
2084
|
var FONT_AWESOME_JSON = {
|
|
2085
2085
|
width: 0,
|
|
2086
2086
|
height: 1,
|
|
2087
|
+
aliases: 2,
|
|
2088
|
+
unicode: 3,
|
|
2087
2089
|
path: 4
|
|
2088
2090
|
};
|
|
2089
2091
|
var fontAwesomeJsonCache = /* @__PURE__ */ new Map();
|
|
@@ -2104,7 +2106,10 @@ var iconTransformer = createTransformer(async (value, { signal }) => {
|
|
|
2104
2106
|
return { html: null, url: null };
|
|
2105
2107
|
}
|
|
2106
2108
|
const svgText = buildFontAwesomeSvg(iconData);
|
|
2107
|
-
|
|
2109
|
+
if (!svgText) {
|
|
2110
|
+
return { html: null, url: null };
|
|
2111
|
+
}
|
|
2112
|
+
const html = processIconSvgContent(svgText);
|
|
2108
2113
|
return { html, url: null };
|
|
2109
2114
|
});
|
|
2110
2115
|
function getFontAwesomeIconName(iconValue) {
|
|
@@ -2112,14 +2117,22 @@ function getFontAwesomeIconName(iconValue) {
|
|
|
2112
2117
|
return match?.[1] ?? null;
|
|
2113
2118
|
}
|
|
2114
2119
|
function getFontAwesomeJsonFileName(library) {
|
|
2115
|
-
|
|
2120
|
+
const fileName = library.replace(/^fa-/, "");
|
|
2121
|
+
const config = getFontAwesome7EditorConfig();
|
|
2122
|
+
if (!config?.jsonFiles.includes(fileName)) {
|
|
2116
2123
|
return null;
|
|
2117
2124
|
}
|
|
2118
|
-
return
|
|
2125
|
+
return fileName;
|
|
2119
2126
|
}
|
|
2120
|
-
function
|
|
2121
|
-
const
|
|
2122
|
-
|
|
2127
|
+
function getFontAwesome7EditorConfig() {
|
|
2128
|
+
const config = window.elementorCommon?.config?.fontAwesome?.v7;
|
|
2129
|
+
if (!config || !Array.isArray(config.jsonFiles) || typeof config.jsonBaseUrl !== "string" || config.jsonBaseUrl === "") {
|
|
2130
|
+
return null;
|
|
2131
|
+
}
|
|
2132
|
+
return {
|
|
2133
|
+
jsonFiles: config.jsonFiles,
|
|
2134
|
+
jsonBaseUrl: config.jsonBaseUrl
|
|
2135
|
+
};
|
|
2123
2136
|
}
|
|
2124
2137
|
async function fetchFontAwesomeIcons(jsonFileName, signal) {
|
|
2125
2138
|
const cached = fontAwesomeJsonCache.get(jsonFileName);
|
|
@@ -2133,26 +2146,83 @@ async function fetchFontAwesomeIcons(jsonFileName, signal) {
|
|
|
2133
2146
|
return icons;
|
|
2134
2147
|
}
|
|
2135
2148
|
async function loadFontAwesomeIcons(jsonFileName, signal) {
|
|
2136
|
-
const
|
|
2137
|
-
if (!
|
|
2149
|
+
const config = getFontAwesome7EditorConfig();
|
|
2150
|
+
if (!config?.jsonFiles.includes(jsonFileName)) {
|
|
2138
2151
|
return null;
|
|
2139
2152
|
}
|
|
2140
2153
|
try {
|
|
2141
|
-
const response = await fetch(`${
|
|
2154
|
+
const response = await fetch(`${config.jsonBaseUrl}${jsonFileName}.json`, {
|
|
2155
|
+
signal
|
|
2156
|
+
});
|
|
2142
2157
|
if (!response.ok) {
|
|
2143
2158
|
return null;
|
|
2144
2159
|
}
|
|
2145
2160
|
const data = await response.json();
|
|
2146
|
-
|
|
2161
|
+
const icons = data.icons;
|
|
2162
|
+
if (!icons || typeof icons !== "object") {
|
|
2163
|
+
return null;
|
|
2164
|
+
}
|
|
2165
|
+
return indexFontAwesomeIcons(icons);
|
|
2147
2166
|
} catch {
|
|
2148
2167
|
return null;
|
|
2149
2168
|
}
|
|
2150
2169
|
}
|
|
2170
|
+
function indexFontAwesomeIcons(icons) {
|
|
2171
|
+
const index = {};
|
|
2172
|
+
for (const [name, iconData] of Object.entries(icons)) {
|
|
2173
|
+
if (!isValidIconTuple(iconData)) {
|
|
2174
|
+
continue;
|
|
2175
|
+
}
|
|
2176
|
+
index[name] = iconData;
|
|
2177
|
+
for (const alias of iconData[FONT_AWESOME_JSON.aliases]) {
|
|
2178
|
+
if (typeof alias === "string" && alias !== "" && !index[alias]) {
|
|
2179
|
+
index[alias] = iconData;
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
return index;
|
|
2184
|
+
}
|
|
2185
|
+
function isValidIconTuple(iconData) {
|
|
2186
|
+
return Array.isArray(iconData) && iconData.length >= 5 && typeof iconData[FONT_AWESOME_JSON.width] === "number" && typeof iconData[FONT_AWESOME_JSON.height] === "number" && Array.isArray(iconData[FONT_AWESOME_JSON.aliases]);
|
|
2187
|
+
}
|
|
2151
2188
|
function buildFontAwesomeSvg(iconData) {
|
|
2152
2189
|
const width = iconData[FONT_AWESOME_JSON.width];
|
|
2153
2190
|
const height = iconData[FONT_AWESOME_JSON.height];
|
|
2154
|
-
const
|
|
2155
|
-
|
|
2191
|
+
const paths = normalizePaths(iconData[FONT_AWESOME_JSON.path]);
|
|
2192
|
+
if (paths.length === 0) {
|
|
2193
|
+
return null;
|
|
2194
|
+
}
|
|
2195
|
+
const pathMarkup = paths.map((path) => `<path d="${escapeSvgPath(path)}"></path>`).join("");
|
|
2196
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}">${pathMarkup}</svg>`;
|
|
2197
|
+
}
|
|
2198
|
+
function normalizePaths(pathData) {
|
|
2199
|
+
if (typeof pathData === "string" && pathData !== "") {
|
|
2200
|
+
return [pathData];
|
|
2201
|
+
}
|
|
2202
|
+
if (!Array.isArray(pathData)) {
|
|
2203
|
+
return [];
|
|
2204
|
+
}
|
|
2205
|
+
return pathData.filter((path) => typeof path === "string" && path !== "");
|
|
2206
|
+
}
|
|
2207
|
+
function escapeSvgPath(path) {
|
|
2208
|
+
return path.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
2209
|
+
}
|
|
2210
|
+
function processIconSvgContent(svgText) {
|
|
2211
|
+
const html = processSvgContent(svgText);
|
|
2212
|
+
if (!html) {
|
|
2213
|
+
return null;
|
|
2214
|
+
}
|
|
2215
|
+
const parser = new DOMParser();
|
|
2216
|
+
const doc = parser.parseFromString(html, "image/svg+xml");
|
|
2217
|
+
const svgElement = doc.querySelector("svg");
|
|
2218
|
+
if (!svgElement) {
|
|
2219
|
+
return null;
|
|
2220
|
+
}
|
|
2221
|
+
svgElement.setAttribute("aria-hidden", "true");
|
|
2222
|
+
svgElement.style.setProperty("width", "100%");
|
|
2223
|
+
svgElement.style.setProperty("height", "100%");
|
|
2224
|
+
svgElement.style.setProperty("overflow", "visible");
|
|
2225
|
+
return svgElement.outerHTML;
|
|
2156
2226
|
}
|
|
2157
2227
|
|
|
2158
2228
|
// src/transformers/shared/image-src-transformer.ts
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elementor/editor-canvas",
|
|
3
3
|
"description": "Elementor Editor Canvas",
|
|
4
|
-
"version": "4.4.0-
|
|
4
|
+
"version": "4.4.0-1073",
|
|
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.4.0-
|
|
41
|
-
"@elementor/editor-controls": "4.4.0-
|
|
42
|
-
"@elementor/editor-documents": "4.4.0-
|
|
43
|
-
"@elementor/editor-elements": "4.4.0-
|
|
44
|
-
"@elementor/editor-interactions": "4.4.0-
|
|
45
|
-
"@elementor/editor-mcp": "4.4.0-
|
|
46
|
-
"@elementor/editor-notifications": "4.4.0-
|
|
47
|
-
"@elementor/editor-props": "4.4.0-
|
|
48
|
-
"@elementor/editor-responsive": "4.4.0-
|
|
49
|
-
"@elementor/editor-styles": "4.4.0-
|
|
50
|
-
"@elementor/editor-styles-repository": "4.4.0-
|
|
51
|
-
"@elementor/editor-ui": "4.4.0-
|
|
52
|
-
"@elementor/editor-v1-adapters": "4.4.0-
|
|
53
|
-
"@elementor/events": "4.4.0-
|
|
54
|
-
"@elementor/http-client": "4.4.0-
|
|
55
|
-
"@elementor/schema": "4.4.0-
|
|
56
|
-
"@elementor/twing": "4.4.0-
|
|
40
|
+
"@elementor/editor": "4.4.0-1073",
|
|
41
|
+
"@elementor/editor-controls": "4.4.0-1073",
|
|
42
|
+
"@elementor/editor-documents": "4.4.0-1073",
|
|
43
|
+
"@elementor/editor-elements": "4.4.0-1073",
|
|
44
|
+
"@elementor/editor-interactions": "4.4.0-1073",
|
|
45
|
+
"@elementor/editor-mcp": "4.4.0-1073",
|
|
46
|
+
"@elementor/editor-notifications": "4.4.0-1073",
|
|
47
|
+
"@elementor/editor-props": "4.4.0-1073",
|
|
48
|
+
"@elementor/editor-responsive": "4.4.0-1073",
|
|
49
|
+
"@elementor/editor-styles": "4.4.0-1073",
|
|
50
|
+
"@elementor/editor-styles-repository": "4.4.0-1073",
|
|
51
|
+
"@elementor/editor-ui": "4.4.0-1073",
|
|
52
|
+
"@elementor/editor-v1-adapters": "4.4.0-1073",
|
|
53
|
+
"@elementor/events": "4.4.0-1073",
|
|
54
|
+
"@elementor/http-client": "4.4.0-1073",
|
|
55
|
+
"@elementor/schema": "4.4.0-1073",
|
|
56
|
+
"@elementor/twing": "4.4.0-1073",
|
|
57
57
|
"@elementor/ui": "1.37.5",
|
|
58
|
-
"@elementor/utils": "4.4.0-
|
|
59
|
-
"@elementor/wp-media": "4.4.0-
|
|
58
|
+
"@elementor/utils": "4.4.0-1073",
|
|
59
|
+
"@elementor/wp-media": "4.4.0-1073",
|
|
60
60
|
"@floating-ui/react": "^0.27.5",
|
|
61
61
|
"@wordpress/i18n": "^5.13.0",
|
|
62
62
|
"dompurify": "^3.2.6"
|
|
@@ -36,6 +36,17 @@ export function linkPropType() {
|
|
|
36
36
|
} );
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
export function iconPropType() {
|
|
40
|
+
return createMockPropType( {
|
|
41
|
+
kind: 'object',
|
|
42
|
+
key: 'icon',
|
|
43
|
+
shape: {
|
|
44
|
+
value: stringPropType(),
|
|
45
|
+
library: stringPropType(),
|
|
46
|
+
},
|
|
47
|
+
} );
|
|
48
|
+
}
|
|
49
|
+
|
|
39
50
|
export function imagePropType() {
|
|
40
51
|
return createMockPropType( {
|
|
41
52
|
kind: 'object',
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { iconPropTypeUtil, stringPropTypeUtil } from '@elementor/editor-props';
|
|
2
|
+
|
|
3
|
+
import { iconPropType } from '../../../__tests__/prop-types';
|
|
4
|
+
import { initSettingsTransformers } from '../../../init-settings-transformers';
|
|
5
|
+
import { createPropsResolver } from '../../../renderers/create-props-resolver';
|
|
6
|
+
import { settingsTransformersRegistry } from '../../../settings-transformers-registry';
|
|
7
|
+
import { resetFontAwesomeIconsCache } from '../icon-transformer';
|
|
2
8
|
|
|
3
9
|
const STAR_PATH = 'M0 0h100v100H0z';
|
|
4
|
-
const STAR_WIDTH =
|
|
10
|
+
const STAR_WIDTH = 576;
|
|
5
11
|
const STAR_HEIGHT = 512;
|
|
6
12
|
|
|
7
13
|
const mockFetch = ( body: unknown, ok = true ) => {
|
|
@@ -11,17 +17,46 @@ const mockFetch = ( body: unknown, ok = true ) => {
|
|
|
11
17
|
} );
|
|
12
18
|
};
|
|
13
19
|
|
|
20
|
+
function createSavedIcon( iconClass: string, library: string ) {
|
|
21
|
+
return iconPropTypeUtil.create( {
|
|
22
|
+
value: stringPropTypeUtil.create( iconClass ),
|
|
23
|
+
library: stringPropTypeUtil.create( library ),
|
|
24
|
+
} );
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function resolveSavedIcon( iconClass: string, library: string ) {
|
|
28
|
+
const resolve = createPropsResolver( {
|
|
29
|
+
transformers: settingsTransformersRegistry,
|
|
30
|
+
schema: { svg: iconPropType() },
|
|
31
|
+
} );
|
|
32
|
+
|
|
33
|
+
const result = await resolve( {
|
|
34
|
+
props: {
|
|
35
|
+
svg: createSavedIcon( iconClass, library ),
|
|
36
|
+
},
|
|
37
|
+
} );
|
|
38
|
+
|
|
39
|
+
return result.svg;
|
|
40
|
+
}
|
|
41
|
+
|
|
14
42
|
describe( 'iconTransformer', () => {
|
|
15
43
|
const originalElementorCommon = window.elementorCommon;
|
|
16
44
|
|
|
17
45
|
beforeEach( () => {
|
|
18
46
|
jest.clearAllMocks();
|
|
19
47
|
resetFontAwesomeIconsCache();
|
|
48
|
+
initSettingsTransformers();
|
|
20
49
|
window.elementorCommon = {
|
|
21
50
|
config: {
|
|
22
51
|
urls: {
|
|
23
52
|
assets: 'https://example.com/assets/',
|
|
24
53
|
},
|
|
54
|
+
fontAwesome: {
|
|
55
|
+
v7: {
|
|
56
|
+
jsonFiles: [ 'solid', 'regular', 'brands' ],
|
|
57
|
+
jsonBaseUrl: 'https://example.com/assets/lib/font-awesome-7/json/',
|
|
58
|
+
},
|
|
59
|
+
},
|
|
25
60
|
},
|
|
26
61
|
};
|
|
27
62
|
} );
|
|
@@ -40,10 +75,10 @@ describe( 'iconTransformer', () => {
|
|
|
40
75
|
} );
|
|
41
76
|
|
|
42
77
|
// Act.
|
|
43
|
-
const result = await
|
|
78
|
+
const result = await resolveSavedIcon( 'fas fa-star', 'fa-solid' );
|
|
44
79
|
|
|
45
80
|
// Assert.
|
|
46
|
-
expect( global.fetch ).toHaveBeenCalledWith( 'https://example.com/assets/lib/font-awesome/json/solid.json', {
|
|
81
|
+
expect( global.fetch ).toHaveBeenCalledWith( 'https://example.com/assets/lib/font-awesome-7/json/solid.json', {
|
|
47
82
|
signal: undefined,
|
|
48
83
|
} );
|
|
49
84
|
expect( result ).toEqual( {
|
|
@@ -51,6 +86,45 @@ describe( 'iconTransformer', () => {
|
|
|
51
86
|
url: null,
|
|
52
87
|
} );
|
|
53
88
|
expect( ( result as { html: string } ).html ).toContain( 'fill="currentColor"' );
|
|
89
|
+
expect( ( result as { html: string } ).html ).toContain( `viewBox="0 0 ${ STAR_WIDTH } ${ STAR_HEIGHT }"` );
|
|
90
|
+
expect( ( result as { html: string } ).html ).toContain( 'aria-hidden="true"' );
|
|
91
|
+
expect( ( result as { html: string } ).html ).toContain( 'overflow: visible' );
|
|
92
|
+
} );
|
|
93
|
+
|
|
94
|
+
it( 'resolves icons by alias name', async () => {
|
|
95
|
+
// Arrange.
|
|
96
|
+
mockFetch( {
|
|
97
|
+
icons: {
|
|
98
|
+
headphones: [ 448, 512, [ 'headphones-simple' ], 'f025', 'M0 0' ],
|
|
99
|
+
},
|
|
100
|
+
} );
|
|
101
|
+
|
|
102
|
+
// Act.
|
|
103
|
+
const result = await resolveSavedIcon( 'fas fa-headphones-simple', 'fa-solid' );
|
|
104
|
+
|
|
105
|
+
// Assert.
|
|
106
|
+
expect( result ).toEqual( {
|
|
107
|
+
html: expect.stringContaining( 'M0 0' ),
|
|
108
|
+
url: null,
|
|
109
|
+
} );
|
|
110
|
+
} );
|
|
111
|
+
|
|
112
|
+
it( 'renders multiple paths when the icon definition contains an array', async () => {
|
|
113
|
+
// Arrange.
|
|
114
|
+
mockFetch( {
|
|
115
|
+
icons: {
|
|
116
|
+
slash: [ 640, 640, [], 'f715', [ 'M0 0', 'M10 10' ] ],
|
|
117
|
+
},
|
|
118
|
+
} );
|
|
119
|
+
|
|
120
|
+
// Act.
|
|
121
|
+
const result = await resolveSavedIcon( 'fas fa-slash', 'fa-solid' );
|
|
122
|
+
|
|
123
|
+
// Assert.
|
|
124
|
+
const html = ( result as { html: string } ).html;
|
|
125
|
+
expect( html.match( /<path/g ) ).toHaveLength( 2 );
|
|
126
|
+
expect( html ).toContain( 'M0 0' );
|
|
127
|
+
expect( html ).toContain( 'M10 10' );
|
|
54
128
|
} );
|
|
55
129
|
|
|
56
130
|
it( 'returns null html when the icon cannot be resolved', async () => {
|
|
@@ -58,17 +132,68 @@ describe( 'iconTransformer', () => {
|
|
|
58
132
|
mockFetch( { icons: {} } );
|
|
59
133
|
|
|
60
134
|
// Act.
|
|
61
|
-
const result = await
|
|
135
|
+
const result = await resolveSavedIcon( 'fas fa-missing', 'fa-solid' );
|
|
62
136
|
|
|
63
137
|
// Assert.
|
|
64
138
|
expect( result ).toEqual( { html: null, url: null } );
|
|
65
139
|
} );
|
|
66
140
|
|
|
67
|
-
it( '
|
|
141
|
+
it( 'fetches json from the localized font awesome 7 base url', async () => {
|
|
142
|
+
// Arrange.
|
|
143
|
+
const jsonBaseUrl = 'https://cdn.example.com/fa7/json/';
|
|
144
|
+
window.elementorCommon = {
|
|
145
|
+
config: {
|
|
146
|
+
fontAwesome: {
|
|
147
|
+
v7: {
|
|
148
|
+
jsonFiles: [ 'solid', 'regular', 'brands' ],
|
|
149
|
+
jsonBaseUrl,
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
mockFetch( {
|
|
155
|
+
icons: {
|
|
156
|
+
star: [ STAR_WIDTH, STAR_HEIGHT, [], 'f005', STAR_PATH ],
|
|
157
|
+
},
|
|
158
|
+
} );
|
|
159
|
+
|
|
68
160
|
// Act.
|
|
69
|
-
|
|
161
|
+
await resolveSavedIcon( 'fas fa-star', 'fa-solid' );
|
|
70
162
|
|
|
71
163
|
// Assert.
|
|
164
|
+
expect( global.fetch ).toHaveBeenCalledWith( `${ jsonBaseUrl }solid.json`, {
|
|
165
|
+
signal: undefined,
|
|
166
|
+
} );
|
|
167
|
+
} );
|
|
168
|
+
|
|
169
|
+
it( 'does not fetch json when library is not an allowed fa7 file', async () => {
|
|
170
|
+
// Arrange.
|
|
171
|
+
mockFetch( { icons: {} } );
|
|
172
|
+
|
|
173
|
+
// Act.
|
|
174
|
+
const result = await resolveSavedIcon( 'fas fa-star', 'fa-../../wp-config' );
|
|
175
|
+
|
|
176
|
+
// Assert.
|
|
177
|
+
expect( global.fetch ).not.toHaveBeenCalled();
|
|
72
178
|
expect( result ).toEqual( { html: null, url: null } );
|
|
73
179
|
} );
|
|
180
|
+
|
|
181
|
+
it( 'returns null html when value or library is missing', async () => {
|
|
182
|
+
// Act.
|
|
183
|
+
const resolve = createPropsResolver( {
|
|
184
|
+
transformers: settingsTransformersRegistry,
|
|
185
|
+
schema: { svg: iconPropType() },
|
|
186
|
+
} );
|
|
187
|
+
const result = await resolve( {
|
|
188
|
+
props: {
|
|
189
|
+
svg: iconPropTypeUtil.create( {
|
|
190
|
+
value: stringPropTypeUtil.create( 'fas fa-star' ),
|
|
191
|
+
library: stringPropTypeUtil.create( '' ),
|
|
192
|
+
} ),
|
|
193
|
+
},
|
|
194
|
+
} );
|
|
195
|
+
|
|
196
|
+
// Assert.
|
|
197
|
+
expect( result.svg ).toEqual( { html: null, url: null } );
|
|
198
|
+
} );
|
|
74
199
|
} );
|
|
@@ -7,11 +7,18 @@ type IconValue = {
|
|
|
7
7
|
library?: unknown;
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
-
type FontAwesomeIconJson = [ number, number, unknown, unknown, string ];
|
|
10
|
+
type FontAwesomeIconJson = [ number, number, unknown[], unknown, string | string[] ];
|
|
11
|
+
|
|
12
|
+
type FontAwesome7EditorConfig = {
|
|
13
|
+
jsonFiles: string[];
|
|
14
|
+
jsonBaseUrl: string;
|
|
15
|
+
};
|
|
11
16
|
|
|
12
17
|
const FONT_AWESOME_JSON = {
|
|
13
18
|
width: 0,
|
|
14
19
|
height: 1,
|
|
20
|
+
aliases: 2,
|
|
21
|
+
unicode: 3,
|
|
15
22
|
path: 4,
|
|
16
23
|
} as const;
|
|
17
24
|
|
|
@@ -40,7 +47,12 @@ export const iconTransformer = createTransformer( async ( value: IconValue, { si
|
|
|
40
47
|
}
|
|
41
48
|
|
|
42
49
|
const svgText = buildFontAwesomeSvg( iconData );
|
|
43
|
-
|
|
50
|
+
|
|
51
|
+
if ( ! svgText ) {
|
|
52
|
+
return { html: null, url: null };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const html = processIconSvgContent( svgText );
|
|
44
56
|
|
|
45
57
|
return { html, url: null };
|
|
46
58
|
} );
|
|
@@ -52,17 +64,32 @@ function getFontAwesomeIconName( iconValue: string ): string | null {
|
|
|
52
64
|
}
|
|
53
65
|
|
|
54
66
|
function getFontAwesomeJsonFileName( library: string ): string | null {
|
|
55
|
-
|
|
67
|
+
const fileName = library.replace( /^fa-/, '' );
|
|
68
|
+
const config = getFontAwesome7EditorConfig();
|
|
69
|
+
|
|
70
|
+
if ( ! config?.jsonFiles.includes( fileName ) ) {
|
|
56
71
|
return null;
|
|
57
72
|
}
|
|
58
73
|
|
|
59
|
-
return
|
|
74
|
+
return fileName;
|
|
60
75
|
}
|
|
61
76
|
|
|
62
|
-
function
|
|
63
|
-
const
|
|
77
|
+
function getFontAwesome7EditorConfig(): FontAwesome7EditorConfig | null {
|
|
78
|
+
const config = window.elementorCommon?.config?.fontAwesome?.v7;
|
|
79
|
+
|
|
80
|
+
if (
|
|
81
|
+
! config ||
|
|
82
|
+
! Array.isArray( config.jsonFiles ) ||
|
|
83
|
+
typeof config.jsonBaseUrl !== 'string' ||
|
|
84
|
+
config.jsonBaseUrl === ''
|
|
85
|
+
) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
64
88
|
|
|
65
|
-
return
|
|
89
|
+
return {
|
|
90
|
+
jsonFiles: config.jsonFiles,
|
|
91
|
+
jsonBaseUrl: config.jsonBaseUrl,
|
|
92
|
+
};
|
|
66
93
|
}
|
|
67
94
|
|
|
68
95
|
async function fetchFontAwesomeIcons(
|
|
@@ -88,33 +115,115 @@ async function loadFontAwesomeIcons(
|
|
|
88
115
|
jsonFileName: string,
|
|
89
116
|
signal?: AbortSignal
|
|
90
117
|
): Promise< Record< string, FontAwesomeIconJson > | null > {
|
|
91
|
-
const
|
|
118
|
+
const config = getFontAwesome7EditorConfig();
|
|
92
119
|
|
|
93
|
-
if ( !
|
|
120
|
+
if ( ! config?.jsonFiles.includes( jsonFileName ) ) {
|
|
94
121
|
return null;
|
|
95
122
|
}
|
|
96
123
|
|
|
97
124
|
try {
|
|
98
|
-
const response = await fetch( `${
|
|
125
|
+
const response = await fetch( `${ config.jsonBaseUrl }${ jsonFileName }.json`, {
|
|
126
|
+
signal,
|
|
127
|
+
} );
|
|
99
128
|
|
|
100
129
|
if ( ! response.ok ) {
|
|
101
130
|
return null;
|
|
102
131
|
}
|
|
103
132
|
|
|
104
133
|
const data = ( await response.json() ) as { icons?: Record< string, FontAwesomeIconJson > };
|
|
134
|
+
const icons = data.icons;
|
|
105
135
|
|
|
106
|
-
|
|
136
|
+
if ( ! icons || typeof icons !== 'object' ) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return indexFontAwesomeIcons( icons );
|
|
107
141
|
} catch {
|
|
108
142
|
return null;
|
|
109
143
|
}
|
|
110
144
|
}
|
|
111
145
|
|
|
112
|
-
function
|
|
146
|
+
function indexFontAwesomeIcons( icons: Record< string, FontAwesomeIconJson > ): Record< string, FontAwesomeIconJson > {
|
|
147
|
+
const index: Record< string, FontAwesomeIconJson > = {};
|
|
148
|
+
|
|
149
|
+
for ( const [ name, iconData ] of Object.entries( icons ) ) {
|
|
150
|
+
if ( ! isValidIconTuple( iconData ) ) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
index[ name ] = iconData;
|
|
155
|
+
|
|
156
|
+
for ( const alias of iconData[ FONT_AWESOME_JSON.aliases ] ) {
|
|
157
|
+
if ( typeof alias === 'string' && alias !== '' && ! index[ alias ] ) {
|
|
158
|
+
index[ alias ] = iconData;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return index;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function isValidIconTuple( iconData: unknown ): iconData is FontAwesomeIconJson {
|
|
167
|
+
return (
|
|
168
|
+
Array.isArray( iconData ) &&
|
|
169
|
+
iconData.length >= 5 &&
|
|
170
|
+
typeof iconData[ FONT_AWESOME_JSON.width ] === 'number' &&
|
|
171
|
+
typeof iconData[ FONT_AWESOME_JSON.height ] === 'number' &&
|
|
172
|
+
Array.isArray( iconData[ FONT_AWESOME_JSON.aliases ] )
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function buildFontAwesomeSvg( iconData: FontAwesomeIconJson ): string | null {
|
|
113
177
|
const width = iconData[ FONT_AWESOME_JSON.width ];
|
|
114
178
|
const height = iconData[ FONT_AWESOME_JSON.height ];
|
|
115
|
-
const
|
|
179
|
+
const paths = normalizePaths( iconData[ FONT_AWESOME_JSON.path ] );
|
|
180
|
+
|
|
181
|
+
if ( paths.length === 0 ) {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const pathMarkup = paths.map( ( path ) => `<path d="${ escapeSvgPath( path ) }"></path>` ).join( '' );
|
|
186
|
+
|
|
187
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${ width } ${ height }">${ pathMarkup }</svg>`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function normalizePaths( pathData: string | string[] ): string[] {
|
|
191
|
+
if ( typeof pathData === 'string' && pathData !== '' ) {
|
|
192
|
+
return [ pathData ];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if ( ! Array.isArray( pathData ) ) {
|
|
196
|
+
return [];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return pathData.filter( ( path ): path is string => typeof path === 'string' && path !== '' );
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function escapeSvgPath( path: string ): string {
|
|
203
|
+
return path.replace( /&/g, '&' ).replace( /"/g, '"' ).replace( /</g, '<' ).replace( />/g, '>' );
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function processIconSvgContent( svgText: string ): string | null {
|
|
207
|
+
const html = processSvgContent( svgText );
|
|
208
|
+
|
|
209
|
+
if ( ! html ) {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const parser = new DOMParser();
|
|
214
|
+
const doc = parser.parseFromString( html, 'image/svg+xml' );
|
|
215
|
+
const svgElement = doc.querySelector( 'svg' );
|
|
216
|
+
|
|
217
|
+
if ( ! svgElement ) {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
svgElement.setAttribute( 'aria-hidden', 'true' );
|
|
222
|
+
svgElement.style.setProperty( 'width', '100%' );
|
|
223
|
+
svgElement.style.setProperty( 'height', '100%' );
|
|
224
|
+
svgElement.style.setProperty( 'overflow', 'visible' );
|
|
116
225
|
|
|
117
|
-
return
|
|
226
|
+
return svgElement.outerHTML;
|
|
118
227
|
}
|
|
119
228
|
|
|
120
229
|
export function resetFontAwesomeIconsCache() {
|