Skip to content

Commit

Permalink
Updated docs, added visual regression test
Browse files Browse the repository at this point in the history
  • Loading branch information
saurabhg772244 committed Feb 21, 2025
1 parent 8857e77 commit 16573d9
Show file tree
Hide file tree
Showing 8 changed files with 63 additions and 31 deletions.
2 changes: 1 addition & 1 deletion .changeset/proud-seahorses-wash.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
32 changes: 32 additions & 0 deletions cypress/integration/rendering/flowchart-icon.spec.js
Original file line number Diff line number Diff line change
@@ -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 }
);
});
});
});
4 changes: 4 additions & 0 deletions cypress/platform/e2e.html
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion cypress/platform/viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"/>',
Expand Down
8 changes: 6 additions & 2 deletions docs/syntax/flowchart.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 6 additions & 2 deletions packages/mermaid/src/docs/syntax/flowchart.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions packages/mermaid/src/rendering-util/createText.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ describe('replaceIconSubstring', () => {
it('converts FontAwesome icon notations to HTML tags', async () => {
const input = 'This is an icon: fa:fa-user and fab:fa-github';
const output = await replaceIconSubstring(input);
const expected = `This is an icon: <i class='fa fa-user'></i> and <i class='fa-brands fa-github'></i>`;
const expected = `This is an icon: <i class='fa fa-user'></i> and <i class='fab fa-github'></i>`;
expect(output).toEqual(expected);
});

Expand All @@ -19,7 +19,7 @@ describe('replaceIconSubstring', () => {
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 = await replaceIconSubstring(input);
const expected = `Icons galore: <i class='fa fa-arrow-right'></i>, <i class='fak fa-truck'></i>, <i class='fa-solid fa-home'></i>`;
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);
});

Expand All @@ -33,7 +33,7 @@ describe('replaceIconSubstring', () => {

it('correctly process the registered icons', async () => {
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"/>',
Expand Down
32 changes: 10 additions & 22 deletions packages/mermaid/src/rendering-util/createText.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,19 +181,12 @@ 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 async 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;
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

const matches = [...text.matchAll(iconRegex)];
if (matches.length === 0) {
return text;
Expand All @@ -203,19 +196,14 @@ export async function replaceIconSubstring(text) {

for (const match of matches) {
const [fullMatch, prefix, iconName] = match;
const className = classNameMap[prefix];
const registeredIconName = `${prefix}:${iconName}`;

try {
const isFreeIcon = await isIconAvailable(registeredIconName);
if (!isFreeIcon) {
log.warn(`Icon ${registeredIconName} is a pro icon.`);
newText = newText.replace(fullMatch, `<i class='${className} fa-${iconName}'></i>`);
continue;
}
const faIcon = await getIconSVG(registeredIconName, undefined, { class: 'label-icon' });
if (faIcon) {
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>`);

Check warning

Code scanning / CodeQL

Incomplete HTML attribute sanitization Medium

Cross-site scripting vulnerability as the output of
this final HTML sanitizer step
may contain single quotes when it reaches this attribute definition.
Cross-site scripting vulnerability as the output of
this final HTML sanitizer step
may contain single quotes when it reaches this attribute definition.
}
} catch (error) {
log.error(`Error processing ${registeredIconName}:`, error);
Expand Down

0 comments on commit 16573d9

Please sign in to comment.