Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/support fa kit custom icons #5430

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions docs/syntax/flowchart.md
Original file line number Diff line number Diff line change
Expand Up @@ -1178,6 +1178,36 @@ Adding this snippet in the `<head>` 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.
Expand Down
6 changes: 2 additions & 4 deletions packages/mermaid/src/dagre-wrapper/createLabel.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) => `<i class='${s.replace(':', ' ')}'></i>`
),
label: replaceIconSubstring(decodeEntities(vertexText)),
labelStyle: style.replace('fill:', 'color:'),
};
let vertexNode = addHtmlLabel(node);
Expand Down
18 changes: 4 additions & 14 deletions packages/mermaid/src/diagrams/flowchart/flowRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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) => `<i class='${s.replace(':', ' ')}'></i>`
),
getConfig()
),
label: await renderKatex(replacedVertexText, getConfig()),
};
vertexNode = addHtmlLabel(svg, node).node();
vertexNode.parentNode.removeChild(vertexNode);
Expand Down Expand Up @@ -242,13 +238,7 @@ export const addEdges = async function (edges, g, diagObj) {
edgeData.labelType = 'html';
edgeData.label = `<span id="L-${linkId}" class="edgeLabel L-${linkNameStart}' L-${linkNameEnd}" style="${
edgeData.labelStyle
}">${await renderKatex(
edge.text.replace(
/fa[blrs]?:fa-[\w-]+/g, // cspell:disable-line
(s) => `<i class='${s.replace(':', ' ')}'></i>`
),
getConfig()
)}</span>`;
}">${await renderKatex(replaceIconSubstring(edge.text), getConfig())}</span>`;
} else {
edgeData.labelType = 'text';
edgeData.label = edge.text.replace(common.lineBreakRegex, '\n');
Expand Down
24 changes: 24 additions & 0 deletions packages/mermaid/src/docs/syntax/flowchart.md
Original file line number Diff line number Diff line change
Expand Up @@ -799,6 +799,30 @@ Adding this snippet in the `<head>` 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.
Expand Down
34 changes: 34 additions & 0 deletions packages/mermaid/src/rendering-util/createText.spec.ts
Original file line number Diff line number Diff line change
@@ -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: <i class='fa fa-user'></i> and <i class='fab fa-github'></i>";
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: <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', () => {
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: <i class='fak fa-truck-driving-long-winding-road'></i> in use";
expect(output).toEqual(expected);
});
});
19 changes: 15 additions & 4 deletions packages/mermaid/src/rendering-util/createText.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => `<i class='${s.replace(':', ' ')}'></i>`
);
}

// 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 = (
Expand All @@ -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) => `<i class='${s.replace(':', ' ')}'></i>`
),
label: decodedReplacedText,
labelStyle: style.replace('fill:', 'color:'),
};
const vertexNode = addHtmlSpan(el, node, width, classes, addSvgBackground);
Expand Down
Loading