@elementor/editor-controls 4.4.0-1110 → 4.4.0-1112
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 +4 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +1069 -337
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +874 -142
- package/dist/index.mjs.map +1 -1
- package/package.json +16 -16
- package/src/controls/icon-library/custom-icon-libraries.ts +556 -0
- package/src/controls/icon-library/font-awesome-7-catalog.ts +16 -7
- package/src/controls/icon-library/font-awesome-7-data.ts +2 -0
- package/src/controls/icon-library/font-awesome-glyph.tsx +62 -17
- package/src/controls/icon-library/fontello-svg-font.ts +227 -0
- package/src/controls/icon-library/icomoon-selection.ts +104 -0
- package/src/controls/icon-library/icon-library-filter.tsx +126 -61
- package/src/controls/icon-library/icon-library-grid.tsx +5 -7
- package/src/controls/icon-library/icon-library-popover.tsx +7 -5
- package/src/controls/icon-library/sanitize-icon-svg-markup.ts +53 -0
- package/src/controls/svg-media-preview.tsx +7 -2
- package/src/index.ts +1 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elementor/editor-controls",
|
|
3
3
|
"description": "This package contains the controls model and utils for the Elementor editor",
|
|
4
|
-
"version": "4.4.0-
|
|
4
|
+
"version": "4.4.0-1112",
|
|
5
5
|
"private": false,
|
|
6
6
|
"author": "Elementor Team",
|
|
7
7
|
"homepage": "https://elementor.com/",
|
|
@@ -40,23 +40,23 @@
|
|
|
40
40
|
"dev": "tsup --config=../../tsup.dev.ts"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@elementor/editor-current-user": "4.4.0-
|
|
44
|
-
"@elementor/editor-elements": "4.4.0-
|
|
45
|
-
"@elementor/editor-props": "4.4.0-
|
|
46
|
-
"@elementor/editor-responsive": "4.4.0-
|
|
47
|
-
"@elementor/editor-ui": "4.4.0-
|
|
48
|
-
"@elementor/editor-v1-adapters": "4.4.0-
|
|
49
|
-
"@elementor/env": "4.4.0-
|
|
50
|
-
"@elementor/events": "4.4.0-
|
|
51
|
-
"@elementor/http-client": "4.4.0-
|
|
43
|
+
"@elementor/editor-current-user": "4.4.0-1112",
|
|
44
|
+
"@elementor/editor-elements": "4.4.0-1112",
|
|
45
|
+
"@elementor/editor-props": "4.4.0-1112",
|
|
46
|
+
"@elementor/editor-responsive": "4.4.0-1112",
|
|
47
|
+
"@elementor/editor-ui": "4.4.0-1112",
|
|
48
|
+
"@elementor/editor-v1-adapters": "4.4.0-1112",
|
|
49
|
+
"@elementor/env": "4.4.0-1112",
|
|
50
|
+
"@elementor/events": "4.4.0-1112",
|
|
51
|
+
"@elementor/http-client": "4.4.0-1112",
|
|
52
52
|
"@elementor/icons": "~1.75.1",
|
|
53
|
-
"@elementor/locations": "4.4.0-
|
|
54
|
-
"@elementor/query": "4.4.0-
|
|
55
|
-
"@elementor/schema": "4.4.0-
|
|
56
|
-
"@elementor/session": "4.4.0-
|
|
53
|
+
"@elementor/locations": "4.4.0-1112",
|
|
54
|
+
"@elementor/query": "4.4.0-1112",
|
|
55
|
+
"@elementor/schema": "4.4.0-1112",
|
|
56
|
+
"@elementor/session": "4.4.0-1112",
|
|
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-1112",
|
|
59
|
+
"@elementor/wp-media": "4.4.0-1112",
|
|
60
60
|
"@monaco-editor/react": "^4.7.0",
|
|
61
61
|
"@tanstack/react-virtual": "^3.13.3",
|
|
62
62
|
"@tiptap/extension-bold": "^3.11.1",
|
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
import { type HttpResponse, httpService } from '@elementor/http-client';
|
|
2
|
+
import { useQuery } from '@elementor/query';
|
|
3
|
+
|
|
4
|
+
import { enqueueIconFonts } from '../open-icon-library';
|
|
5
|
+
import { type FontAwesome7Icon } from './font-awesome-7-catalog';
|
|
6
|
+
import { parseFontelloSvgFont } from './fontello-svg-font';
|
|
7
|
+
import { parseIcomoonSelection } from './icomoon-selection';
|
|
8
|
+
|
|
9
|
+
const NATIVE_TAB_NAMES = new Set( [ 'all', 'recommended', 'GoPro' ] );
|
|
10
|
+
const DEFAULT_ICON_SIZE = 512;
|
|
11
|
+
const CUSTOM_ICON_LIBRARIES_QUERY_KEY = [ 'custom-icon-libraries' ];
|
|
12
|
+
const CUSTOM_ICON_SVG_URL = 'elementor/v1/atomic-widgets/custom-icon-svg';
|
|
13
|
+
|
|
14
|
+
type CustomIconLibraryConfig = {
|
|
15
|
+
name: string;
|
|
16
|
+
prefix: string;
|
|
17
|
+
displayPrefix?: string;
|
|
18
|
+
fetchJson?: string;
|
|
19
|
+
configUrl?: string;
|
|
20
|
+
fontUrl?: string;
|
|
21
|
+
selectionUrl?: string;
|
|
22
|
+
custom_icon_type?: string;
|
|
23
|
+
icons?: unknown;
|
|
24
|
+
native?: boolean;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const SUPPORTED_CUSTOM_ICON_TYPES = new Set( [ 'fontello', 'icomoon', 'fontastic' ] );
|
|
28
|
+
|
|
29
|
+
const svgMapCache = new Map< string, Record< string, string > >();
|
|
30
|
+
const inFlightSvgMaps = new Map< string, Promise< Record< string, string > > >();
|
|
31
|
+
|
|
32
|
+
export function resetCustomIconSvgCache() {
|
|
33
|
+
svgMapCache.clear();
|
|
34
|
+
inFlightSvgMaps.clear();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function isDeletedCustomIconLibrary( library: string, iconValue: string ): boolean {
|
|
38
|
+
const libraryName = String( library );
|
|
39
|
+
|
|
40
|
+
if ( ! libraryName || ! iconValue ) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if ( NATIVE_TAB_NAMES.has( libraryName ) || libraryName.startsWith( 'fa-' ) ) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return ! getCustomIconLibraryConfigs().some( ( item ) => item.name === libraryName );
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function useCustomIconLibraries( enabled: boolean ) {
|
|
52
|
+
return useQuery< FontAwesome7Icon[] >( {
|
|
53
|
+
queryKey: CUSTOM_ICON_LIBRARIES_QUERY_KEY,
|
|
54
|
+
queryFn: ( { signal } ) => loadCustomIconLibraries( signal ),
|
|
55
|
+
enabled: enabled && areCustomIconLibrariesEnabled(),
|
|
56
|
+
staleTime: Infinity,
|
|
57
|
+
} );
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function loadCustomIconLibraries( signal?: AbortSignal ): Promise< FontAwesome7Icon[] > {
|
|
61
|
+
const catalogs = await Promise.all(
|
|
62
|
+
getCustomIconLibraryConfigs().map( ( library ) => loadCustomLibrary( library, signal ) )
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
return catalogs.flat();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function resolveCustomIconSvg(
|
|
69
|
+
library: string,
|
|
70
|
+
iconValue: string,
|
|
71
|
+
signal?: AbortSignal
|
|
72
|
+
): Promise< string | null > {
|
|
73
|
+
const config = getCustomIconLibraryConfigs().find( ( item ) => item.name === String( library ) );
|
|
74
|
+
|
|
75
|
+
if ( ! config ) {
|
|
76
|
+
svgMapCache.delete( String( library ) );
|
|
77
|
+
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const map = await loadLibrarySvgMap( String( library ), config, signal );
|
|
82
|
+
const markup = map[ iconValue ];
|
|
83
|
+
|
|
84
|
+
return typeof markup === 'string' && markup !== '' ? markup : null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function areCustomIconLibrariesEnabled(): boolean {
|
|
88
|
+
return true === window.elementorCommon?.config?.fontAwesome?.v7?.customIconLibrariesEnabled;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function getCustomIconLibraryConfigs(): CustomIconLibraryConfig[] {
|
|
92
|
+
if ( ! areCustomIconLibrariesEnabled() ) {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const libraries = window.elementor?.config as { icons?: { libraries?: unknown } } | undefined;
|
|
97
|
+
const items = Array.isArray( libraries?.icons?.libraries ) ? libraries.icons.libraries : [];
|
|
98
|
+
|
|
99
|
+
return items.flatMap( ( value ) => {
|
|
100
|
+
const library = normalizeCustomIconLibraryConfig( value );
|
|
101
|
+
|
|
102
|
+
return library ? [ library ] : [];
|
|
103
|
+
} );
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function normalizeCustomIconLibraryConfig( value: unknown ): CustomIconLibraryConfig | null {
|
|
107
|
+
if ( ! value || typeof value !== 'object' ) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const library = value as CustomIconLibraryConfig & { name?: unknown; prefix?: unknown };
|
|
112
|
+
const name = coerceLibraryName( library.name );
|
|
113
|
+
|
|
114
|
+
if ( name === '' || NATIVE_TAB_NAMES.has( name ) || library.native === true || name.startsWith( 'fa-' ) ) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if ( typeof library.prefix !== 'string' ) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if ( ! library.fetchJson && library.icons === undefined ) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const resolved: CustomIconLibraryConfig = {
|
|
127
|
+
...library,
|
|
128
|
+
name,
|
|
129
|
+
prefix: library.prefix,
|
|
130
|
+
...getPackUrls( name, library ),
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
if ( ! resolveCustomIconType( resolved ) ) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return resolved;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function coerceLibraryName( value: unknown ): string {
|
|
141
|
+
if ( typeof value === 'string' ) {
|
|
142
|
+
return value;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if ( typeof value === 'number' && Number.isFinite( value ) ) {
|
|
146
|
+
return String( value );
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return '';
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function getPackUrls(
|
|
153
|
+
library: string,
|
|
154
|
+
config: CustomIconLibraryConfig
|
|
155
|
+
): Pick< CustomIconLibraryConfig, 'configUrl' | 'fontUrl' | 'selectionUrl' | 'custom_icon_type' > {
|
|
156
|
+
const derived = derivePackAssetUrls( config.fetchJson, resolveCustomIconType( config ) );
|
|
157
|
+
const packs = window.elementorCommon?.config?.fontAwesome?.v7?.customIconPacks;
|
|
158
|
+
|
|
159
|
+
if ( ! packs || typeof packs !== 'object' ) {
|
|
160
|
+
return derived;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const pack = packs[ library ];
|
|
164
|
+
|
|
165
|
+
if ( ! pack || typeof pack !== 'object' ) {
|
|
166
|
+
return derived;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const packType = typeof pack.type === 'string' ? pack.type : config.custom_icon_type;
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
...derived,
|
|
173
|
+
configUrl: typeof pack.configUrl === 'string' ? pack.configUrl : derived.configUrl,
|
|
174
|
+
fontUrl: typeof pack.fontUrl === 'string' ? pack.fontUrl : derived.fontUrl,
|
|
175
|
+
selectionUrl: typeof pack.selectionUrl === 'string' ? pack.selectionUrl : derived.selectionUrl,
|
|
176
|
+
custom_icon_type: packType ?? derived.custom_icon_type,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function resolveCustomIconType( library: CustomIconLibraryConfig ): string {
|
|
181
|
+
const type = typeof library.custom_icon_type === 'string' ? library.custom_icon_type.toLowerCase() : '';
|
|
182
|
+
|
|
183
|
+
return SUPPORTED_CUSTOM_ICON_TYPES.has( type ) ? type : '';
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function derivePackAssetUrls(
|
|
187
|
+
fetchJson: string | undefined,
|
|
188
|
+
type: string
|
|
189
|
+
): Pick< CustomIconLibraryConfig, 'configUrl' | 'fontUrl' | 'selectionUrl' | 'custom_icon_type' > {
|
|
190
|
+
if ( ! fetchJson ) {
|
|
191
|
+
return type ? { custom_icon_type: type } : {};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
const url = new URL( fetchJson, window.location.origin );
|
|
196
|
+
const base = url.href.replace( /\/[^/?#]+(?:\?.*)?$/, '' );
|
|
197
|
+
|
|
198
|
+
if ( 'icomoon' === type ) {
|
|
199
|
+
return {
|
|
200
|
+
custom_icon_type: type,
|
|
201
|
+
selectionUrl: `${ base }/selection.json`,
|
|
202
|
+
fontUrl: `${ base }/fonts/icomoon.svg`,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if ( 'fontastic' === type ) {
|
|
207
|
+
return {
|
|
208
|
+
custom_icon_type: type,
|
|
209
|
+
fontUrl: `${ base }/fonts/fontastic.svg`,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if ( 'fontello' === type ) {
|
|
214
|
+
return {
|
|
215
|
+
custom_icon_type: type,
|
|
216
|
+
configUrl: `${ base }/config.json`,
|
|
217
|
+
fontUrl: `${ base }/font/fontello.svg`,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return {};
|
|
222
|
+
} catch {
|
|
223
|
+
return type ? { custom_icon_type: type } : {};
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function loadCustomLibrary(
|
|
228
|
+
library: CustomIconLibraryConfig,
|
|
229
|
+
signal?: AbortSignal
|
|
230
|
+
): Promise< FontAwesome7Icon[] > {
|
|
231
|
+
enqueueIconFonts( library.name );
|
|
232
|
+
|
|
233
|
+
const names = parseIconNames( await loadLibraryPayload( library, signal ) );
|
|
234
|
+
const svgMap = await loadLibrarySvgMap( library.name, library, signal );
|
|
235
|
+
|
|
236
|
+
return names.map( ( name ) => {
|
|
237
|
+
const value = createCustomIconSelectionValue( library, name );
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
id: `${ library.name }:${ name }`,
|
|
241
|
+
name,
|
|
242
|
+
label: name.replace( /-/g, ' ' ),
|
|
243
|
+
library: library.name,
|
|
244
|
+
value,
|
|
245
|
+
aliases: [],
|
|
246
|
+
width: DEFAULT_ICON_SIZE,
|
|
247
|
+
height: DEFAULT_ICON_SIZE,
|
|
248
|
+
paths: [],
|
|
249
|
+
glyphClass: value,
|
|
250
|
+
svgMarkup: svgMap[ value ],
|
|
251
|
+
};
|
|
252
|
+
} );
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async function loadLibrarySvgMap(
|
|
256
|
+
library: string,
|
|
257
|
+
config?: CustomIconLibraryConfig,
|
|
258
|
+
signal?: AbortSignal
|
|
259
|
+
): Promise< Record< string, string > > {
|
|
260
|
+
if ( ! config ) {
|
|
261
|
+
svgMapCache.delete( library );
|
|
262
|
+
inFlightSvgMaps.delete( library );
|
|
263
|
+
|
|
264
|
+
return {};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const cached = svgMapCache.get( library );
|
|
268
|
+
|
|
269
|
+
if ( cached && Object.keys( cached ).length > 0 ) {
|
|
270
|
+
return cached;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const pending = inFlightSvgMaps.get( library );
|
|
274
|
+
|
|
275
|
+
if ( pending ) {
|
|
276
|
+
return pending;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const request = fetchLibrarySvgMap( library, config, signal ).finally( () => {
|
|
280
|
+
inFlightSvgMaps.delete( library );
|
|
281
|
+
} );
|
|
282
|
+
|
|
283
|
+
inFlightSvgMaps.set( library, request );
|
|
284
|
+
|
|
285
|
+
return request;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async function fetchLibrarySvgMap(
|
|
289
|
+
library: string,
|
|
290
|
+
config: CustomIconLibraryConfig,
|
|
291
|
+
signal?: AbortSignal
|
|
292
|
+
): Promise< Record< string, string > > {
|
|
293
|
+
const fromRest = await loadSvgMapFromRest( library, signal );
|
|
294
|
+
const restIcons = fromRest.icons;
|
|
295
|
+
|
|
296
|
+
if ( Object.keys( restIcons ).length > 0 && ! fromRest.truncated ) {
|
|
297
|
+
svgMapCache.set( library, restIcons );
|
|
298
|
+
|
|
299
|
+
return restIcons;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const fromPack = await loadSvgMapFromPackFiles( config, signal );
|
|
303
|
+
const merged = { ...fromPack, ...restIcons };
|
|
304
|
+
|
|
305
|
+
if ( Object.keys( merged ).length > 0 ) {
|
|
306
|
+
svgMapCache.set( library, merged );
|
|
307
|
+
|
|
308
|
+
return merged;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return {};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async function loadSvgMapFromRest(
|
|
315
|
+
library: string,
|
|
316
|
+
signal?: AbortSignal
|
|
317
|
+
): Promise< { icons: Record< string, string >; truncated: boolean } > {
|
|
318
|
+
try {
|
|
319
|
+
const response = await httpService().get<
|
|
320
|
+
HttpResponse< { icons?: Record< string, unknown >; truncated?: unknown } >
|
|
321
|
+
>( CUSTOM_ICON_SVG_URL, { params: { library: String( library ) }, signal } );
|
|
322
|
+
|
|
323
|
+
return unwrapIconMap( response );
|
|
324
|
+
} catch {
|
|
325
|
+
return { icons: {}, truncated: false };
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function unwrapIconMap( payload: unknown ): { icons: Record< string, string >; truncated: boolean } {
|
|
330
|
+
let current: unknown = payload;
|
|
331
|
+
|
|
332
|
+
for ( let depth = 0; depth < 4; depth++ ) {
|
|
333
|
+
if ( ! current || typeof current !== 'object' ) {
|
|
334
|
+
return { icons: {}, truncated: false };
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const record = current as Record< string, unknown >;
|
|
338
|
+
|
|
339
|
+
if ( record.icons && typeof record.icons === 'object' && ! Array.isArray( record.icons ) ) {
|
|
340
|
+
const icons: Record< string, string > = {};
|
|
341
|
+
|
|
342
|
+
Object.entries( record.icons as Record< string, unknown > ).forEach( ( [ key, value ] ) => {
|
|
343
|
+
if ( typeof value === 'string' && value !== '' ) {
|
|
344
|
+
icons[ key ] = value;
|
|
345
|
+
}
|
|
346
|
+
} );
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
icons,
|
|
350
|
+
truncated: true === record.truncated,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
current = 'data' in record ? record.data : undefined;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return { icons: {}, truncated: false };
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
async function loadSvgMapFromPackFiles(
|
|
361
|
+
library: CustomIconLibraryConfig,
|
|
362
|
+
signal?: AbortSignal
|
|
363
|
+
): Promise< Record< string, string > > {
|
|
364
|
+
const type = resolveCustomIconType( library );
|
|
365
|
+
|
|
366
|
+
if ( ! type ) {
|
|
367
|
+
return {};
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if ( 'icomoon' === type && library.selectionUrl ) {
|
|
371
|
+
try {
|
|
372
|
+
const response = await fetch( library.selectionUrl, { signal, mode: 'cors' } );
|
|
373
|
+
|
|
374
|
+
if ( response.ok ) {
|
|
375
|
+
const selectionJson = await response.text();
|
|
376
|
+
const names = parseIconNames( safeJson( selectionJson ) );
|
|
377
|
+
const payloadNames = names.length > 0 ? names : parseIconNames( { icons: library.icons } );
|
|
378
|
+
const fromSelection = parseIcomoonSelection(
|
|
379
|
+
selectionJson,
|
|
380
|
+
library.prefix,
|
|
381
|
+
library.displayPrefix ?? '',
|
|
382
|
+
payloadNames
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
if ( Object.keys( fromSelection ).length > 0 ) {
|
|
386
|
+
return fromSelection;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
} catch {
|
|
390
|
+
// Continue with SVG-font fallback.
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const fontUrls = uniqueUrls( [
|
|
395
|
+
library.fontUrl,
|
|
396
|
+
'fontello' === type && library.fetchJson ? packSiblingUrl( library.fetchJson, 'font/fontello.svg' ) : null,
|
|
397
|
+
'fontastic' === type && library.fetchJson ? packSiblingUrl( library.fetchJson, 'fonts/fontastic.svg' ) : null,
|
|
398
|
+
'icomoon' === type && library.fetchJson ? packSiblingUrl( library.fetchJson, 'fonts/icomoon.svg' ) : null,
|
|
399
|
+
] );
|
|
400
|
+
|
|
401
|
+
if ( fontUrls.length === 0 ) {
|
|
402
|
+
return {};
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
let configJson = '{}';
|
|
406
|
+
|
|
407
|
+
if ( library.configUrl ) {
|
|
408
|
+
try {
|
|
409
|
+
const configResponse = await fetch( library.configUrl, { signal, mode: 'cors' } );
|
|
410
|
+
|
|
411
|
+
if ( configResponse.ok ) {
|
|
412
|
+
configJson = await configResponse.text();
|
|
413
|
+
}
|
|
414
|
+
} catch {
|
|
415
|
+
configJson = '{}';
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const names = parseIconNames( safeJson( configJson ) );
|
|
420
|
+
const payloadNames = names.length > 0 ? names : parseIconNames( { icons: library.icons } );
|
|
421
|
+
|
|
422
|
+
for ( const fontUrl of fontUrls ) {
|
|
423
|
+
try {
|
|
424
|
+
const fontResponse = await fetch( fontUrl, { signal, mode: 'cors' } );
|
|
425
|
+
|
|
426
|
+
if ( ! fontResponse.ok ) {
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const fromFont = parseFontelloSvgFont(
|
|
431
|
+
configJson,
|
|
432
|
+
await fontResponse.text(),
|
|
433
|
+
library.prefix,
|
|
434
|
+
library.displayPrefix ?? '',
|
|
435
|
+
payloadNames
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
if ( Object.keys( fromFont ).length > 0 ) {
|
|
439
|
+
return fromFont;
|
|
440
|
+
}
|
|
441
|
+
} catch {
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return {};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function packSiblingUrl( fetchJson: string, relativePath: string ): string | null {
|
|
450
|
+
try {
|
|
451
|
+
const url = new URL( fetchJson, window.location.origin );
|
|
452
|
+
|
|
453
|
+
url.pathname = url.pathname.replace( /\/[^/]+$/, `/${ relativePath }` );
|
|
454
|
+
|
|
455
|
+
return url.href;
|
|
456
|
+
} catch {
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function uniqueUrls( urls: Array< string | null | undefined > ): string[] {
|
|
462
|
+
return [ ...new Set( urls.filter( ( url ): url is string => typeof url === 'string' && url !== '' ) ) ];
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function safeJson( value: string ): unknown {
|
|
466
|
+
try {
|
|
467
|
+
return JSON.parse( value );
|
|
468
|
+
} catch {
|
|
469
|
+
return null;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
async function loadLibraryPayload( library: CustomIconLibraryConfig, signal?: AbortSignal ): Promise< unknown > {
|
|
474
|
+
if ( library.icons !== undefined ) {
|
|
475
|
+
return { icons: library.icons };
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if ( ! library.fetchJson ) {
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
try {
|
|
483
|
+
const response = await fetch( library.fetchJson, { signal, mode: 'cors' } );
|
|
484
|
+
|
|
485
|
+
return response.ok ? response.json() : null;
|
|
486
|
+
} catch {
|
|
487
|
+
return null;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function parseIconNames( payload: unknown ): string[] {
|
|
492
|
+
if ( ! payload || typeof payload !== 'object' ) {
|
|
493
|
+
return [];
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const icons = ( payload as { icons?: unknown } ).icons;
|
|
497
|
+
|
|
498
|
+
if ( Array.isArray( icons ) ) {
|
|
499
|
+
return icons
|
|
500
|
+
.flatMap( ( entry ) => {
|
|
501
|
+
if ( typeof entry === 'string' ) {
|
|
502
|
+
return [ normalizeIconName( entry ) ];
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if ( entry && typeof entry === 'object' && 'name' in entry && typeof entry.name === 'string' ) {
|
|
506
|
+
return [ normalizeIconName( entry.name ) ];
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (
|
|
510
|
+
entry &&
|
|
511
|
+
typeof entry === 'object' &&
|
|
512
|
+
'properties' in entry &&
|
|
513
|
+
entry.properties &&
|
|
514
|
+
typeof entry.properties === 'object' &&
|
|
515
|
+
'name' in entry.properties &&
|
|
516
|
+
typeof entry.properties.name === 'string'
|
|
517
|
+
) {
|
|
518
|
+
return [ normalizeIconName( entry.properties.name ) ];
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
return [];
|
|
522
|
+
} )
|
|
523
|
+
.filter( Boolean );
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if ( icons && typeof icons === 'object' ) {
|
|
527
|
+
return Object.keys( icons ).map( normalizeIconName ).filter( Boolean );
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const glyphs = ( payload as { glyphs?: unknown } ).glyphs;
|
|
531
|
+
|
|
532
|
+
if ( Array.isArray( glyphs ) ) {
|
|
533
|
+
return glyphs
|
|
534
|
+
.flatMap( ( entry ) => {
|
|
535
|
+
if ( entry && typeof entry === 'object' && 'css' in entry && typeof entry.css === 'string' ) {
|
|
536
|
+
return [ normalizeIconName( entry.css ) ];
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
return [];
|
|
540
|
+
} )
|
|
541
|
+
.filter( Boolean );
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
return [];
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function normalizeIconName( name: string ): string {
|
|
548
|
+
return name.trim().replace( /^:/, '' ).replace( /:$/, '' );
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function createCustomIconSelectionValue( library: CustomIconLibraryConfig, name: string ): string {
|
|
552
|
+
const prefix = library.prefix;
|
|
553
|
+
const displayPrefix = library.displayPrefix || prefix.replace( /-$/, '' );
|
|
554
|
+
|
|
555
|
+
return `${ displayPrefix } ${ prefix }${ name }`.trim();
|
|
556
|
+
}
|
|
@@ -9,13 +9,15 @@ import {
|
|
|
9
9
|
export { FONT_AWESOME_7_LIBRARIES, getFontAwesome7EditorConfig } from './font-awesome-7-data';
|
|
10
10
|
|
|
11
11
|
export type FontAwesome7Library = ( typeof FONT_AWESOME_7_LIBRARIES )[ number ][ 'library' ];
|
|
12
|
-
export type FontAwesome7LibraryFilter =
|
|
12
|
+
export type FontAwesome7LibraryFilter = string[];
|
|
13
13
|
|
|
14
14
|
export type FontAwesome7Icon = FontAwesome7IconDefinition & {
|
|
15
15
|
id: string;
|
|
16
16
|
label: string;
|
|
17
|
-
library:
|
|
17
|
+
library: string;
|
|
18
18
|
value: string;
|
|
19
|
+
glyphClass?: string;
|
|
20
|
+
svgMarkup?: string;
|
|
19
21
|
};
|
|
20
22
|
|
|
21
23
|
export async function loadFontAwesome7Catalog( signal?: AbortSignal ): Promise< FontAwesome7Icon[] > {
|
|
@@ -85,20 +87,27 @@ export function findFontAwesome7Icon(
|
|
|
85
87
|
iconClass: string | null,
|
|
86
88
|
library: string | null
|
|
87
89
|
): FontAwesome7Icon | undefined {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if ( ! selectedId || ! library ) {
|
|
90
|
+
if ( ! iconClass || ! library ) {
|
|
91
91
|
return undefined;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
const
|
|
94
|
+
const selectedId = getSelectedIconId( iconClass, library );
|
|
95
|
+
const selectedName = selectedId?.slice( `${ library }:`.length );
|
|
95
96
|
|
|
96
97
|
return icons.find( ( icon ) => {
|
|
97
98
|
if ( icon.library !== library ) {
|
|
98
99
|
return false;
|
|
99
100
|
}
|
|
100
101
|
|
|
101
|
-
|
|
102
|
+
if ( icon.value === iconClass || icon.id === selectedId ) {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if ( ! selectedName ) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return icon.name === selectedName || icon.aliases.includes( selectedName );
|
|
102
111
|
} );
|
|
103
112
|
}
|
|
104
113
|
|
|
@@ -17,6 +17,7 @@ type FontAwesomeIconJson = [ number, number, unknown[], unknown, string | string
|
|
|
17
17
|
export type FontAwesome7EditorConfig = {
|
|
18
18
|
jsonFiles: string[];
|
|
19
19
|
jsonBaseUrl: string;
|
|
20
|
+
filter?: unknown;
|
|
20
21
|
};
|
|
21
22
|
|
|
22
23
|
export type FontAwesome7IconDefinition = {
|
|
@@ -50,6 +51,7 @@ export function getFontAwesome7EditorConfig(): FontAwesome7EditorConfig | null {
|
|
|
50
51
|
return {
|
|
51
52
|
jsonFiles: config.jsonFiles,
|
|
52
53
|
jsonBaseUrl,
|
|
54
|
+
filter: config.filter,
|
|
53
55
|
};
|
|
54
56
|
}
|
|
55
57
|
|