diff --git a/docs/syntax/flowchart.md b/docs/syntax/flowchart.md index 83676b0e41..8edb5e208d 100644 --- a/docs/syntax/flowchart.md +++ b/docs/syntax/flowchart.md @@ -1178,6 +1178,36 @@ Adding this snippet in the `` would add support for Font Awesome v6.5.1 /> ``` +### Custom icons + +It is possible to use custom icons served from Font Awesome as long as the website imports the corresponding kit. + +Note that this is currently a paid feature from Font Awesome. + +For custom icons, you need to use the `fak` prefix. + +**Example** + +``` +flowchart TD + B[fa:fa-twitter] %% standard icon + B-->E(fak:fa-custom-icon-name) %% custom icon +``` + +And trying to render it + +```mermaid-example +flowchart TD + B["fa:fa-twitter for peace"] + B-->C["fab:fa-truck-bold a custom icon"] +``` + +```mermaid +flowchart TD + B["fa:fa-twitter for peace"] + B-->C["fab:fa-truck-bold a custom icon"] +``` + ## Graph declarations with spaces between vertices and link and without semicolon - In graph declarations, the statements also can now end without a semicolon. After release 0.2.16, ending a graph statement with semicolon is just optional. So the below graph declaration is also valid along with the old declarations of the graph. diff --git a/packages/mermaid/src/dagre-wrapper/createLabel.js b/packages/mermaid/src/dagre-wrapper/createLabel.js index 22496a2509..f49d65f251 100644 --- a/packages/mermaid/src/dagre-wrapper/createLabel.js +++ b/packages/mermaid/src/dagre-wrapper/createLabel.js @@ -3,6 +3,7 @@ import { log } from '../logger.js'; import { getConfig } from '../diagram-api/diagramAPI.js'; import { evaluate } from '../diagrams/common/common.js'; import { decodeEntities } from '../utils.js'; +import { replaceIconSubstring } from '../rendering-util/createText.js'; /** * @param dom @@ -59,10 +60,7 @@ const createLabel = (_vertexText, style, isTitle, isNode) => { log.debug('vertexText' + vertexText); const node = { isNode, - label: decodeEntities(vertexText).replace( - /fa[blrs]?:fa-[\w-]+/g, // cspell: disable-line - (s) => `` - ), + label: replaceIconSubstring(decodeEntities(vertexText)), labelStyle: style.replace('fill:', 'color:'), }; let vertexNode = addHtmlLabel(node); diff --git a/packages/mermaid/src/diagrams/flowchart/flowRenderer.js b/packages/mermaid/src/diagrams/flowchart/flowRenderer.js index c9e152012d..b2e6bcc9cb 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowRenderer.js +++ b/packages/mermaid/src/diagrams/flowchart/flowRenderer.js @@ -9,6 +9,7 @@ import common, { evaluate, renderKatex } from '../common/common.js'; import { interpolateToCurve, getStylesFromArray } from '../../utils.js'; import { setupGraphViewbox } from '../../setupGraphViewbox.js'; import flowChartShapes from './flowChartShapes.js'; +import { replaceIconSubstring } from '../../rendering-util/createText.js'; const conf = {}; export const setConf = function (cnf) { @@ -56,14 +57,9 @@ export const addVertices = async function (vert, g, svgId, root, _doc, diagObj) let vertexNode; if (evaluate(getConfig().flowchart.htmlLabels)) { // TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that? + const replacedVertexText = replaceIconSubstring(vertexText); const node = { - label: await renderKatex( - vertexText.replace( - /fa[blrs]?:fa-[\w-]+/g, // cspell:disable-line - (s) => `` - ), - getConfig() - ), + label: await renderKatex(replacedVertexText, getConfig()), }; vertexNode = addHtmlLabel(svg, node).node(); vertexNode.parentNode.removeChild(vertexNode); @@ -242,13 +238,7 @@ export const addEdges = async function (edges, g, diagObj) { edgeData.labelType = 'html'; edgeData.label = `${await renderKatex( - edge.text.replace( - /fa[blrs]?:fa-[\w-]+/g, // cspell:disable-line - (s) => `` - ), - getConfig() - )}`; + }">${await renderKatex(replaceIconSubstring(edge.text), getConfig())}`; } else { edgeData.labelType = 'text'; edgeData.label = edge.text.replace(common.lineBreakRegex, '\n'); diff --git a/packages/mermaid/src/docs/syntax/flowchart.md b/packages/mermaid/src/docs/syntax/flowchart.md index 462a9aecc9..ba0e9ce9e9 100644 --- a/packages/mermaid/src/docs/syntax/flowchart.md +++ b/packages/mermaid/src/docs/syntax/flowchart.md @@ -799,6 +799,30 @@ Adding this snippet in the `` would add support for Font Awesome v6.5.1 /> ``` +### Custom icons + +It is possible to use custom icons served from Font Awesome as long as the website imports the corresponding kit. + +Note that this is currently a paid feature from Font Awesome. + +For custom icons, you need to use the `fak` prefix. + +**Example** + +``` +flowchart TD + B[fa:fa-twitter] %% standard icon + B-->E(fak:fa-custom-icon-name) %% custom icon +``` + +And trying to render it + +```mermaid-example +flowchart TD + B["fa:fa-twitter for peace"] + B-->C["fab:fa-truck-bold a custom icon"] +``` + ## Graph declarations with spaces between vertices and link and without semicolon - In graph declarations, the statements also can now end without a semicolon. After release 0.2.16, ending a graph statement with semicolon is just optional. So the below graph declaration is also valid along with the old declarations of the graph. diff --git a/packages/mermaid/src/rendering-util/createText.spec.ts b/packages/mermaid/src/rendering-util/createText.spec.ts new file mode 100644 index 0000000000..da0505ad8d --- /dev/null +++ b/packages/mermaid/src/rendering-util/createText.spec.ts @@ -0,0 +1,34 @@ +import { describe, it, expect } from 'vitest'; +import { replaceIconSubstring } from './createText.js'; + +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: and "; + expect(output).toEqual(expected); + }); + + it('handles strings without FontAwesome icon notations', () => { + const input = 'This string has no icons'; + const output = replaceIconSubstring(input); + expect(output).toEqual(input); // No change expected + }); + + 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: , , "; + expect(output).toEqual(expected); + }); + + it('correctly replaces a very long icon name with the fak prefix', () => { + const input = 'Here is a long icon: fak:fa-truck-driving-long-winding-road in use'; + const output = replaceIconSubstring(input); + const expected = + "Here is a long icon: in use"; + expect(output).toEqual(expected); + }); +}); diff --git a/packages/mermaid/src/rendering-util/createText.ts b/packages/mermaid/src/rendering-util/createText.ts index 725d65da66..0a7e3bbb09 100644 --- a/packages/mermaid/src/rendering-util/createText.ts +++ b/packages/mermaid/src/rendering-util/createText.ts @@ -168,6 +168,19 @@ 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 + */ +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) => `` + ); +} + // 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' // When not using htmlLabels => to set classes to 'title-row' when isTitle=true otherwise 'title-row' export const createText = ( @@ -189,12 +202,10 @@ export const createText = ( // TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that? const htmlText = markdownToHTML(text, config); + const decodedReplacedText = replaceIconSubstring(decodeEntities(htmlText)); const node = { isNode, - label: decodeEntities(htmlText).replace( - /fa[blrs]?:fa-[\w-]+/g, // cspell: disable-line - (s) => `` - ), + label: decodedReplacedText, labelStyle: style.replace('fill:', 'color:'), }; const vertexNode = addHtmlSpan(el, node, width, classes, addSvgBackground);