mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-11-15 02:04:08 +01:00
Merge pull request #6282 from mermaid-js/saurabh/refactor-fontawesome-icon-usage
Refactor fontawesome icon usage.
This commit is contained in:
5
.changeset/proud-seahorses-wash.md
Normal file
5
.changeset/proud-seahorses-wash.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'mermaid': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
FontAwesome icons can now be embedded as SVGs in flowcharts if they are registered via `mermaid.registerIconPacks`.
|
||||||
@@ -26,6 +26,7 @@ dompurify
|
|||||||
elkjs
|
elkjs
|
||||||
fcose
|
fcose
|
||||||
fontawesome
|
fontawesome
|
||||||
|
Fonticons
|
||||||
Forgejo
|
Forgejo
|
||||||
Foswiki
|
Foswiki
|
||||||
Gitea
|
Gitea
|
||||||
|
|||||||
28
cypress/integration/rendering/flowchart-icon.spec.js
Normal file
28
cypress/integration/rendering/flowchart-icon.spec.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { imgSnapshotTest } from '../../helpers/util.ts';
|
||||||
|
|
||||||
|
const themes = ['default', 'forest', 'dark', 'base', 'neutral'];
|
||||||
|
|
||||||
|
describe('when rendering flowchart with icons', () => {
|
||||||
|
for (const theme of themes) {
|
||||||
|
it(`should render icons from fontawesome library on 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 }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should render registered icons on theme ${theme}`, () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`flowchart TD
|
||||||
|
A("fa:fa-bell Bell")
|
||||||
|
`,
|
||||||
|
{ theme }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -99,7 +99,7 @@ describe('Flowchart v2', () => {
|
|||||||
const style = svg.attr('style');
|
const style = svg.attr('style');
|
||||||
expect(style).to.match(/^max-width: [\d.]+px;$/);
|
expect(style).to.match(/^max-width: [\d.]+px;$/);
|
||||||
const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join(''));
|
const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join(''));
|
||||||
expect(maxWidthValue).to.be.within(417 * 0.95, 417 * 1.05);
|
expect(maxWidthValue).to.be.within(440 * 0.95, 440 * 1.05);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('8: should render a flowchart when useMaxWidth is false', () => {
|
it('8: should render a flowchart when useMaxWidth is false', () => {
|
||||||
@@ -118,7 +118,7 @@ describe('Flowchart v2', () => {
|
|||||||
const width = parseFloat(svg.attr('width'));
|
const width = parseFloat(svg.attr('width'));
|
||||||
// use within because the absolute value can be slightly different depending on the environment ±5%
|
// use within because the absolute value can be slightly different depending on the environment ±5%
|
||||||
// expect(height).to.be.within(446 * 0.95, 446 * 1.05);
|
// expect(height).to.be.within(446 * 0.95, 446 * 1.05);
|
||||||
expect(width).to.be.within(417 * 0.95, 417 * 1.05);
|
expect(width).to.be.within(440 * 0.95, 440 * 1.05);
|
||||||
expect(svg).to.not.have.attr('style');
|
expect(svg).to.not.have.attr('style');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
/>
|
/>
|
||||||
<style>
|
<style>
|
||||||
svg {
|
svg:not(svg svg) {
|
||||||
border: 2px solid darkred;
|
border: 2px solid darkred;
|
||||||
}
|
}
|
||||||
.exClass2 > rect,
|
.exClass2 > rect,
|
||||||
|
|||||||
@@ -14,12 +14,28 @@ function markRendered() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loadFontAwesomeCSS() {
|
||||||
|
const link = document.createElement('link');
|
||||||
|
link.rel = 'stylesheet';
|
||||||
|
link.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css';
|
||||||
|
|
||||||
|
document.head.appendChild(link);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
link.onload = resolve;
|
||||||
|
link.onerror = () => reject(new Error('Failed to load FontAwesome'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ##contentLoaded Callback function that is called when page is loaded. This functions fetches
|
* ##contentLoaded Callback function that is called when page is loaded. This functions fetches
|
||||||
* configuration for mermaid rendering and calls init for rendering the mermaid diagrams on the
|
* configuration for mermaid rendering and calls init for rendering the mermaid diagrams on the
|
||||||
* page.
|
* page.
|
||||||
*/
|
*/
|
||||||
const contentLoaded = async function () {
|
const contentLoaded = async function () {
|
||||||
|
await loadFontAwesomeCSS();
|
||||||
|
await Promise.all(Array.from(document.fonts, (font) => font.load()));
|
||||||
|
|
||||||
let pos = document.location.href.indexOf('?graph=');
|
let pos = document.location.href.indexOf('?graph=');
|
||||||
if (pos > 0) {
|
if (pos > 0) {
|
||||||
pos = pos + 7;
|
pos = pos + 7;
|
||||||
@@ -50,8 +66,13 @@ const contentLoaded = async function () {
|
|||||||
|
|
||||||
mermaid.registerLayoutLoaders(layouts);
|
mermaid.registerLayoutLoaders(layouts);
|
||||||
mermaid.initialize(graphObj.mermaid);
|
mermaid.initialize(graphObj.mermaid);
|
||||||
|
/**
|
||||||
|
* CC-BY-4.0
|
||||||
|
* Copyright (c) Fonticons, Inc. - https://fontawesome.com/license/free
|
||||||
|
* https://fontawesome.com/icons/bell?f=classic&s=regular
|
||||||
|
*/
|
||||||
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"/>',
|
||||||
|
|||||||
@@ -1950,6 +1950,19 @@ flowchart TD
|
|||||||
B-->E(A fa:fa-camera-retro perhaps?)
|
B-->E(A fa:fa-camera-retro perhaps?)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
There are two ways to display these FontAwesome icons:
|
||||||
|
|
||||||
|
### Register FontAwesome icon packs (v\<MERMAID_RELEASE_VERSION>+)
|
||||||
|
|
||||||
|
You can register your own FontAwesome icon pack following the ["Registering icon packs" instructions](../config/icons.md).
|
||||||
|
|
||||||
|
Supported prefixes: `fa`, `fab`, `fas`, `far`, `fal`, `fad`.
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
> Note that it will fall back to FontAwesome CSS if FontAwesome packs are not registered.
|
||||||
|
|
||||||
|
### Register FontAwesome CSS
|
||||||
|
|
||||||
Mermaid supports Font Awesome if the CSS is included on the website.
|
Mermaid supports Font Awesome if the CSS is included on the website.
|
||||||
Mermaid does not have any restriction on the version of Font Awesome that can be used.
|
Mermaid does not have any restriction on the version of Font Awesome that can be used.
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { getConfig } from '../diagram-api/diagramAPI.js';
|
|||||||
import { evaluate } from '../diagrams/common/common.js';
|
import { evaluate } from '../diagrams/common/common.js';
|
||||||
import { getSubGraphTitleMargins } from '../utils/subGraphTitleMargins.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);
|
log.info('Creating subgraph rect for ', node.id, node);
|
||||||
const siteConfig = getConfig();
|
const siteConfig = getConfig();
|
||||||
|
|
||||||
@@ -31,7 +31,9 @@ const rect = (parent, node) => {
|
|||||||
const text =
|
const text =
|
||||||
node.labelType === 'markdown'
|
node.labelType === 'markdown'
|
||||||
? createText(label, node.labelText, { style: node.labelStyle, useHtmlLabels }, siteConfig)
|
? 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
|
// Get the size of the label
|
||||||
let bbox = text.getBBox();
|
let bbox = text.getBBox();
|
||||||
@@ -129,7 +131,7 @@ const noteGroup = (parent, node) => {
|
|||||||
|
|
||||||
return shapeSvg;
|
return shapeSvg;
|
||||||
};
|
};
|
||||||
const roundedWithTitle = (parent, node) => {
|
const roundedWithTitle = async (parent, node) => {
|
||||||
const siteConfig = getConfig();
|
const siteConfig = getConfig();
|
||||||
|
|
||||||
// Add outer g element
|
// Add outer g element
|
||||||
@@ -144,7 +146,7 @@ const roundedWithTitle = (parent, node) => {
|
|||||||
|
|
||||||
const text = label
|
const text = label
|
||||||
.node()
|
.node()
|
||||||
.appendChild(createLabel(node.labelText, node.labelStyle, undefined, true));
|
.appendChild(await createLabel(node.labelText, node.labelStyle, undefined, true));
|
||||||
|
|
||||||
// Get the size of the label
|
// Get the size of the label
|
||||||
let bbox = text.getBBox();
|
let bbox = text.getBBox();
|
||||||
@@ -236,13 +238,13 @@ const shapes = { rect, roundedWithTitle, noteGroup, divider };
|
|||||||
|
|
||||||
let clusterElems = {};
|
let clusterElems = {};
|
||||||
|
|
||||||
export const insertCluster = (elem, node) => {
|
export const insertCluster = async (elem, node) => {
|
||||||
log.trace('Inserting cluster');
|
log.trace('Inserting cluster');
|
||||||
const shape = node.shape || 'rect';
|
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) => {
|
export const getClusterTitleWidth = async (elem, node) => {
|
||||||
const label = createLabel(node.labelText, node.labelStyle, undefined, true);
|
const label = await createLabel(node.labelText, node.labelStyle, undefined, true);
|
||||||
elem.node().appendChild(label);
|
elem.node().appendChild(label);
|
||||||
const width = label.getBBox().width;
|
const width = label.getBBox().width;
|
||||||
elem.node().removeChild(label);
|
elem.node().removeChild(label);
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ function addHtmlLabel(node) {
|
|||||||
* @param isNode
|
* @param isNode
|
||||||
* @deprecated svg-util/createText instead
|
* @deprecated svg-util/createText instead
|
||||||
*/
|
*/
|
||||||
const createLabel = (_vertexText, style, isTitle, isNode) => {
|
const createLabel = async (_vertexText, style, isTitle, isNode) => {
|
||||||
let vertexText = _vertexText || '';
|
let vertexText = _vertexText || '';
|
||||||
if (typeof vertexText === 'object') {
|
if (typeof vertexText === 'object') {
|
||||||
vertexText = vertexText[0];
|
vertexText = vertexText[0];
|
||||||
@@ -53,9 +53,10 @@ const createLabel = (_vertexText, style, isTitle, isNode) => {
|
|||||||
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
|
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
|
||||||
vertexText = vertexText.replace(/\\n|\n/g, '<br />');
|
vertexText = vertexText.replace(/\\n|\n/g, '<br />');
|
||||||
log.debug('vertexText' + vertexText);
|
log.debug('vertexText' + vertexText);
|
||||||
|
const label = await replaceIconSubstring(decodeEntities(vertexText));
|
||||||
const node = {
|
const node = {
|
||||||
isNode,
|
isNode,
|
||||||
label: replaceIconSubstring(decodeEntities(vertexText)),
|
label,
|
||||||
labelStyle: style.replace('fill:', 'color:'),
|
labelStyle: style.replace('fill:', 'color:'),
|
||||||
};
|
};
|
||||||
let vertexNode = addHtmlLabel(node);
|
let vertexNode = addHtmlLabel(node);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export const clear = () => {
|
|||||||
terminalLabels = {};
|
terminalLabels = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const insertEdgeLabel = (elem, edge) => {
|
export const insertEdgeLabel = async (elem, edge) => {
|
||||||
const config = getConfig();
|
const config = getConfig();
|
||||||
const useHtmlLabels = evaluate(config.flowchart.htmlLabels);
|
const useHtmlLabels = evaluate(config.flowchart.htmlLabels);
|
||||||
// Create the actual text element
|
// Create the actual text element
|
||||||
@@ -33,7 +33,7 @@ export const insertEdgeLabel = (elem, edge) => {
|
|||||||
},
|
},
|
||||||
config
|
config
|
||||||
)
|
)
|
||||||
: createLabel(edge.label, edge.labelStyle);
|
: await createLabel(edge.label, edge.labelStyle);
|
||||||
|
|
||||||
// Create outer g, edgeLabel, this will be positioned after graph layout
|
// Create outer g, edgeLabel, this will be positioned after graph layout
|
||||||
const edgeLabel = elem.insert('g').attr('class', 'edgeLabel');
|
const edgeLabel = elem.insert('g').attr('class', 'edgeLabel');
|
||||||
@@ -63,7 +63,7 @@ export const insertEdgeLabel = (elem, edge) => {
|
|||||||
let fo;
|
let fo;
|
||||||
if (edge.startLabelLeft) {
|
if (edge.startLabelLeft) {
|
||||||
// Create the actual text element
|
// 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 startEdgeLabelLeft = elem.insert('g').attr('class', 'edgeTerminals');
|
||||||
const inner = startEdgeLabelLeft.insert('g').attr('class', 'inner');
|
const inner = startEdgeLabelLeft.insert('g').attr('class', 'inner');
|
||||||
fo = inner.node().appendChild(startLabelElement);
|
fo = inner.node().appendChild(startLabelElement);
|
||||||
@@ -77,7 +77,7 @@ export const insertEdgeLabel = (elem, edge) => {
|
|||||||
}
|
}
|
||||||
if (edge.startLabelRight) {
|
if (edge.startLabelRight) {
|
||||||
// Create the actual text element
|
// 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 startEdgeLabelRight = elem.insert('g').attr('class', 'edgeTerminals');
|
||||||
const inner = startEdgeLabelRight.insert('g').attr('class', 'inner');
|
const inner = startEdgeLabelRight.insert('g').attr('class', 'inner');
|
||||||
fo = startEdgeLabelRight.node().appendChild(startLabelElement);
|
fo = startEdgeLabelRight.node().appendChild(startLabelElement);
|
||||||
@@ -93,7 +93,7 @@ export const insertEdgeLabel = (elem, edge) => {
|
|||||||
}
|
}
|
||||||
if (edge.endLabelLeft) {
|
if (edge.endLabelLeft) {
|
||||||
// Create the actual text element
|
// 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 endEdgeLabelLeft = elem.insert('g').attr('class', 'edgeTerminals');
|
||||||
const inner = endEdgeLabelLeft.insert('g').attr('class', 'inner');
|
const inner = endEdgeLabelLeft.insert('g').attr('class', 'inner');
|
||||||
fo = inner.node().appendChild(endLabelElement);
|
fo = inner.node().appendChild(endLabelElement);
|
||||||
@@ -110,7 +110,7 @@ export const insertEdgeLabel = (elem, edge) => {
|
|||||||
}
|
}
|
||||||
if (edge.endLabelRight) {
|
if (edge.endLabelRight) {
|
||||||
// Create the actual text element
|
// 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 endEdgeLabelRight = elem.insert('g').attr('class', 'edgeTerminals');
|
||||||
const inner = endEdgeLabelRight.insert('g').attr('class', 'inner');
|
const inner = endEdgeLabelRight.insert('g').attr('class', 'inner');
|
||||||
|
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit
|
|||||||
// Move the nodes to the correct place
|
// Move the nodes to the correct place
|
||||||
let diff = 0;
|
let diff = 0;
|
||||||
const { subGraphTitleTotalMargin } = getSubGraphTitleMargins(siteConfig);
|
const { subGraphTitleTotalMargin } = getSubGraphTitleMargins(siteConfig);
|
||||||
sortNodesByHierarchy(graph).forEach(function (v) {
|
for (const v of sortNodesByHierarchy(graph)) {
|
||||||
const node = graph.node(v);
|
const node = graph.node(v);
|
||||||
log.info('Position ' + v + ': ' + JSON.stringify(graph.node(v)));
|
log.info('Position ' + v + ': ' + JSON.stringify(graph.node(v)));
|
||||||
log.info(
|
log.info(
|
||||||
@@ -141,14 +141,14 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit
|
|||||||
// A cluster in the non-recursive way
|
// A cluster in the non-recursive way
|
||||||
// positionCluster(node);
|
// positionCluster(node);
|
||||||
node.height += subGraphTitleTotalMargin;
|
node.height += subGraphTitleTotalMargin;
|
||||||
insertCluster(clusters, node);
|
await insertCluster(clusters, node);
|
||||||
clusterDb[node.id].node = node;
|
clusterDb[node.id].node = node;
|
||||||
} else {
|
} else {
|
||||||
node.y += subGraphTitleTotalMargin / 2;
|
node.y += subGraphTitleTotalMargin / 2;
|
||||||
positionNode(node);
|
positionNode(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
// Move the edge labels to the correct place after layout
|
// Move the edge labels to the correct place after layout
|
||||||
graph.edges().forEach(function (e) {
|
graph.edges().forEach(function (e) {
|
||||||
|
|||||||
@@ -553,7 +553,7 @@ function applyNodePropertyBorders(rect, borders, totalWidth, totalHeight) {
|
|||||||
rect.attr('stroke-dasharray', strokeDashArray.join(' '));
|
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);
|
// const { shapeSvg, bbox, halfPadding } = labelHelper(parent, node, 'node ' + node.classes);
|
||||||
|
|
||||||
let classes;
|
let classes;
|
||||||
@@ -586,7 +586,7 @@ const rectWithTitle = (parent, node) => {
|
|||||||
}
|
}
|
||||||
log.info('Label text abc79', title, text2, typeof text2 === 'object');
|
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 };
|
let bbox = { width: 0, height: 0 };
|
||||||
if (evaluate(getConfig().flowchart.htmlLabels)) {
|
if (evaluate(getConfig().flowchart.htmlLabels)) {
|
||||||
const div = text.children[0];
|
const div = text.children[0];
|
||||||
@@ -601,7 +601,12 @@ const rectWithTitle = (parent, node) => {
|
|||||||
const descr = label
|
const descr = label
|
||||||
.node()
|
.node()
|
||||||
.appendChild(
|
.appendChild(
|
||||||
createLabel(textRows.join ? textRows.join('<br/>') : textRows, node.labelStyle, true, true)
|
await createLabel(
|
||||||
|
textRows.join ? textRows.join('<br/>') : textRows,
|
||||||
|
node.labelStyle,
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (evaluate(getConfig().flowchart.htmlLabels)) {
|
if (evaluate(getConfig().flowchart.htmlLabels)) {
|
||||||
@@ -876,7 +881,7 @@ const end = (parent, node) => {
|
|||||||
return shapeSvg;
|
return shapeSvg;
|
||||||
};
|
};
|
||||||
|
|
||||||
const class_box = (parent, node) => {
|
const class_box = async (parent, node) => {
|
||||||
const halfPadding = node.padding / 2;
|
const halfPadding = node.padding / 2;
|
||||||
const rowPadding = 4;
|
const rowPadding = 4;
|
||||||
const lineHeight = 8;
|
const lineHeight = 8;
|
||||||
@@ -910,7 +915,7 @@ const class_box = (parent, node) => {
|
|||||||
: '';
|
: '';
|
||||||
const interfaceLabel = labelContainer
|
const interfaceLabel = labelContainer
|
||||||
.node()
|
.node()
|
||||||
.appendChild(createLabel(interfaceLabelText, node.labelStyle, true, true));
|
.appendChild(await createLabel(interfaceLabelText, node.labelStyle, true, true));
|
||||||
let interfaceBBox = interfaceLabel.getBBox();
|
let interfaceBBox = interfaceLabel.getBBox();
|
||||||
if (evaluate(getConfig().flowchart.htmlLabels)) {
|
if (evaluate(getConfig().flowchart.htmlLabels)) {
|
||||||
const div = interfaceLabel.children[0];
|
const div = interfaceLabel.children[0];
|
||||||
@@ -935,7 +940,7 @@ const class_box = (parent, node) => {
|
|||||||
}
|
}
|
||||||
const classTitleLabel = labelContainer
|
const classTitleLabel = labelContainer
|
||||||
.node()
|
.node()
|
||||||
.appendChild(createLabel(classTitleString, node.labelStyle, true, true));
|
.appendChild(await createLabel(classTitleString, node.labelStyle, true, true));
|
||||||
select(classTitleLabel).attr('class', 'classTitle');
|
select(classTitleLabel).attr('class', 'classTitle');
|
||||||
let classTitleBBox = classTitleLabel.getBBox();
|
let classTitleBBox = classTitleLabel.getBBox();
|
||||||
if (evaluate(getConfig().flowchart.htmlLabels)) {
|
if (evaluate(getConfig().flowchart.htmlLabels)) {
|
||||||
@@ -950,7 +955,7 @@ const class_box = (parent, node) => {
|
|||||||
maxWidth = classTitleBBox.width;
|
maxWidth = classTitleBBox.width;
|
||||||
}
|
}
|
||||||
const classAttributes = [];
|
const classAttributes = [];
|
||||||
node.classData.members.forEach((member) => {
|
node.classData.members.forEach(async (member) => {
|
||||||
const parsedInfo = member.getDisplayDetails();
|
const parsedInfo = member.getDisplayDetails();
|
||||||
let parsedText = parsedInfo.displayText;
|
let parsedText = parsedInfo.displayText;
|
||||||
if (getConfig().flowchart.htmlLabels) {
|
if (getConfig().flowchart.htmlLabels) {
|
||||||
@@ -959,7 +964,7 @@ const class_box = (parent, node) => {
|
|||||||
const lbl = labelContainer
|
const lbl = labelContainer
|
||||||
.node()
|
.node()
|
||||||
.appendChild(
|
.appendChild(
|
||||||
createLabel(
|
await createLabel(
|
||||||
parsedText,
|
parsedText,
|
||||||
parsedInfo.cssStyle ? parsedInfo.cssStyle : node.labelStyle,
|
parsedInfo.cssStyle ? parsedInfo.cssStyle : node.labelStyle,
|
||||||
true,
|
true,
|
||||||
@@ -984,7 +989,7 @@ const class_box = (parent, node) => {
|
|||||||
maxHeight += lineHeight;
|
maxHeight += lineHeight;
|
||||||
|
|
||||||
const classMethods = [];
|
const classMethods = [];
|
||||||
node.classData.methods.forEach((member) => {
|
node.classData.methods.forEach(async (member) => {
|
||||||
const parsedInfo = member.getDisplayDetails();
|
const parsedInfo = member.getDisplayDetails();
|
||||||
let displayText = parsedInfo.displayText;
|
let displayText = parsedInfo.displayText;
|
||||||
if (getConfig().flowchart.htmlLabels) {
|
if (getConfig().flowchart.htmlLabels) {
|
||||||
@@ -993,7 +998,7 @@ const class_box = (parent, node) => {
|
|||||||
const lbl = labelContainer
|
const lbl = labelContainer
|
||||||
.node()
|
.node()
|
||||||
.appendChild(
|
.appendChild(
|
||||||
createLabel(
|
await createLabel(
|
||||||
displayText,
|
displayText,
|
||||||
parsedInfo.cssStyle ? parsedInfo.cssStyle : node.labelStyle,
|
parsedInfo.cssStyle ? parsedInfo.cssStyle : node.labelStyle,
|
||||||
true,
|
true,
|
||||||
|
|||||||
@@ -48,7 +48,12 @@ export const labelHelper = async (parent, node, _classes, isNode) => {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
text = textNode.appendChild(
|
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
|
// Get the size of the label
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import * as khroma from 'khroma';
|
import * as khroma from 'khroma';
|
||||||
|
import { getIconStyles } from '../globalStyles.js';
|
||||||
|
|
||||||
/** Returns the styles given options */
|
/** Returns the styles given options */
|
||||||
export interface BlockChartStyleOptions {
|
export interface BlockChartStyleOptions {
|
||||||
@@ -142,6 +143,7 @@ const getStyles = (options: BlockChartStyleOptions) =>
|
|||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
fill: ${options.textColor};
|
fill: ${options.textColor};
|
||||||
}
|
}
|
||||||
|
${getIconStyles()}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default getStyles;
|
export default getStyles;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { getIconStyles } from '../globalStyles.js';
|
||||||
|
|
||||||
const getStyles = (options) =>
|
const getStyles = (options) =>
|
||||||
`g.classGroup text {
|
`g.classGroup text {
|
||||||
fill: ${options.nodeBorder || options.classText};
|
fill: ${options.nodeBorder || options.classText};
|
||||||
@@ -157,6 +159,7 @@ g.classGroup line {
|
|||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
fill: ${options.textColor};
|
fill: ${options.textColor};
|
||||||
}
|
}
|
||||||
|
${getIconStyles()}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default getStyles;
|
export default getStyles;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// import khroma from 'khroma';
|
// import khroma from 'khroma';
|
||||||
import * as khroma from 'khroma';
|
import * as khroma from 'khroma';
|
||||||
|
import { getIconStyles } from '../globalStyles.js';
|
||||||
|
|
||||||
/** Returns the styles given options */
|
/** Returns the styles given options */
|
||||||
export interface FlowChartStyleOptions {
|
export interface FlowChartStyleOptions {
|
||||||
@@ -177,6 +178,7 @@ const getStyles = (options: FlowChartStyleOptions) =>
|
|||||||
}
|
}
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
${getIconStyles()}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default getStyles;
|
export default getStyles;
|
||||||
|
|||||||
15
packages/mermaid/src/diagrams/globalStyles.ts
Normal file
15
packages/mermaid/src/diagrams/globalStyles.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
export const getIconStyles = () => `
|
||||||
|
/* Font Awesome icon styling - consolidated */
|
||||||
|
.label-icon {
|
||||||
|
display: inline-block;
|
||||||
|
height: 1em;
|
||||||
|
overflow: visible;
|
||||||
|
vertical-align: -0.125em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node .label-icon path {
|
||||||
|
fill: currentColor;
|
||||||
|
stroke: revert;
|
||||||
|
stroke-width: revert;
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
// @ts-expect-error Incorrect khroma types
|
// @ts-expect-error Incorrect khroma types
|
||||||
import { darken, lighten, isDark } from 'khroma';
|
import { darken, lighten, isDark } from 'khroma';
|
||||||
import type { DiagramStylesProvider } from '../../diagram-api/types.js';
|
import type { DiagramStylesProvider } from '../../diagram-api/types.js';
|
||||||
|
import { getIconStyles } from '../globalStyles.js';
|
||||||
|
|
||||||
const genSections: DiagramStylesProvider = (options) => {
|
const genSections: DiagramStylesProvider = (options) => {
|
||||||
let sections = '';
|
let sections = '';
|
||||||
@@ -105,5 +106,6 @@ const getStyles: DiagramStylesProvider = (options) =>
|
|||||||
dominant-baseline: middle;
|
dominant-baseline: middle;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
${getIconStyles()}
|
||||||
`;
|
`;
|
||||||
export default getStyles;
|
export default getStyles;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { getIconStyles } from '../globalStyles.js';
|
||||||
|
|
||||||
const getStyles = (options) =>
|
const getStyles = (options) =>
|
||||||
`.label {
|
`.label {
|
||||||
font-family: ${options.fontFamily};
|
font-family: ${options.fontFamily};
|
||||||
@@ -131,6 +133,7 @@ const getStyles = (options) =>
|
|||||||
.actor-5 {
|
.actor-5 {
|
||||||
${options.actor5 ? `fill: ${options.actor5}` : ''};
|
${options.actor5 ? `fill: ${options.actor5}` : ''};
|
||||||
}
|
}
|
||||||
|
${getIconStyles()}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default getStyles;
|
export default getStyles;
|
||||||
|
|||||||
@@ -1250,6 +1250,20 @@ flowchart TD
|
|||||||
B-->E(A fa:fa-camera-retro perhaps?)
|
B-->E(A fa:fa-camera-retro perhaps?)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
There are two ways to display these FontAwesome icons:
|
||||||
|
|
||||||
|
### Register FontAwesome icon packs (v<MERMAID_RELEASE_VERSION>+)
|
||||||
|
|
||||||
|
You can register your own FontAwesome icon pack following the ["Registering icon packs" instructions](../config/icons.md).
|
||||||
|
|
||||||
|
Supported prefixes: `fa`, `fab`, `fas`, `far`, `fal`, `fad`.
|
||||||
|
|
||||||
|
```note
|
||||||
|
Note that it will fall back to FontAwesome CSS if FontAwesome packs are not registered.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Register FontAwesome CSS
|
||||||
|
|
||||||
Mermaid supports Font Awesome if the CSS is included on the website.
|
Mermaid supports Font Awesome if the CSS is included on the website.
|
||||||
Mermaid does not have any restriction on the version of Font Awesome that can be used.
|
Mermaid does not have any restriction on the version of Font Awesome that can be used.
|
||||||
|
|
||||||
|
|||||||
@@ -1,34 +1,62 @@
|
|||||||
import { describe, it, expect } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { replaceIconSubstring } from './createText.js';
|
import { replaceIconSubstring } from './createText.js';
|
||||||
|
import mermaid from '../mermaid.js';
|
||||||
|
|
||||||
describe('replaceIconSubstring', () => {
|
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 input = 'This is an icon: fa:fa-user and fab:fa-github';
|
||||||
const output = replaceIconSubstring(input);
|
const output = await replaceIconSubstring(input);
|
||||||
const expected =
|
const expected = `This is an icon: <i class='fa fa-user'></i> and <i class='fab fa-github'></i>`;
|
||||||
"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);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles strings without FontAwesome icon notations', () => {
|
it('handles strings without FontAwesome icon notations', async () => {
|
||||||
const input = 'This string has no icons';
|
const input = 'This string has no icons';
|
||||||
const output = replaceIconSubstring(input);
|
const output = await replaceIconSubstring(input);
|
||||||
expect(output).toEqual(input); // No change expected
|
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 input = 'Icons galore: fa:fa-arrow-right, fak:fa-truck, fas:fa-home';
|
||||||
const output = replaceIconSubstring(input);
|
const output = await replaceIconSubstring(input);
|
||||||
const expected =
|
const expected = `Icons galore: <i class='fa fa-arrow-right'></i>, <i class='fak fa-truck'></i>, <i class='fas fa-home'></i>`;
|
||||||
"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);
|
||||||
});
|
});
|
||||||
|
|
||||||
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 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 =
|
const expected =
|
||||||
"Here is a long icon: <i class='fak fa-truck-driving-long-winding-road'></i> in use";
|
"Here is a long icon: <i class='fak fa-truck-driving-long-winding-road'></i> in use";
|
||||||
expect(output).toEqual(expected);
|
expect(output).toEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('correctly process the registered icons', async () => {
|
||||||
|
/**
|
||||||
|
* CC-BY-4.0
|
||||||
|
* Copyright (c) Fonticons, Inc. - https://fontawesome.com/license/free
|
||||||
|
* https://fontawesome.com/icons/bell?f=classic&s=regular
|
||||||
|
*/
|
||||||
|
const staticBellIconPack = {
|
||||||
|
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"/>',
|
||||||
|
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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
// @ts-nocheck TODO: Fix types
|
// @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 { select } from 'd3';
|
||||||
import type { MermaidConfig } from '../config.type.js';
|
import type { MermaidConfig } from '../config.type.js';
|
||||||
|
import { getConfig, sanitizeText } from '../diagram-api/diagramAPI.js';
|
||||||
import type { SVGGroup } from '../diagram-api/types.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 type { D3TSpanElement, D3TextElement } from '../diagrams/common/commonTypes.js';
|
||||||
import { log } from '../logger.js';
|
import { log } from '../logger.js';
|
||||||
import { markdownToHTML, markdownToLines } from '../rendering-util/handle-markdown-text.js';
|
import { markdownToHTML, markdownToLines } from '../rendering-util/handle-markdown-text.js';
|
||||||
import { decodeEntities } from '../utils.js';
|
import { decodeEntities } from '../utils.js';
|
||||||
|
import { getIconSVG, isIconAvailable } from './icons.js';
|
||||||
import { splitLineToFitWidth } from './splitText.js';
|
import { splitLineToFitWidth } from './splitText.js';
|
||||||
import type { MarkdownLine, MarkdownWord } from './types.js';
|
import type { MarkdownLine, MarkdownWord } from './types.js';
|
||||||
|
|
||||||
@@ -180,14 +181,28 @@ 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
|
* @returns string with fontawesome icons as svg if the icon is registered otherwise as i tags
|
||||||
*/
|
*/
|
||||||
export function replaceIconSubstring(text: string) {
|
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 pendingReplacements: Promise<string>[] = [];
|
||||||
return text.replace(
|
// cspell: disable-next-line
|
||||||
/fa[bklrs]?:fa-[\w-]+/g, // cspell: disable-line
|
text.replace(/(fa[bklrs]?):fa-([\w-]+)/g, (fullMatch, prefix, iconName) => {
|
||||||
(s) => `<i class='${s.replace(':', ' ')}'></i>`
|
pendingReplacements.push(
|
||||||
);
|
(async () => {
|
||||||
|
const registeredIconName = `${prefix}:${iconName}`;
|
||||||
|
if (await isIconAvailable(registeredIconName)) {
|
||||||
|
return await getIconSVG(registeredIconName, undefined, { class: 'label-icon' });
|
||||||
|
} else {
|
||||||
|
return `<i class='${sanitizeText(fullMatch).replace(':', ' ')}'></i>`;
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
);
|
||||||
|
return fullMatch;
|
||||||
|
});
|
||||||
|
|
||||||
|
const replacements = await Promise.all(pendingReplacements);
|
||||||
|
// cspell: disable-next-line
|
||||||
|
return text.replace(/(fa[bklrs]?):fa-([\w-]+)/g, () => replacements.shift() ?? '');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note when using from flowcharts converting the API isNode means classes should be set accordingly. When using htmlLabels => to set classes to 'nodeLabel' when isNode=true otherwise 'edgeLabel'
|
// Note when using from flowcharts converting the API isNode means classes should be set accordingly. When using htmlLabels => to set classes to 'nodeLabel' when isNode=true otherwise 'edgeLabel'
|
||||||
@@ -221,7 +236,7 @@ export const createText = async (
|
|||||||
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
|
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
|
||||||
|
|
||||||
const htmlText = markdownToHTML(text, config);
|
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
|
//for Katex the text could contain escaped characters, \\relax that should be transformed to \relax
|
||||||
const inputForKatex = text.replace(/\\\\/g, '\\');
|
const inputForKatex = text.replace(/\\\\/g, '\\');
|
||||||
|
|||||||
@@ -85,7 +85,8 @@ export const isIconAvailable = async (iconName: string) => {
|
|||||||
|
|
||||||
export const getIconSVG = async (
|
export const getIconSVG = async (
|
||||||
iconName: string,
|
iconName: string,
|
||||||
customisations?: IconifyIconCustomisations & { fallbackPrefix?: string }
|
customisations?: IconifyIconCustomisations & { fallbackPrefix?: string },
|
||||||
|
extraAttributes?: Record<string, string>
|
||||||
) => {
|
) => {
|
||||||
let iconData: ExtendedIconifyIcon;
|
let iconData: ExtendedIconifyIcon;
|
||||||
try {
|
try {
|
||||||
@@ -95,6 +96,9 @@ export const getIconSVG = async (
|
|||||||
iconData = unknownIcon;
|
iconData = unknownIcon;
|
||||||
}
|
}
|
||||||
const renderData = iconToSVG(iconData, customisations);
|
const renderData = iconToSVG(iconData, customisations);
|
||||||
const svg = iconToHTML(replaceIDs(renderData.body), renderData.attributes);
|
const svg = iconToHTML(replaceIDs(renderData.body), {
|
||||||
|
...renderData.attributes,
|
||||||
|
...extraAttributes,
|
||||||
|
});
|
||||||
return svg;
|
return svg;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user