From a81c3187ba86016384c4ea7c32e6eec2605a47c5 Mon Sep 17 00:00:00 2001 From: saurabhg772244 Date: Tue, 18 Feb 2025 19:53:18 +0530 Subject: [PATCH] removed font awesome packages and used registered icons instead --- packages/mermaid/package.json | 4 - .../mermaid/src/dagre-wrapper/clusters.js | 18 +++-- .../mermaid/src/dagre-wrapper/createLabel.js | 5 +- packages/mermaid/src/dagre-wrapper/edges.js | 12 +-- packages/mermaid/src/dagre-wrapper/index.js | 6 +- packages/mermaid/src/dagre-wrapper/nodes.js | 25 +++--- .../mermaid/src/dagre-wrapper/shapes/util.js | 7 +- packages/mermaid/src/diagrams/block/styles.ts | 11 ++- packages/mermaid/src/diagrams/class/styles.js | 11 ++- .../mermaid/src/diagrams/flowchart/styles.ts | 11 ++- .../mermaid/src/diagrams/kanban/styles.ts | 11 ++- .../src/diagrams/user-journey/styles.js | 11 ++- .../src/rendering-util/createText.spec.ts | 50 ++++++++---- .../mermaid/src/rendering-util/createText.ts | 64 +++++++-------- packages/mermaid/src/rendering-util/icons.ts | 8 +- pnpm-lock.yaml | 79 +++---------------- 16 files changed, 174 insertions(+), 159 deletions(-) diff --git a/packages/mermaid/package.json b/packages/mermaid/package.json index 325985c7c..71abdfdb4 100644 --- a/packages/mermaid/package.json +++ b/packages/mermaid/package.json @@ -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", diff --git a/packages/mermaid/src/dagre-wrapper/clusters.js b/packages/mermaid/src/dagre-wrapper/clusters.js index 2c7746876..4103c48f3 100644 --- a/packages/mermaid/src/dagre-wrapper/clusters.js +++ b/packages/mermaid/src/dagre-wrapper/clusters.js @@ -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); diff --git a/packages/mermaid/src/dagre-wrapper/createLabel.js b/packages/mermaid/src/dagre-wrapper/createLabel.js index d2b59b5a9..467eed260 100644 --- a/packages/mermaid/src/dagre-wrapper/createLabel.js +++ b/packages/mermaid/src/dagre-wrapper/createLabel.js @@ -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, '
'); 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); diff --git a/packages/mermaid/src/dagre-wrapper/edges.js b/packages/mermaid/src/dagre-wrapper/edges.js index 1a72328e8..c1e985fdb 100644 --- a/packages/mermaid/src/dagre-wrapper/edges.js +++ b/packages/mermaid/src/dagre-wrapper/edges.js @@ -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'); diff --git a/packages/mermaid/src/dagre-wrapper/index.js b/packages/mermaid/src/dagre-wrapper/index.js index 86ae7e284..f000dbe01 100644 --- a/packages/mermaid/src/dagre-wrapper/index.js +++ b/packages/mermaid/src/dagre-wrapper/index.js @@ -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) { diff --git a/packages/mermaid/src/dagre-wrapper/nodes.js b/packages/mermaid/src/dagre-wrapper/nodes.js index 2677fd785..24dd5610f 100644 --- a/packages/mermaid/src/dagre-wrapper/nodes.js +++ b/packages/mermaid/src/dagre-wrapper/nodes.js @@ -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('
') : textRows, node.labelStyle, true, true) + await createLabel( + textRows.join ? textRows.join('
') : 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, diff --git a/packages/mermaid/src/dagre-wrapper/shapes/util.js b/packages/mermaid/src/dagre-wrapper/shapes/util.js index 1d0d2d77e..cbe683604 100644 --- a/packages/mermaid/src/dagre-wrapper/shapes/util.js +++ b/packages/mermaid/src/dagre-wrapper/shapes/util.js @@ -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 diff --git a/packages/mermaid/src/diagrams/block/styles.ts b/packages/mermaid/src/diagrams/block/styles.ts index 38f44ae9d..eac1f2d83 100644 --- a/packages/mermaid/src/diagrams/block/styles.ts +++ b/packages/mermaid/src/diagrams/block/styles.ts @@ -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; diff --git a/packages/mermaid/src/diagrams/class/styles.js b/packages/mermaid/src/diagrams/class/styles.js index a2ca1f9dd..c88585ad0 100644 --- a/packages/mermaid/src/diagrams/class/styles.js +++ b/packages/mermaid/src/diagrams/class/styles.js @@ -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; } `; diff --git a/packages/mermaid/src/diagrams/flowchart/styles.ts b/packages/mermaid/src/diagrams/flowchart/styles.ts index 878b97b18..dc10f7917 100644 --- a/packages/mermaid/src/diagrams/flowchart/styles.ts +++ b/packages/mermaid/src/diagrams/flowchart/styles.ts @@ -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; } `; diff --git a/packages/mermaid/src/diagrams/kanban/styles.ts b/packages/mermaid/src/diagrams/kanban/styles.ts index 7150b3cdc..c71b7f873 100644 --- a/packages/mermaid/src/diagrams/kanban/styles.ts +++ b/packages/mermaid/src/diagrams/kanban/styles.ts @@ -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; diff --git a/packages/mermaid/src/diagrams/user-journey/styles.js b/packages/mermaid/src/diagrams/user-journey/styles.js index 0ef15c375..fb3d3be0f 100644 --- a/packages/mermaid/src/diagrams/user-journey/styles.js +++ b/packages/mermaid/src/diagrams/user-journey/styles.js @@ -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; diff --git a/packages/mermaid/src/rendering-util/createText.spec.ts b/packages/mermaid/src/rendering-util/createText.spec.ts index 7229072ce..c99ae2b58 100644 --- a/packages/mermaid/src/rendering-util/createText.spec.ts +++ b/packages/mermaid/src/rendering-util/createText.spec.ts @@ -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: and `; 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()}, , ${icon(faHome).html.join()}`; + const output = await replaceIconSubstring(input); + const expected = `Icons galore: , , `; 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: in use"; expect(output).toEqual(expected); }); + + it('correctly process the registered icons', async () => { + const staticBellIconPack = { + prefix: 'fa6-regular', + icons: { + bell: { + body: '', + 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); + }); }); diff --git a/packages/mermaid/src/rendering-util/createText.ts b/packages/mermaid/src/rendering-util/createText.ts index 3fa01a777..c45255fe2 100644 --- a/packages/mermaid/src/rendering-util/createText.ts +++ b/packages/mermaid/src/rendering-util/createText.ts @@ -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) { @@ -198,7 +183,7 @@ function updateTextContentAndStyles(tspan: any, wrappedLine: MarkdownWord[]) { * @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 */ -export function replaceIconSubstring(text) { +export async function replaceIconSubstring(text) { const iconRegex = /(fas|fab|far|fa|fal|fak|fad):fa-([a-z-]+)/g; const classNameMap = { fas: 'fa-solid', @@ -209,23 +194,34 @@ export function replaceIconSubstring(text) { fad: 'fa-duotone', fak: 'fak', } as const; - const freeIconPack = ['fas', 'fab', 'far', 'fa']; + const matches = [...text.matchAll(iconRegex)]; + if (matches.length === 0) { + return text; + } - return text.replace(iconRegex, (match, prefix, iconName) => { - const isFreeIcon = freeIconPack.includes(prefix); + let newText = text; + + for (const match of matches) { + const [fullMatch, prefix, iconName] = match; const className = classNameMap[prefix]; - if (!isFreeIcon) { - log.warn(`Icon ${prefix}:fa-${iconName} is pro icon.`); - return ``; - } - const faIcon = icon({ prefix: prefix, iconName: iconName }); - if (!faIcon) { - log.warn(`Icon ${prefix}:fa-${iconName} not found.`); - return match; - } + const registeredIconName = `${prefix}:${iconName}`; - return faIcon.html.join(''); - }); + try { + const isFreeIcon = await isIconAvailable(registeredIconName); + if (!isFreeIcon) { + log.warn(`Icon ${registeredIconName} is a pro icon.`); + newText = newText.replace(fullMatch, ``); + continue; + } + const faIcon = await getIconSVG(registeredIconName, undefined, { class: 'label-icon' }); + if (faIcon) { + newText = newText.replace(fullMatch, faIcon); + } + } 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 +255,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, '\\'); diff --git a/packages/mermaid/src/rendering-util/icons.ts b/packages/mermaid/src/rendering-util/icons.ts index 5eef3f7eb..50b1bbeb9 100644 --- a/packages/mermaid/src/rendering-util/icons.ts +++ b/packages/mermaid/src/rendering-util/icons.ts @@ -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 ) => { 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; }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 178fb27ec..17621c1d9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,7 +30,7 @@ importers: version: 8.14.4(eslint@9.12.0(jiti@1.21.6)) '@cypress/code-coverage': specifier: ^3.12.30 - version: 3.13.4(@babel/core@7.25.7)(@babel/preset-env@7.26.7(@babel/core@7.25.7))(babel-loader@9.2.1(@babel/core@7.25.7)(webpack@5.95.0(esbuild@0.21.5)))(cypress@13.15.0)(webpack@5.95.0(esbuild@0.21.5)) + version: 3.13.4(@babel/core@7.25.7)(@babel/preset-env@7.26.8(@babel/core@7.25.7))(babel-loader@9.2.1(@babel/core@7.25.7)(webpack@5.95.0(esbuild@0.21.5)))(cypress@13.15.0)(webpack@5.95.0(esbuild@0.21.5)) '@eslint/js': specifier: ^9.4.0 version: 9.12.0 @@ -217,18 +217,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 @@ -1478,12 +1466,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/preset-env@7.26.7': - resolution: {integrity: sha512-Ycg2tnXwixaXOVb29rana8HNPgLVBof8qqtNQ9LE22IoyZboQbGSxI6ZySMdW3K5nAe6gu35IaJefUJflhUFTQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/preset-env@7.26.8': resolution: {integrity: sha512-um7Sy+2THd697S4zJEfv/U5MHGJzkN2xhtsR3T/SWRbVSic62nbISh51VVfU9JiO/L/Z97QczHTaFVkOU8IzNg==} engines: {node: '>=6.9.0'} @@ -2237,26 +2219,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==} @@ -3932,11 +3894,6 @@ packages: peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - babel-plugin-polyfill-corejs3@0.10.6: - resolution: {integrity: sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - babel-plugin-polyfill-corejs3@0.11.1: resolution: {integrity: sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==} peerDependencies: @@ -11630,7 +11587,7 @@ snapshots: '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.8) '@babel/helper-plugin-utils': 7.26.5 - '@babel/preset-env@7.26.7(@babel/core@7.25.7)': + '@babel/preset-env@7.26.8(@babel/core@7.25.7)': dependencies: '@babel/compat-data': 7.26.8 '@babel/core': 7.25.7 @@ -11698,7 +11655,7 @@ snapshots: '@babel/plugin-transform-unicode-sets-regex': 7.25.9(@babel/core@7.25.7) '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.25.7) babel-plugin-polyfill-corejs2: 0.4.12(@babel/core@7.25.7) - babel-plugin-polyfill-corejs3: 0.10.6(@babel/core@7.25.7) + babel-plugin-polyfill-corejs3: 0.11.1(@babel/core@7.25.7) babel-plugin-polyfill-regenerator: 0.6.3(@babel/core@7.25.7) core-js-compat: 3.40.0 semver: 6.3.1 @@ -12246,11 +12203,11 @@ snapshots: '@cspell/url@8.14.4': {} - '@cypress/code-coverage@3.13.4(@babel/core@7.25.7)(@babel/preset-env@7.26.7(@babel/core@7.25.7))(babel-loader@9.2.1(@babel/core@7.25.7)(webpack@5.95.0(esbuild@0.21.5)))(cypress@13.15.0)(webpack@5.95.0(esbuild@0.21.5))': + '@cypress/code-coverage@3.13.4(@babel/core@7.25.7)(@babel/preset-env@7.26.8(@babel/core@7.25.7))(babel-loader@9.2.1(@babel/core@7.25.7)(webpack@5.95.0(esbuild@0.21.5)))(cypress@13.15.0)(webpack@5.95.0(esbuild@0.21.5))': dependencies: '@babel/core': 7.25.7 - '@babel/preset-env': 7.26.7(@babel/core@7.25.7) - '@cypress/webpack-preprocessor': 6.0.2(@babel/core@7.25.7)(@babel/preset-env@7.26.7(@babel/core@7.25.7))(babel-loader@9.2.1(@babel/core@7.25.7)(webpack@5.95.0(esbuild@0.21.5)))(webpack@5.95.0(esbuild@0.21.5)) + '@babel/preset-env': 7.26.8(@babel/core@7.25.7) + '@cypress/webpack-preprocessor': 6.0.2(@babel/core@7.25.7)(@babel/preset-env@7.26.8(@babel/core@7.25.7))(babel-loader@9.2.1(@babel/core@7.25.7)(webpack@5.95.0(esbuild@0.21.5)))(webpack@5.95.0(esbuild@0.21.5)) babel-loader: 9.2.1(@babel/core@7.25.7)(webpack@5.95.0(esbuild@0.21.5)) chalk: 4.1.2 cypress: 13.15.0 @@ -12286,10 +12243,10 @@ snapshots: tunnel-agent: 0.6.0 uuid: 8.3.2 - '@cypress/webpack-preprocessor@6.0.2(@babel/core@7.25.7)(@babel/preset-env@7.26.7(@babel/core@7.25.7))(babel-loader@9.2.1(@babel/core@7.25.7)(webpack@5.95.0(esbuild@0.21.5)))(webpack@5.95.0(esbuild@0.21.5))': + '@cypress/webpack-preprocessor@6.0.2(@babel/core@7.25.7)(@babel/preset-env@7.26.8(@babel/core@7.25.7))(babel-loader@9.2.1(@babel/core@7.25.7)(webpack@5.95.0(esbuild@0.21.5)))(webpack@5.95.0(esbuild@0.21.5))': dependencies: '@babel/core': 7.25.7 - '@babel/preset-env': 7.26.7(@babel/core@7.25.7) + '@babel/preset-env': 7.26.8(@babel/core@7.25.7) babel-loader: 9.2.1(@babel/core@7.25.7)(webpack@5.95.0(esbuild@0.21.5)) bluebird: 3.7.1 debug: 4.3.7(supports-color@8.1.1) @@ -12565,24 +12522,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': @@ -14619,7 +14558,7 @@ snapshots: transitivePeerDependencies: - supports-color - babel-plugin-polyfill-corejs3@0.10.6(@babel/core@7.25.7): + babel-plugin-polyfill-corejs3@0.11.1(@babel/core@7.25.7): dependencies: '@babel/core': 7.25.7 '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.25.7)