mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-08-15 22:39:26 +02:00
Updated docs, added visual regression test
This commit is contained in:
@@ -2,4 +2,4 @@
|
|||||||
'mermaid': patch
|
'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
cypress/integration/rendering/flowchart-icon.spec.js
Normal file
32
cypress/integration/rendering/flowchart-icon.spec.js
Normal 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 }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -6,6 +6,10 @@
|
|||||||
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"
|
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
/>
|
/>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css"
|
||||||
|
/>
|
||||||
<style>
|
<style>
|
||||||
svg {
|
svg {
|
||||||
border: 2px solid darkred;
|
border: 2px solid darkred;
|
||||||
|
@@ -51,7 +51,7 @@ const contentLoaded = async function () {
|
|||||||
mermaid.registerLayoutLoaders(layouts);
|
mermaid.registerLayoutLoaders(layouts);
|
||||||
mermaid.initialize(graphObj.mermaid);
|
mermaid.initialize(graphObj.mermaid);
|
||||||
const staticBellIconPack = {
|
const staticBellIconPack = {
|
||||||
prefix: 'fa6-regular',
|
prefix: 'fa',
|
||||||
icons: {
|
icons: {
|
||||||
bell: {
|
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"/>',
|
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"/>',
|
||||||
|
@@ -1916,9 +1916,13 @@ If a class is named default it will be assigned to all classes without specific
|
|||||||
|
|
||||||
## Basic support for fontawesome
|
## 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
|
```mermaid-example
|
||||||
flowchart TD
|
flowchart TD
|
||||||
|
@@ -1231,9 +1231,13 @@ If a class is named default it will be assigned to all classes without specific
|
|||||||
|
|
||||||
## Basic support for fontawesome
|
## 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
|
```mermaid-example
|
||||||
flowchart TD
|
flowchart TD
|
||||||
|
@@ -6,7 +6,7 @@ describe('replaceIconSubstring', () => {
|
|||||||
it('converts FontAwesome icon notations to HTML tags', async () => {
|
it('converts FontAwesome icon notations to HTML tags', async () => {
|
||||||
const input = 'This is an icon: fa:fa-user and fab:fa-github';
|
const input = 'This is an icon: fa:fa-user and fab:fa-github';
|
||||||
const output = await replaceIconSubstring(input);
|
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);
|
expect(output).toEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ describe('replaceIconSubstring', () => {
|
|||||||
it('correctly processes multiple FontAwesome icon notations in one string', async () => {
|
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 input = 'Icons galore: fa:fa-arrow-right, fak:fa-truck, fas:fa-home';
|
||||||
const output = await replaceIconSubstring(input);
|
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);
|
expect(output).toEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ describe('replaceIconSubstring', () => {
|
|||||||
|
|
||||||
it('correctly process the registered icons', async () => {
|
it('correctly process the registered icons', async () => {
|
||||||
const staticBellIconPack = {
|
const staticBellIconPack = {
|
||||||
prefix: 'fa6-regular',
|
prefix: 'fa',
|
||||||
icons: {
|
icons: {
|
||||||
bell: {
|
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"/>',
|
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"/>',
|
||||||
|
@@ -181,19 +181,12 @@ function updateTextContentAndStyles(tspan: any, wrappedLine: MarkdownWord[]) {
|
|||||||
/**
|
/**
|
||||||
* Convert fontawesome labels into fontawesome icons by using a regex pattern
|
* Convert fontawesome labels into fontawesome icons by using a regex pattern
|
||||||
* @param text - The raw string to convert
|
* @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) {
|
export async function replaceIconSubstring(text: string) {
|
||||||
const iconRegex = /(fas|fab|far|fa|fal|fak|fad):fa-([a-z-]+)/g;
|
// The letters 'bklrs' stand for possible endings of the fontawesome prefix (e.g. 'fab' for brands, 'fak' for fa-kit) // cspell: disable-line
|
||||||
const classNameMap = {
|
const iconRegex = /(fa[bklrs]?):fa-([\w-]+)/g; // cspell: disable-line
|
||||||
fas: 'fa-solid',
|
|
||||||
fab: 'fa-brands',
|
|
||||||
far: 'fa-regular',
|
|
||||||
fa: 'fa',
|
|
||||||
fal: 'fa-light',
|
|
||||||
fad: 'fa-duotone',
|
|
||||||
fak: 'fak',
|
|
||||||
} as const;
|
|
||||||
const matches = [...text.matchAll(iconRegex)];
|
const matches = [...text.matchAll(iconRegex)];
|
||||||
if (matches.length === 0) {
|
if (matches.length === 0) {
|
||||||
return text;
|
return text;
|
||||||
@@ -203,19 +196,14 @@ export async function replaceIconSubstring(text) {
|
|||||||
|
|
||||||
for (const match of matches) {
|
for (const match of matches) {
|
||||||
const [fullMatch, prefix, iconName] = match;
|
const [fullMatch, prefix, iconName] = match;
|
||||||
const className = classNameMap[prefix];
|
|
||||||
const registeredIconName = `${prefix}:${iconName}`;
|
const registeredIconName = `${prefix}:${iconName}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const isFreeIcon = await isIconAvailable(registeredIconName);
|
const isIconAvail = await isIconAvailable(registeredIconName);
|
||||||
if (!isFreeIcon) {
|
if (isIconAvail) {
|
||||||
log.warn(`Icon ${registeredIconName} is a pro icon.`);
|
const faIcon = await getIconSVG(registeredIconName, undefined, { class: 'label-icon' });
|
||||||
newText = newText.replace(fullMatch, `<i class='${className} fa-${iconName}'></i>`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const faIcon = await getIconSVG(registeredIconName, undefined, { class: 'label-icon' });
|
|
||||||
if (faIcon) {
|
|
||||||
newText = newText.replace(fullMatch, faIcon);
|
newText = newText.replace(fullMatch, faIcon);
|
||||||
|
} else {
|
||||||
|
newText = newText.replace(fullMatch, `<i class='${fullMatch.replace(':', ' ')}'></i>`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error processing ${registeredIconName}:`, error);
|
log.error(`Error processing ${registeredIconName}:`, error);
|
||||||
|
Reference in New Issue
Block a user