forked from EVOgeek/Vmeda.Online
		
	MOBILE-4309 format-text: Treat font awesome tags to be rendered
This commit is contained in:
		
							parent
							
								
									b60fd49cec
								
							
						
					
					
						commit
						a7df95fd50
					
				| @ -14,12 +14,10 @@ | ||||
| 
 | ||||
| import { AfterViewInit, Directive, ElementRef, Input, OnChanges, SimpleChange } from '@angular/core'; | ||||
| import { CoreLogger } from '@singletons/logger'; | ||||
| import { Http } from '@singletons'; | ||||
| import { CoreConstants } from '@/core/constants'; | ||||
| import { CorePromisedValue } from '@classes/promised-value'; | ||||
| import { CoreIcons } from '@singletons/icons'; | ||||
| 
 | ||||
| /** | ||||
|  * Directive to enable font-awesome 6.3 as ionicons. | ||||
|  * Directive to enable font-awesome 6.4 as ionicons. | ||||
|  * Check available icons at https://fontawesome.com/search?o=r&m=free
 | ||||
|  * | ||||
|  * Example usage: | ||||
| @ -31,13 +29,6 @@ import { CorePromisedValue } from '@classes/promised-value'; | ||||
| }) | ||||
| export class CoreFaIconDirective implements AfterViewInit, OnChanges { | ||||
| 
 | ||||
|     /** | ||||
|      * Object used to store whether icons exist or not during development. | ||||
|      */ | ||||
|     private static readonly DEV_ICONS_STATUS: Record<string, Promise<boolean>> = {}; | ||||
| 
 | ||||
|     protected static aliases?: CorePromisedValue<Record<string, string>>; | ||||
| 
 | ||||
|     @Input() name = ''; | ||||
| 
 | ||||
|     protected element: HTMLElement; | ||||
| @ -95,19 +86,19 @@ export class CoreFaIconDirective implements AfterViewInit, OnChanges { | ||||
|         iconName = iconName.substring(parts[0].length + 1); | ||||
| 
 | ||||
|         // Set it here to avoid loading unexisting icon paths (svg/iconName) caused by the tick delay of the checkIconAlias promise.
 | ||||
|         let src = `assets/fonts/${font}/${library}/${iconName}.svg`; | ||||
|         let src = CoreIcons.getIconSrc(font, library, iconName); | ||||
|         this.element.setAttribute('src', src); | ||||
| 
 | ||||
|         if (font === 'font-awesome') { | ||||
|             const iconNameChecked = await this.checkIconAlias(iconName); | ||||
|             if (iconNameChecked !== iconName) { | ||||
|                 src = `assets/fonts/${font}/${library}/${iconName}.svg`; | ||||
|             const { fileName } = await CoreIcons.getFontAwesomeIconFileName(iconName); | ||||
|             if (fileName !== iconName) { | ||||
|                 src = CoreIcons.getIconSrc(font, library, fileName); | ||||
|                 this.element.setAttribute('src', src); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         this.element.classList.add('faicon'); | ||||
|         this.validateIcon(this.name, src); | ||||
|         CoreIcons.validateIcon(this.name, src); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| @ -135,77 +126,4 @@ export class CoreFaIconDirective implements AfterViewInit, OnChanges { | ||||
|         this.setIcon(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check icon alias and returns the new icon name. | ||||
|      * | ||||
|      * @param iconName Icon name. | ||||
|      * @returns New icon name. | ||||
|      */ | ||||
|     protected async checkIconAlias(iconName: string): Promise<string> { | ||||
|         const aliases = await CoreFaIconDirective.getIconsAliases(); | ||||
| 
 | ||||
|         if (aliases[iconName]) { | ||||
|             this.logger.error(`Icon ${iconName} is an alias of ${aliases[iconName]}, please use the new name.`); | ||||
| 
 | ||||
|             return aliases[iconName]; | ||||
|         } | ||||
| 
 | ||||
|         return iconName; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Read the icon aliases json file. | ||||
|      * | ||||
|      * @returns Promise resolved when loaded. | ||||
|      */ | ||||
|     protected static async getIconsAliases(): Promise<Record<string, string>> { | ||||
|         if (CoreFaIconDirective.aliases !== undefined) { | ||||
|             return CoreFaIconDirective.aliases; | ||||
|         } | ||||
| 
 | ||||
|         CoreFaIconDirective.aliases = new CorePromisedValue(); | ||||
| 
 | ||||
|         try { | ||||
|             const aliases = await Http.get<Record<string, string>>('assets/fonts/font-awesome/aliases.json', { | ||||
|                 responseType: 'json', | ||||
|             }).toPromise(); | ||||
| 
 | ||||
|             CoreFaIconDirective.aliases.resolve(aliases); | ||||
| 
 | ||||
|             return aliases; | ||||
|         } catch { | ||||
|             CoreFaIconDirective.aliases.resolve({}); | ||||
| 
 | ||||
|             return {}; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Validate that an icon exists, or show warning otherwise (only in development and testing environments). | ||||
|      * | ||||
|      * @param name Icon name. | ||||
|      * @param src Icon source url. | ||||
|      */ | ||||
|     private validateIcon(name: string, src: string): void { | ||||
|         if (!CoreConstants.BUILD.isDevelopment && !CoreConstants.BUILD.isTesting) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if (!(src in CoreFaIconDirective.DEV_ICONS_STATUS)) { | ||||
|             CoreFaIconDirective.DEV_ICONS_STATUS[src] = Http.get(src, { responseType: 'text' }) | ||||
|                 .toPromise() | ||||
|                 .then(() => true) | ||||
|                 .catch(() => false); | ||||
|         } | ||||
| 
 | ||||
|         // eslint-disable-next-line promise/catch-or-return
 | ||||
|         CoreFaIconDirective.DEV_ICONS_STATUS[src].then(exists => { | ||||
|             if (exists) { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             return this.logger.error(`Icon ${name} not found`); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -54,6 +54,7 @@ import { ElementController } from '@classes/element-controllers/ElementControlle | ||||
| import { MediaElementController } from '@classes/element-controllers/MediaElementController'; | ||||
| import { FrameElementController } from '@classes/element-controllers/FrameElementController'; | ||||
| import { CoreUrl } from '@singletons/url'; | ||||
| import { CoreIcons } from '@singletons/icons'; | ||||
| 
 | ||||
| /** | ||||
|  * Directive to format text rendered. It renders the HTML and treats all links and media, using CoreLinkDirective | ||||
| @ -278,10 +279,10 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec | ||||
|             button.classList.add('core-image-viewer-icon'); | ||||
|             button.classList.add('hidden'); | ||||
|             button.setAttribute('aria-label', label); | ||||
|             const iconName = 'up-right-and-down-left-from-center'; | ||||
|             const src = CoreIcons.getIconSrc('font-awesome', 'solid', iconName); | ||||
|             // Add an ion-icon item to apply the right styles, but the ion-icon component won't be executed.
 | ||||
|             button.innerHTML = '<ion-icon name="fas-up-right-and-down-left-from-center" aria-hidden="true" \ | ||||
|                 src="assets/fonts/font-awesome/solid/up-right-and-down-left-from-center.svg">\ | ||||
|             </ion-icon>'; | ||||
|             button.innerHTML = `<ion-icon name="fas-${iconName}" aria-hidden="true" src="${src}"></ion-icon>`; | ||||
| 
 | ||||
|             button.addEventListener('click', (e: Event) => { | ||||
|                 e.preventDefault(); | ||||
| @ -478,6 +479,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec | ||||
|         const videos = Array.from(div.querySelectorAll('video')); | ||||
|         const iframes = Array.from(div.querySelectorAll('iframe')); | ||||
|         const buttons = Array.from(div.querySelectorAll('.button')); | ||||
|         const icons = Array.from(div.querySelectorAll('i.fa,i.fas,i.far,i.fab')); | ||||
|         const elementsWithInlineStyles = Array.from(div.querySelectorAll('*[style]')); | ||||
|         const stopClicksElements = Array.from(div.querySelectorAll('button,input,select,textarea')); | ||||
|         const frames = Array.from(div.querySelectorAll(CoreIframeUtilsProvider.FRAME_TAGS.join(',').replace(/iframe,?/, ''))); | ||||
| @ -550,6 +552,11 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         // Handle Font Awesome icons to be rendered by the app.
 | ||||
|         icons.forEach((icon) => { | ||||
|             CoreIcons.replaceCSSIcon(icon); | ||||
|         }); | ||||
| 
 | ||||
|         // Handle inline styles.
 | ||||
|         elementsWithInlineStyles.forEach((el: HTMLElement) => { | ||||
|             // Only add external content for tags that haven't been treated already.
 | ||||
|  | ||||
| @ -26,6 +26,7 @@ import { CoreWSFile } from '@services/ws'; | ||||
| import { makeSingleton, Translate } from '@singletons'; | ||||
| import { CoreQuestion, CoreQuestionProvider, CoreQuestionQuestionParsed, CoreQuestionsAnswers } from './question'; | ||||
| import { CoreQuestionDelegate } from './question-delegate'; | ||||
| import { CoreIcons } from '@singletons/icons'; | ||||
| 
 | ||||
| /** | ||||
|  * Service with some common functions to handle questions. | ||||
| @ -801,12 +802,14 @@ export class CoreQuestionHelperProvider { | ||||
|             const newIcon: HTMLIonIconElement = document.createElement('ion-icon'); | ||||
| 
 | ||||
|             if (correct) { | ||||
|                 newIcon.setAttribute('name', 'fas-check'); | ||||
|                 newIcon.setAttribute('src', 'assets/fonts/font-awesome/solid/check.svg'); | ||||
|                 const iconName = 'check'; | ||||
|                 newIcon.setAttribute('name', `fas-${iconName}`); | ||||
|                 newIcon.setAttribute('src', CoreIcons.getIconSrc('font-awesome', 'solid', iconName)); | ||||
|                 newIcon.className = 'core-correct-icon ion-color ion-color-success questioncorrectnessicon'; | ||||
|             } else { | ||||
|                 newIcon.setAttribute('name', 'fas-xmark'); | ||||
|                 newIcon.setAttribute('src', 'assets/fonts/font-awesome/solid/xmark.svg'); | ||||
|                 const iconName = 'xmark'; | ||||
|                 newIcon.setAttribute('name', `fas-${iconName}`); | ||||
|                 newIcon.setAttribute('src', CoreIcons.getIconSrc('font-awesome', 'solid', iconName)); | ||||
|                 newIcon.className = 'core-correct-icon ion-color ion-color-danger questioncorrectnessicon'; | ||||
|             } | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										198
									
								
								src/core/singletons/icons.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										198
									
								
								src/core/singletons/icons.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,198 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Http } from '@singletons'; | ||||
| import { CoreConstants } from '../constants'; | ||||
| import { CoreLogger } from './logger'; | ||||
| import aliases from '@/assets/fonts/font-awesome/aliases.json'; | ||||
| 
 | ||||
| /** | ||||
|  * Singleton with helper functions for icon management. | ||||
|  */ | ||||
| export class CoreIcons { | ||||
| 
 | ||||
|     /** | ||||
|      * Object used to store whether icons exist or not during development. | ||||
|      */ | ||||
|     private static readonly DEV_ICONS_STATUS: Record<string, Promise<boolean>> = {}; | ||||
| 
 | ||||
|     private static readonly ALIASES = { ...aliases } as unknown as Record<string, string>; | ||||
| 
 | ||||
|     protected static logger = CoreLogger.getInstance('CoreIcons'); | ||||
| 
 | ||||
|     /** | ||||
|      * Check icon alias and returns the new icon name. | ||||
|      * | ||||
|      * @param icon Icon name. | ||||
|      * @returns New icon name and new library if changed. | ||||
|      */ | ||||
|     static async getFontAwesomeIconFileName(icon: string): Promise<{fileName: string; newLibrary?: string}> { | ||||
|         let newLibrary: string | undefined = undefined; | ||||
|         if (icon.endsWith('-o')) { | ||||
|             newLibrary = 'regular'; | ||||
|             icon = icon.substring(0, icon.length - 2); | ||||
|         } | ||||
| 
 | ||||
|         if (CoreIcons.ALIASES[icon]) { | ||||
|             this.logger.error(`Icon ${icon} is an alias of ${CoreIcons.ALIASES[icon]}, please use the new name.`); | ||||
| 
 | ||||
|             return { newLibrary, fileName: CoreIcons.ALIASES[icon] }; | ||||
|         } | ||||
| 
 | ||||
|         return { newLibrary, fileName: icon }; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Validate that an icon exists, or show warning otherwise (only in development and testing environments). | ||||
|      * | ||||
|      * @param name Icon name. | ||||
|      * @param src Icon source url. | ||||
|      */ | ||||
|     static validateIcon(name: string, src: string): void { | ||||
|         if (!CoreConstants.BUILD.isDevelopment && !CoreConstants.BUILD.isTesting) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if (!(src in CoreIcons.DEV_ICONS_STATUS)) { | ||||
|             CoreIcons.DEV_ICONS_STATUS[src] = Http.get(src, { responseType: 'text' }) | ||||
|                 .toPromise() | ||||
|                 .then(() => true) | ||||
|                 .catch(() => false); | ||||
|         } | ||||
| 
 | ||||
|         // eslint-disable-next-line promise/catch-or-return
 | ||||
|         CoreIcons.DEV_ICONS_STATUS[src].then(exists => { | ||||
|             if (exists) { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             return this.logger.error(`Icon ${name} not found`); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Replaces an <i> icon that uses CSS by a ion-icon with SVG. | ||||
|      * It supports from 4.7 to 6.4 Font awesome versions. | ||||
|      * But it can fail on 4.7 and 5.x because of the lack of assets. | ||||
|      * | ||||
|      * @param icon Current icon element. | ||||
|      * @returns New icon, already included in the DOM. | ||||
|      */ | ||||
|     static async replaceCSSIcon(icon: Element): Promise<HTMLIonIconElement | undefined> { | ||||
|         let library = 'solid'; | ||||
|         let iconName = ''; | ||||
| 
 | ||||
|         Array.from(icon.classList).forEach(async (className) => { | ||||
|             // Library name of 5.x
 | ||||
|             switch (className) { | ||||
|                 case 'fas': | ||||
|                     library = 'solid'; | ||||
| 
 | ||||
|                     return; | ||||
|                 case 'far': | ||||
|                     library = 'regular'; | ||||
| 
 | ||||
|                     return; | ||||
|                 case 'fab': | ||||
|                     library = 'brands'; | ||||
| 
 | ||||
|                     return; | ||||
|             } | ||||
| 
 | ||||
|             // Check fa- style class names.
 | ||||
|             const faPart = className.match(/fa-([a-zA-Z0-9-]+)/); | ||||
|             if (!faPart) { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             const firstPart = faPart[1].split('-')[0]; | ||||
| 
 | ||||
|             switch (firstPart) { | ||||
|                 // Class is defining library.
 | ||||
|                 case 'solid': | ||||
|                     library = 'solid'; | ||||
|                     break; | ||||
|                 case 'regular': | ||||
|                 case 'light': | ||||
|                     library = 'regular'; | ||||
|                     break; | ||||
|                 case 'brands': | ||||
|                     library = 'brands'; | ||||
|                     break; | ||||
|                 // Class is defining special cases.
 | ||||
|                 case '2xs': | ||||
|                 case 'xs': | ||||
|                 case 'sm': | ||||
|                 case 'lg': | ||||
|                 case 'xl': | ||||
|                 case '2xl': | ||||
|                 case 'fw': | ||||
|                 case 'sharp': | ||||
|                 case 'rotate': | ||||
|                     return; | ||||
|                 // Class is defining the icon name (fa-ICONNAME).
 | ||||
|                 default: | ||||
|                     iconName = faPart[1]; | ||||
|                     break; | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         if (!iconName) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         const newIcon = document.createElement('ion-icon'); | ||||
| 
 | ||||
|         Array.from(icon.attributes).forEach(attr => { | ||||
|             newIcon.setAttribute(attr.nodeName, attr.nodeValue || ''); | ||||
|         }); | ||||
| 
 | ||||
|         if (!newIcon.getAttribute('aria-label') && | ||||
|                 !newIcon.getAttribute('aria-labelledby') && | ||||
|                 !newIcon.getAttribute('title')) { | ||||
|             newIcon.setAttribute('aria-hidden', 'true'); | ||||
|         } | ||||
| 
 | ||||
|         const { fileName, newLibrary } = await CoreIcons.getFontAwesomeIconFileName(iconName); | ||||
|         if (newLibrary) { | ||||
|             library = newLibrary; | ||||
|         } | ||||
|         iconName = fileName; | ||||
| 
 | ||||
|         const src = CoreIcons.getIconSrc('font-awesome', library, iconName); | ||||
| 
 | ||||
|         newIcon.setAttribute('src', src); | ||||
| 
 | ||||
|         newIcon.classList.add('faicon'); | ||||
|         CoreIcons.validateIcon(iconName, src); | ||||
| 
 | ||||
|         icon.parentElement?.insertBefore(newIcon, icon); | ||||
|         icon.remove(); | ||||
| 
 | ||||
|         return newIcon; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get icon SVG path. | ||||
|      * | ||||
|      * @param font Font Family. | ||||
|      * @param library Library to use. | ||||
|      * @param icon Icon Name. | ||||
|      * @returns Path. | ||||
|      */ | ||||
|     static getIconSrc(font: string, library: string, icon: string): string { | ||||
|         return `assets/fonts/${font}/${library}/${icon}.svg`; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										84
									
								
								src/core/singletons/tests/icons.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								src/core/singletons/tests/icons.test.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,84 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { CoreIcons } from '@singletons/icons'; | ||||
| 
 | ||||
| describe('CoreIcons singleton', () => { | ||||
| 
 | ||||
|     it('replaces CSS icon with the correspondant ion-icon', async () => { | ||||
|         const icon = document.createElement('i'); | ||||
| 
 | ||||
|         // Not an icon
 | ||||
|         icon.className = 'test'; | ||||
|         expect((await CoreIcons.replaceCSSIcon(icon))) | ||||
|             .toEqual(undefined); | ||||
| 
 | ||||
|         icon.className = 'fas fanoicon'; | ||||
|         expect((await CoreIcons.replaceCSSIcon(icon))) | ||||
|             .toEqual(undefined); | ||||
| 
 | ||||
|         icon.className = 'fa-solid fanoicon'; | ||||
|         expect((await CoreIcons.replaceCSSIcon(icon))) | ||||
|             .toEqual(undefined); | ||||
| 
 | ||||
|         // Font awesome 6
 | ||||
|         icon.className = 'fa-solid fa-face-awesome'; | ||||
|         expect((await CoreIcons.replaceCSSIcon(icon))?.getAttribute('src')) | ||||
|             .toEqual('assets/fonts/font-awesome/solid/face-awesome.svg'); | ||||
| 
 | ||||
|         icon.className = 'fa-regular fa-face-awesome'; | ||||
|         expect((await CoreIcons.replaceCSSIcon(icon))?.getAttribute('src')) | ||||
|             .toEqual('assets/fonts/font-awesome/regular/face-awesome.svg'); | ||||
| 
 | ||||
|         icon.className = 'fa-light fa-face-awesome'; | ||||
|         expect((await CoreIcons.replaceCSSIcon(icon))?.getAttribute('src')) | ||||
|             .toEqual('assets/fonts/font-awesome/regular/face-awesome.svg'); | ||||
| 
 | ||||
|         icon.className = 'fa-brands fa-facebook'; | ||||
|         expect((await CoreIcons.replaceCSSIcon(icon))?.getAttribute('src')) | ||||
|             .toEqual('assets/fonts/font-awesome/brands/facebook.svg'); | ||||
| 
 | ||||
|         // Font awesome 5
 | ||||
|         icon.className = 'fas fa-yin-yang'; | ||||
|         expect((await CoreIcons.replaceCSSIcon(icon))?.getAttribute('src')) | ||||
|             .toEqual('assets/fonts/font-awesome/solid/yin-yang.svg'); | ||||
| 
 | ||||
|         icon.className = 'far fa-wrench'; | ||||
|         expect((await CoreIcons.replaceCSSIcon(icon))?.getAttribute('src')) | ||||
|             .toEqual('assets/fonts/font-awesome/regular/wrench.svg'); | ||||
| 
 | ||||
|         icon.className = 'fab fa-youtube'; | ||||
|         expect((await CoreIcons.replaceCSSIcon(icon))?.getAttribute('src')) | ||||
|             .toEqual('assets/fonts/font-awesome/brands/youtube.svg'); | ||||
| 
 | ||||
|         // Font awesome 4.7
 | ||||
|         icon.className = 'fa fa-address-book'; | ||||
|         expect((await CoreIcons.replaceCSSIcon(icon))?.getAttribute('src')) | ||||
|             .toEqual('assets/fonts/font-awesome/solid/address-book.svg'); | ||||
| 
 | ||||
|         icon.className = 'fa fa-address-book-o'; | ||||
|         expect((await CoreIcons.replaceCSSIcon(icon))?.getAttribute('src')) | ||||
|             .toEqual('assets/fonts/font-awesome/regular/address-book.svg'); | ||||
| 
 | ||||
|         // Aliases
 | ||||
|         icon.className = 'fas fa-battery-5'; | ||||
|         expect((await CoreIcons.replaceCSSIcon(icon))?.getAttribute('src')) | ||||
|             .toEqual('assets/fonts/font-awesome/solid/battery-full.svg'); | ||||
| 
 | ||||
|         icon.className = 'fa fa-check-square'; | ||||
|         expect((await CoreIcons.replaceCSSIcon(icon))?.getAttribute('src')) | ||||
|             .toEqual('assets/fonts/font-awesome/solid/square-check.svg'); | ||||
|     }); | ||||
| 
 | ||||
| }); | ||||
| @ -4,6 +4,7 @@ information provided here is intended especially for developers. | ||||
| === 4.3.0 === | ||||
| 
 | ||||
|  - CoreSiteBasicInfo fullName attribute has changed to fullname and avatar to userpictureurl to match user fields. | ||||
|  - Font Awesome icon library has been updated to 6.4.0. But nothing has changed, only version number. | ||||
| 
 | ||||
| === 4.2.0 === | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user