embed free font awesome svg instead of i tag. pro icons will still work with i tag

This commit is contained in:
Saurabh Gore
2025-02-14 16:32:11 +05:30
parent 348401c4f4
commit 302ba725ae
4 changed files with 104 additions and 11 deletions

View File

@@ -68,6 +68,10 @@
},
"dependencies": {
"@braintree/sanitize-url": "^7.0.1",
"@fortawesome/fontawesome-svg-core": "^6.7.2",
"@fortawesome/free-brands-svg-icons": "^6.7.2",
"@fortawesome/free-regular-svg-icons": "^6.7.2",
"@fortawesome/free-solid-svg-icons": "^6.7.2",
"@iconify/utils": "^2.1.32",
"@mermaid-js/parser": "workspace:^",
"@types/d3": "^7.4.3",

View File

@@ -1,12 +1,14 @@
import { describe, it, expect } from 'vitest';
import { replaceIconSubstring } from './createText.js';
import { icon } from '@fortawesome/fontawesome-svg-core';
import { faUser, faArrowRight, faHome } from '@fortawesome/free-solid-svg-icons';
import { faGithub } from '@fortawesome/free-brands-svg-icons';
describe('replaceIconSubstring', () => {
it('converts FontAwesome icon notations to HTML tags', () => {
const input = 'This is an icon: fa:fa-user and fab:fa-github';
const output = replaceIconSubstring(input);
const expected =
"This is an icon: <i class='fa fa-user'></i> and <i class='fab fa-github'></i>";
const expected = `This is an icon: ${icon(faUser).html.join('')} and ${icon(faGithub).html.join('')}`;
expect(output).toEqual(expected);
});
@@ -19,8 +21,7 @@ describe('replaceIconSubstring', () => {
it('correctly processes multiple FontAwesome icon notations in one string', () => {
const input = 'Icons galore: fa:fa-arrow-right, fak:fa-truck, fas:fa-home';
const output = replaceIconSubstring(input);
const expected =
"Icons galore: <i class='fa fa-arrow-right'></i>, <i class='fak fa-truck'></i>, <i class='fas fa-home'></i>";
const expected = `Icons galore: ${icon(faArrowRight).html.join()}, <i class='fak fa-truck'></i>, ${icon(faHome).html.join()}`;
expect(output).toEqual(expected);
});

View File

@@ -11,6 +11,22 @@ import { markdownToHTML, markdownToLines } from '../rendering-util/handle-markdo
import { decodeEntities } from '../utils.js';
import { splitLineToFitWidth } from './splitText.js';
import type { MarkdownLine, MarkdownWord } from './types.js';
import { library, icon } from '@fortawesome/fontawesome-svg-core';
import * as fab from '@fortawesome/free-brands-svg-icons';
import * as fas from '@fortawesome/free-solid-svg-icons';
import * as far from '@fortawesome/free-regular-svg-icons';
const iconListFab = Object.keys(fab)
.filter((key) => key !== 'fab' && key !== 'prefix')
.map((icon) => fab[icon]);
const iconListFas = Object.keys(fas)
.filter((key) => key !== 'fas' && key !== 'prefix')
.map((icon) => fas[icon]);
const iconListFar = Object.keys(far)
.filter((key) => key !== 'far' && key !== 'prefix')
.map((icon) => far[icon]);
library.add(...iconListFab, ...iconListFas, ...iconListFar);
function applyStyle(dom, styleFn) {
if (styleFn) {
@@ -180,14 +196,36 @@ function updateTextContentAndStyles(tspan: any, wrappedLine: MarkdownWord[]) {
/**
* Convert fontawesome labels into fontawesome icons by using a regex pattern
* @param text - The raw string to convert
* @returns string with fontawesome icons as i tags
* @returns string with fontawesome icons as i tags if they are from pro pack and as svg if they are from free pack
*/
export function replaceIconSubstring(text: string) {
// The letters 'bklrs' stand for possible endings of the fontawesome prefix (e.g. 'fab' for brands, 'fak' for fa-kit) // cspell: disable-line
return text.replace(
/fa[bklrs]?:fa-[\w-]+/g, // cspell: disable-line
(s) => `<i class='${s.replace(':', ' ')}'></i>`
);
export function replaceIconSubstring(text) {
const iconRegex = /(fas|fab|far|fa|fal|fak|fad):fa-([a-z-]+)/g;
const classNameMap = {
fas: 'fa-solid',
fab: 'fa-brands',
far: 'fa-regular',
fa: 'fa',
fal: 'fa-light',
fad: 'fa-duotone',
fak: 'fak',
} as const;
const freeIconPack = ['fas', 'fab', 'far', 'fa'];
return text.replace(iconRegex, (match, prefix, iconName) => {
const isFreeIcon = freeIconPack.includes(prefix);
const className = classNameMap[prefix];
if (!isFreeIcon) {
log.warn(`Icon ${prefix}:fa-${iconName} is pro icon.`);
return `<i class='${className} fa-${iconName}'></i>`;
}
const faIcon = icon({ prefix: prefix, iconName: iconName });
if (!faIcon) {
log.warn(`Icon ${prefix}:fa-${iconName} not found.`);
return match;
}
return faIcon.html.join('');
});
}
// Note when using from flowcharts converting the API isNode means classes should be set accordingly. When using htmlLabels => to sett classes to'nodeLabel' when isNode=true otherwise 'edgeLabel'

50
pnpm-lock.yaml generated
View File

@@ -217,6 +217,18 @@ importers:
'@braintree/sanitize-url':
specifier: ^7.0.1
version: 7.1.0
'@fortawesome/fontawesome-svg-core':
specifier: ^6.7.2
version: 6.7.2
'@fortawesome/free-brands-svg-icons':
specifier: ^6.7.2
version: 6.7.2
'@fortawesome/free-regular-svg-icons':
specifier: ^6.7.2
version: 6.7.2
'@fortawesome/free-solid-svg-icons':
specifier: ^6.7.2
version: 6.7.2
'@iconify/utils':
specifier: ^2.1.32
version: 2.1.33
@@ -2225,6 +2237,26 @@ packages:
'@floating-ui/vue@1.1.5':
resolution: {integrity: sha512-ynL1p5Z+woPVSwgMGqeDrx6HrJfGIDzFyESFkyqJKilGW1+h/8yVY29Khn0LaU6wHBRwZ13ntG6reiHWK6jyzw==}
'@fortawesome/fontawesome-common-types@6.7.2':
resolution: {integrity: sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==}
engines: {node: '>=6'}
'@fortawesome/fontawesome-svg-core@6.7.2':
resolution: {integrity: sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==}
engines: {node: '>=6'}
'@fortawesome/free-brands-svg-icons@6.7.2':
resolution: {integrity: sha512-zu0evbcRTgjKfrr77/2XX+bU+kuGfjm0LbajJHVIgBWNIDzrhpRxiCPNT8DW5AdmSsq7Mcf9D1bH0aSeSUSM+Q==}
engines: {node: '>=6'}
'@fortawesome/free-regular-svg-icons@6.7.2':
resolution: {integrity: sha512-7Z/ur0gvCMW8G93dXIQOkQqHo2M5HLhYrRVC0//fakJXxcF1VmMPsxnG6Ee8qEylA8b8Q3peQXWMNZ62lYF28g==}
engines: {node: '>=6'}
'@fortawesome/free-solid-svg-icons@6.7.2':
resolution: {integrity: sha512-GsBrnOzU8uj0LECDfD5zomZJIjrPhIlWU82AHwa2s40FKH+kcxQaBvBo3Z4TxyZHIyX8XTDxsyA33/Vx9eFuQA==}
engines: {node: '>=6'}
'@hapi/hoek@9.3.0':
resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==}
@@ -12533,6 +12565,24 @@ snapshots:
- '@vue/composition-api'
- vue
'@fortawesome/fontawesome-common-types@6.7.2': {}
'@fortawesome/fontawesome-svg-core@6.7.2':
dependencies:
'@fortawesome/fontawesome-common-types': 6.7.2
'@fortawesome/free-brands-svg-icons@6.7.2':
dependencies:
'@fortawesome/fontawesome-common-types': 6.7.2
'@fortawesome/free-regular-svg-icons@6.7.2':
dependencies:
'@fortawesome/fontawesome-common-types': 6.7.2
'@fortawesome/free-solid-svg-icons@6.7.2':
dependencies:
'@fortawesome/fontawesome-common-types': 6.7.2
'@hapi/hoek@9.3.0': {}
'@hapi/topo@5.1.0':