Merge branch 'saurabh/refactor-fontawesome-icon-usage' of github.com-mermaid:mermaid-js/mermaid into saurabh/refactor-fontawesome-icon-usage

This commit is contained in:
saurabhg772244
2025-02-21 23:00:47 +05:30
22 changed files with 228 additions and 166 deletions

View File

@@ -2,4 +2,4 @@
'mermaid': patch
---
Free fontawesome icons are now embeded as svg inside diagram. Pro icons will still be using <i> tag.
Registered icons are now embedded as SVGs inside diagram. If an icon is not available in the registered icons it will still use <i> tag

View File

@@ -0,0 +1,32 @@
import { imgSnapshotTest } from '../../helpers/util.ts';
const themes = ['default', 'forest', 'dark', 'base', 'neutral'];
themes.forEach((theme, index) => {
describe('Flowchart Icon', () => {
it(`${index + 1}-icon: verify if icons are working from fontawesome library ${theme} theme`, () => {
imgSnapshotTest(
`flowchart TD
A("fab:fa-twitter Twitter") --> B("fab:fa-facebook Facebook")
B --> C("fa:fa-coffee Coffee")
C --> D("fa:fa-car Car")
D --> E("fab:fa-github GitHub")
`,
{ theme }
);
});
});
});
themes.forEach((theme, index) => {
describe('Flowchart Icon', () => {
it(`${index + 1}-icon: verify if registered icons are working on ${theme} theme`, () => {
imgSnapshotTest(
`flowchart TD
A("fa:fa-bell Bell")
`,
{ theme }
);
});
});
});

View File

@@ -6,6 +6,10 @@
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"
rel="stylesheet"
/>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css"
/>
<style>
svg {
border: 2px solid darkred;

View File

@@ -51,7 +51,7 @@ const contentLoaded = async function () {
mermaid.registerLayoutLoaders(layouts);
mermaid.initialize(graphObj.mermaid);
const staticBellIconPack = {
prefix: 'fa6-regular',
prefix: 'fa',
icons: {
bell: {
body: '<path fill="currentColor" d="M224 0c-17.7 0-32 14.3-32 32v19.2C119 66 64 130.6 64 208v25.4c0 45.4-15.5 89.5-43.8 124.9L5.3 377c-5.8 7.2-6.9 17.1-2.9 25.4S14.8 416 24 416h400c9.2 0 17.6-5.3 21.6-13.6s2.9-18.2-2.9-25.4l-14.9-18.6c-28.3-35.5-43.8-79.6-43.8-125V208c0-77.4-55-142-128-156.8V32c0-17.7-14.3-32-32-32m0 96c61.9 0 112 50.1 112 112v25.4c0 47.9 13.9 94.6 39.7 134.6H72.3c25.8-40 39.7-86.7 39.7-134.6V208c0-61.9 50.1-112 112-112m64 352H160c0 17 6.7 33.3 18.7 45.3S207 512 224 512s33.3-6.7 45.3-18.7S288 465 288 448"/>',

View File

@@ -1916,9 +1916,13 @@ If a class is named default it will be assigned to all classes without specific
## Basic support for fontawesome
It is possible to add icons from fontawesome.
It is possible to add icons from fontawesome and registered icon pack.
The icons are accessed via the syntax fa:#icon class name#.
Mermaid supports icons from registered icon packs. Follow the instructions provided [here](../config/icons.md) to register your icon packs.
The registered icons can be accessed via the syntax #registered icon pack name#:#icon name#.
The fontawesome icons are accessed via the syntax fa:#icon class name#.
```mermaid-example
flowchart TD

View File

@@ -68,10 +68,6 @@
},
"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

@@ -7,7 +7,7 @@ import { getConfig } from '../diagram-api/diagramAPI.js';
import { evaluate } from '../diagrams/common/common.js';
import { getSubGraphTitleMargins } from '../utils/subGraphTitleMargins.js';
const rect = (parent, node) => {
const rect = async (parent, node) => {
log.info('Creating subgraph rect for ', node.id, node);
const siteConfig = getConfig();
@@ -31,7 +31,9 @@ const rect = (parent, node) => {
const text =
node.labelType === 'markdown'
? createText(label, node.labelText, { style: node.labelStyle, useHtmlLabels }, siteConfig)
: label.node().appendChild(createLabel(node.labelText, node.labelStyle, undefined, true));
: label
.node()
.appendChild(await createLabel(node.labelText, node.labelStyle, undefined, true));
// Get the size of the label
let bbox = text.getBBox();
@@ -129,7 +131,7 @@ const noteGroup = (parent, node) => {
return shapeSvg;
};
const roundedWithTitle = (parent, node) => {
const roundedWithTitle = async (parent, node) => {
const siteConfig = getConfig();
// Add outer g element
@@ -144,7 +146,7 @@ const roundedWithTitle = (parent, node) => {
const text = label
.node()
.appendChild(createLabel(node.labelText, node.labelStyle, undefined, true));
.appendChild(await createLabel(node.labelText, node.labelStyle, undefined, true));
// Get the size of the label
let bbox = text.getBBox();
@@ -236,13 +238,13 @@ const shapes = { rect, roundedWithTitle, noteGroup, divider };
let clusterElems = {};
export const insertCluster = (elem, node) => {
export const insertCluster = async (elem, node) => {
log.trace('Inserting cluster');
const shape = node.shape || 'rect';
clusterElems[node.id] = shapes[shape](elem, node);
clusterElems[node.id] = await shapes[shape](elem, node);
};
export const getClusterTitleWidth = (elem, node) => {
const label = createLabel(node.labelText, node.labelStyle, undefined, true);
export const getClusterTitleWidth = async (elem, node) => {
const label = await createLabel(node.labelText, node.labelStyle, undefined, true);
elem.node().appendChild(label);
const width = label.getBBox().width;
elem.node().removeChild(label);

View File

@@ -44,7 +44,7 @@ function addHtmlLabel(node) {
* @param isNode
* @deprecated svg-util/createText instead
*/
const createLabel = (_vertexText, style, isTitle, isNode) => {
const createLabel = async (_vertexText, style, isTitle, isNode) => {
let vertexText = _vertexText || '';
if (typeof vertexText === 'object') {
vertexText = vertexText[0];
@@ -53,9 +53,10 @@ const createLabel = (_vertexText, style, isTitle, isNode) => {
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
vertexText = vertexText.replace(/\\n|\n/g, '<br />');
log.debug('vertexText' + vertexText);
const label = await replaceIconSubstring(decodeEntities(vertexText));
const node = {
isNode,
label: replaceIconSubstring(decodeEntities(vertexText)),
label,
labelStyle: style.replace('fill:', 'color:'),
};
let vertexNode = addHtmlLabel(node);

View File

@@ -17,7 +17,7 @@ export const clear = () => {
terminalLabels = {};
};
export const insertEdgeLabel = (elem, edge) => {
export const insertEdgeLabel = async (elem, edge) => {
const config = getConfig();
const useHtmlLabels = evaluate(config.flowchart.htmlLabels);
// Create the actual text element
@@ -33,7 +33,7 @@ export const insertEdgeLabel = (elem, edge) => {
},
config
)
: createLabel(edge.label, edge.labelStyle);
: await createLabel(edge.label, edge.labelStyle);
// Create outer g, edgeLabel, this will be positioned after graph layout
const edgeLabel = elem.insert('g').attr('class', 'edgeLabel');
@@ -63,7 +63,7 @@ export const insertEdgeLabel = (elem, edge) => {
let fo;
if (edge.startLabelLeft) {
// Create the actual text element
const startLabelElement = createLabel(edge.startLabelLeft, edge.labelStyle);
const startLabelElement = await createLabel(edge.startLabelLeft, edge.labelStyle);
const startEdgeLabelLeft = elem.insert('g').attr('class', 'edgeTerminals');
const inner = startEdgeLabelLeft.insert('g').attr('class', 'inner');
fo = inner.node().appendChild(startLabelElement);
@@ -77,7 +77,7 @@ export const insertEdgeLabel = (elem, edge) => {
}
if (edge.startLabelRight) {
// Create the actual text element
const startLabelElement = createLabel(edge.startLabelRight, edge.labelStyle);
const startLabelElement = await createLabel(edge.startLabelRight, edge.labelStyle);
const startEdgeLabelRight = elem.insert('g').attr('class', 'edgeTerminals');
const inner = startEdgeLabelRight.insert('g').attr('class', 'inner');
fo = startEdgeLabelRight.node().appendChild(startLabelElement);
@@ -93,7 +93,7 @@ export const insertEdgeLabel = (elem, edge) => {
}
if (edge.endLabelLeft) {
// Create the actual text element
const endLabelElement = createLabel(edge.endLabelLeft, edge.labelStyle);
const endLabelElement = await createLabel(edge.endLabelLeft, edge.labelStyle);
const endEdgeLabelLeft = elem.insert('g').attr('class', 'edgeTerminals');
const inner = endEdgeLabelLeft.insert('g').attr('class', 'inner');
fo = inner.node().appendChild(endLabelElement);
@@ -110,7 +110,7 @@ export const insertEdgeLabel = (elem, edge) => {
}
if (edge.endLabelRight) {
// Create the actual text element
const endLabelElement = createLabel(edge.endLabelRight, edge.labelStyle);
const endLabelElement = await createLabel(edge.endLabelRight, edge.labelStyle);
const endEdgeLabelRight = elem.insert('g').attr('class', 'edgeTerminals');
const inner = endEdgeLabelRight.insert('g').attr('class', 'inner');

View File

@@ -120,7 +120,7 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit
// Move the nodes to the correct place
let diff = 0;
const { subGraphTitleTotalMargin } = getSubGraphTitleMargins(siteConfig);
sortNodesByHierarchy(graph).forEach(function (v) {
for (const v of sortNodesByHierarchy(graph)) {
const node = graph.node(v);
log.info('Position ' + v + ': ' + JSON.stringify(graph.node(v)));
log.info(
@@ -141,14 +141,14 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit
// A cluster in the non-recursive way
// positionCluster(node);
node.height += subGraphTitleTotalMargin;
insertCluster(clusters, node);
await insertCluster(clusters, node);
clusterDb[node.id].node = node;
} else {
node.y += subGraphTitleTotalMargin / 2;
positionNode(node);
}
}
});
}
// Move the edge labels to the correct place after layout
graph.edges().forEach(function (e) {

View File

@@ -553,7 +553,7 @@ function applyNodePropertyBorders(rect, borders, totalWidth, totalHeight) {
rect.attr('stroke-dasharray', strokeDashArray.join(' '));
}
const rectWithTitle = (parent, node) => {
const rectWithTitle = async (parent, node) => {
// const { shapeSvg, bbox, halfPadding } = labelHelper(parent, node, 'node ' + node.classes);
let classes;
@@ -586,7 +586,7 @@ const rectWithTitle = (parent, node) => {
}
log.info('Label text abc79', title, text2, typeof text2 === 'object');
const text = label.node().appendChild(createLabel(title, node.labelStyle, true, true));
const text = label.node().appendChild(await createLabel(title, node.labelStyle, true, true));
let bbox = { width: 0, height: 0 };
if (evaluate(getConfig().flowchart.htmlLabels)) {
const div = text.children[0];
@@ -601,7 +601,12 @@ const rectWithTitle = (parent, node) => {
const descr = label
.node()
.appendChild(
createLabel(textRows.join ? textRows.join('<br/>') : textRows, node.labelStyle, true, true)
await createLabel(
textRows.join ? textRows.join('<br/>') : textRows,
node.labelStyle,
true,
true
)
);
if (evaluate(getConfig().flowchart.htmlLabels)) {
@@ -876,7 +881,7 @@ const end = (parent, node) => {
return shapeSvg;
};
const class_box = (parent, node) => {
const class_box = async (parent, node) => {
const halfPadding = node.padding / 2;
const rowPadding = 4;
const lineHeight = 8;
@@ -910,7 +915,7 @@ const class_box = (parent, node) => {
: '';
const interfaceLabel = labelContainer
.node()
.appendChild(createLabel(interfaceLabelText, node.labelStyle, true, true));
.appendChild(await createLabel(interfaceLabelText, node.labelStyle, true, true));
let interfaceBBox = interfaceLabel.getBBox();
if (evaluate(getConfig().flowchart.htmlLabels)) {
const div = interfaceLabel.children[0];
@@ -935,7 +940,7 @@ const class_box = (parent, node) => {
}
const classTitleLabel = labelContainer
.node()
.appendChild(createLabel(classTitleString, node.labelStyle, true, true));
.appendChild(await createLabel(classTitleString, node.labelStyle, true, true));
select(classTitleLabel).attr('class', 'classTitle');
let classTitleBBox = classTitleLabel.getBBox();
if (evaluate(getConfig().flowchart.htmlLabels)) {
@@ -950,7 +955,7 @@ const class_box = (parent, node) => {
maxWidth = classTitleBBox.width;
}
const classAttributes = [];
node.classData.members.forEach((member) => {
node.classData.members.forEach(async (member) => {
const parsedInfo = member.getDisplayDetails();
let parsedText = parsedInfo.displayText;
if (getConfig().flowchart.htmlLabels) {
@@ -959,7 +964,7 @@ const class_box = (parent, node) => {
const lbl = labelContainer
.node()
.appendChild(
createLabel(
await createLabel(
parsedText,
parsedInfo.cssStyle ? parsedInfo.cssStyle : node.labelStyle,
true,
@@ -984,7 +989,7 @@ const class_box = (parent, node) => {
maxHeight += lineHeight;
const classMethods = [];
node.classData.methods.forEach((member) => {
node.classData.methods.forEach(async (member) => {
const parsedInfo = member.getDisplayDetails();
let displayText = parsedInfo.displayText;
if (getConfig().flowchart.htmlLabels) {
@@ -993,7 +998,7 @@ const class_box = (parent, node) => {
const lbl = labelContainer
.node()
.appendChild(
createLabel(
await createLabel(
displayText,
parsedInfo.cssStyle ? parsedInfo.cssStyle : node.labelStyle,
true,

View File

@@ -48,7 +48,12 @@ export const labelHelper = async (parent, node, _classes, isNode) => {
);
} else {
text = textNode.appendChild(
createLabel(sanitizeText(decodeEntities(labelText), config), node.labelStyle, false, isNode)
await createLabel(
sanitizeText(decodeEntities(labelText), config),
node.labelStyle,
false,
isNode
)
);
}
// Get the size of the label

View File

@@ -142,11 +142,20 @@ const getStyles = (options: BlockChartStyleOptions) =>
font-size: 18px;
fill: ${options.textColor};
}
.node .svg-inline--fa path {
.node label-icon path {
fill: currentColor;
stroke: revert;
stroke-width: revert;
}
/**
* These are copied from font-awesome.css
*/
.label-icon {
display: inline-block;
height: 1em;
overflow: visible;
vertical-align: -0.125em;
}
`;
export default getStyles;

View File

@@ -157,10 +157,19 @@ g.classGroup line {
font-size: 18px;
fill: ${options.textColor};
}
.node .svg-inline--fa path {
.node label-icon path {
fill: currentColor;
stroke: revert;
stroke-width: revert;
}
/**
* These are copied from font-awesome.css
*/
.label-icon {
display: inline-block;
height: 1em;
overflow: visible;
vertical-align: -0.125em;
}
`;

View File

@@ -177,10 +177,19 @@ const getStyles = (options: FlowChartStyleOptions) =>
}
text-align: center;
}
.node .svg-inline--fa path {
.node .label-icon path {
fill: currentColor;
stroke: revert;
stroke-width: revert;
}
/**
* These are copied from font-awesome.css
*/
.label-icon {
display: inline-block;
height: 1em;
overflow: visible;
vertical-align: -0.125em;
}
`;

View File

@@ -105,10 +105,19 @@ const getStyles: DiagramStylesProvider = (options) =>
dominant-baseline: middle;
text-align: center;
}
.node .svg-inline--fa path {
.node label-icon path {
fill: currentColor;
stroke: revert;
stroke-width: revert;
}
/**
* These are copied from font-awesome.css
*/
.label-icon {
display: inline-block;
height: 1em;
overflow: visible;
vertical-align: -0.125em;
}
`;
export default getStyles;

View File

@@ -131,11 +131,20 @@ const getStyles = (options) =>
.actor-5 {
${options.actor5 ? `fill: ${options.actor5}` : ''};
}
.node .svg-inline--fa path {
.node label-icon path {
fill: currentColor;
stroke: revert;
stroke-width: revert;
}
/**
* These are copied from font-awesome.css
*/
.label-icon {
display: inline-block;
height: 1em;
overflow: visible;
vertical-align: -0.125em;
}
`;
export default getStyles;

View File

@@ -1231,9 +1231,13 @@ If a class is named default it will be assigned to all classes without specific
## Basic support for fontawesome
It is possible to add icons from fontawesome.
It is possible to add icons from fontawesome and registered icon pack.
The icons are accessed via the syntax fa:#icon class name#.
Mermaid supports icons from registered icon packs. Follow the instructions provided [here](../config/icons.md) to register your icon packs.
The registered icons can be accessed via the syntax #registered icon pack name#:#icon name#.
The fontawesome icons are accessed via the syntax fa:#icon class name#.
```mermaid-example
flowchart TD

View File

@@ -1,35 +1,57 @@
import { describe, it, expect } from 'vitest';
import { describe, expect, it } 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';
import mermaid from '../mermaid.js';
describe('replaceIconSubstring', () => {
it('converts FontAwesome icon notations to HTML tags', () => {
it('converts FontAwesome icon notations to HTML tags', async () => {
const input = 'This is an icon: fa:fa-user and fab:fa-github';
const output = replaceIconSubstring(input);
const expected = `This is an icon: ${icon(faUser).html.join('')} and ${icon(faGithub).html.join('')}`;
const output = await replaceIconSubstring(input);
const expected = `This is an icon: <i class='fa fa-user'></i> and <i class='fab fa-github'></i>`;
expect(output).toEqual(expected);
});
it('handles strings without FontAwesome icon notations', () => {
it('handles strings without FontAwesome icon notations', async () => {
const input = 'This string has no icons';
const output = replaceIconSubstring(input);
const output = await replaceIconSubstring(input);
expect(output).toEqual(input); // No change expected
});
it('correctly processes multiple FontAwesome icon notations in one string', () => {
it('correctly processes multiple FontAwesome icon notations in one string', async () => {
const input = 'Icons galore: fa:fa-arrow-right, fak:fa-truck, fas:fa-home';
const output = replaceIconSubstring(input);
const expected = `Icons galore: ${icon(faArrowRight).html.join()}, <i class='fak fa-truck'></i>, ${icon(faHome).html.join()}`;
const output = await 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>`;
expect(output).toEqual(expected);
});
it('correctly replaces a very long icon name with the fak prefix', () => {
it('correctly replaces a very long icon name with the fak prefix', async () => {
const input = 'Here is a long icon: fak:fa-truck-driving-long-winding-road in use';
const output = replaceIconSubstring(input);
const output = await replaceIconSubstring(input);
const expected =
"Here is a long icon: <i class='fak fa-truck-driving-long-winding-road'></i> in use";
expect(output).toEqual(expected);
});
it('correctly process the registered icons', async () => {
const staticBellIconPack = {
prefix: 'fa',
icons: {
bell: {
body: '<path fill="currentColor" d="M224 0c-17.7 0-32 14.3-32 32v19.2C119 66 64 130.6 64 208v25.4c0 45.4-15.5 89.5-43.8 124.9L5.3 377c-5.8 7.2-6.9 17.1-2.9 25.4S14.8 416 24 416h400c9.2 0 17.6-5.3 21.6-13.6s2.9-18.2-2.9-25.4l-14.9-18.6c-28.3-35.5-43.8-79.6-43.8-125V208c0-77.4-55-142-128-156.8V32c0-17.7-14.3-32-32-32m0 96c61.9 0 112 50.1 112 112v25.4c0 47.9 13.9 94.6 39.7 134.6H72.3c25.8-40 39.7-86.7 39.7-134.6V208c0-61.9 50.1-112 112-112m64 352H160c0 17 6.7 33.3 18.7 45.3S207 512 224 512s33.3-6.7 45.3-18.7S288 465 288 448"/>',
width: 448,
},
},
width: 512,
height: 512,
};
mermaid.registerIconPacks([
{
name: 'fa',
loader: () => Promise.resolve(staticBellIconPack),
},
]);
const input = 'Icons galore: fa:fa-bell';
const output = await replaceIconSubstring(input);
const expected = staticBellIconPack.icons.bell.body;
expect(output).toContain(expected);
});
});

View File

@@ -1,32 +1,17 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// @ts-nocheck TODO: Fix types
import { getConfig } from '../diagram-api/diagramAPI.js';
import common, { hasKatex, renderKatex } from '../diagrams/common/common.js';
import { select } from 'd3';
import type { MermaidConfig } from '../config.type.js';
import { getConfig } from '../diagram-api/diagramAPI.js';
import type { SVGGroup } from '../diagram-api/types.js';
import common, { hasKatex, renderKatex } from '../diagrams/common/common.js';
import type { D3TSpanElement, D3TextElement } from '../diagrams/common/commonTypes.js';
import { log } from '../logger.js';
import { markdownToHTML, markdownToLines } from '../rendering-util/handle-markdown-text.js';
import { decodeEntities } from '../utils.js';
import { getIconSVG, isIconAvailable } from './icons.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) {
@@ -196,36 +181,35 @@ 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 if they are from pro pack and as svg if they are from free pack
* @returns string with fontawesome icons as svg if the icon is registered otherwise as i tags
*/
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'];
export async 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
const iconRegex = /(fa[bklrs]?):fa-([\w-]+)/g; // cspell: disable-line
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;
}
const matches = [...text.matchAll(iconRegex)];
if (matches.length === 0) {
return text;
}
return faIcon.html.join('');
});
let newText = text;
for (const match of matches) {
const [fullMatch, prefix, iconName] = match;
const registeredIconName = `${prefix}:${iconName}`;
try {
const isIconAvail = await isIconAvailable(registeredIconName);
if (isIconAvail) {
const faIcon = await getIconSVG(registeredIconName, undefined, { class: 'label-icon' });
newText = newText.replace(fullMatch, faIcon);
} else {
newText = newText.replace(fullMatch, `<i class='${fullMatch.replace(':', ' ')}'></i>`);
}
} catch (error) {
log.error(`Error processing ${registeredIconName}:`, error);
}
}
return newText;
}
// 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'
@@ -259,7 +243,7 @@ export const createText = async (
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
const htmlText = markdownToHTML(text, config);
const decodedReplacedText = replaceIconSubstring(decodeEntities(htmlText));
const decodedReplacedText = await replaceIconSubstring(decodeEntities(htmlText));
//for Katex the text could contain escaped characters, \\relax that should be transformed to \relax
const inputForKatex = text.replace(/\\\\/g, '\\');

View File

@@ -85,7 +85,8 @@ export const isIconAvailable = async (iconName: string) => {
export const getIconSVG = async (
iconName: string,
customisations?: IconifyIconCustomisations & { fallbackPrefix?: string }
customisations?: IconifyIconCustomisations & { fallbackPrefix?: string },
extraAttributes?: Record<string, string>
) => {
let iconData: ExtendedIconifyIcon;
try {
@@ -95,6 +96,9 @@ export const getIconSVG = async (
iconData = unknownIcon;
}
const renderData = iconToSVG(iconData, customisations);
const svg = iconToHTML(replaceIDs(renderData.body), renderData.attributes);
const svg = iconToHTML(replaceIDs(renderData.body), {
...renderData.attributes,
...extraAttributes,
});
return svg;
};

72
pnpm-lock.yaml generated
View File

@@ -220,18 +220,6 @@ 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
@@ -2589,26 +2577,6 @@ 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==}
@@ -13246,24 +13214,6 @@ 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':
@@ -13313,7 +13263,7 @@ snapshots:
'@antfu/install-pkg': 0.4.1
'@antfu/utils': 0.7.10
'@iconify/types': 2.0.0
debug: 4.3.7(supports-color@8.1.1)
debug: 4.3.7
kolorist: 1.8.0
local-pkg: 0.5.0
mlly: 1.7.2
@@ -13594,7 +13544,7 @@ snapshots:
'@jridgewell/source-map@0.3.6':
dependencies:
'@jridgewell/gen-mapping': 0.3.5
'@jridgewell/gen-mapping': 0.3.8
'@jridgewell/trace-mapping': 0.3.25
'@jridgewell/sourcemap-codec@1.5.0': {}
@@ -14773,7 +14723,7 @@ snapshots:
'@vueuse/shared': 10.11.1(vue@3.5.11(typescript@5.4.5))
vue-demi: 0.14.10(vue@3.5.11(typescript@5.4.5))
optionalDependencies:
axios: 1.7.7
axios: 1.7.7(debug@4.3.7)
focus-trap: 7.6.0
transitivePeerDependencies:
- '@vue/composition-api'
@@ -15011,7 +14961,7 @@ snapshots:
agent-base@7.1.1:
dependencies:
debug: 4.3.7(supports-color@8.1.1)
debug: 4.3.7
transitivePeerDependencies:
- supports-color
@@ -15222,7 +15172,7 @@ snapshots:
axios@1.7.7:
dependencies:
follow-redirects: 1.15.9(debug@4.4.0)
follow-redirects: 1.15.9(debug@4.3.7)
form-data: 4.0.0
proxy-from-env: 1.1.0
transitivePeerDependencies:
@@ -16436,6 +16386,10 @@ snapshots:
dependencies:
ms: 2.1.2
debug@4.3.7:
dependencies:
ms: 2.1.3
debug@4.3.7(supports-color@8.1.1):
dependencies:
ms: 2.1.3
@@ -17566,7 +17520,7 @@ snapshots:
follow-redirects@1.15.9(debug@4.3.7):
optionalDependencies:
debug: 4.3.7(supports-color@8.1.1)
debug: 4.3.7
follow-redirects@1.15.9(debug@4.4.0):
optionalDependencies:
@@ -18008,7 +17962,7 @@ snapshots:
http-proxy-agent@7.0.2:
dependencies:
agent-base: 7.1.1
debug: 4.3.7(supports-color@8.1.1)
debug: 4.3.7
transitivePeerDependencies:
- supports-color
@@ -18064,7 +18018,7 @@ snapshots:
https-proxy-agent@7.0.5:
dependencies:
agent-base: 7.1.1
debug: 4.3.7(supports-color@8.1.1)
debug: 4.3.7
transitivePeerDependencies:
- supports-color
@@ -21202,7 +21156,7 @@ snapshots:
arg: 5.0.2
bluebird: 3.7.2
check-more-types: 2.24.0
debug: 4.3.7(supports-color@8.1.1)
debug: 4.3.7
execa: 5.1.1
lazy-ass: 1.6.0
ps-tree: 1.2.0