Repository: mermaid-js/mermaid
File: packages/mermaid/src/rendering-util/createText.ts (TypeScript)

Code before:

  const matches = [...text.matchAll(iconRegex)];
  if (matches.length === 0) {
    return text;
  }

  const replacements = await Promise.all(
    matches.map(async ([fullMatch, prefix, iconName]) => {
      const registeredIconName = `${prefix}:${iconName}`;
      try {
        const isIconRegistered = await isIconAvailable(registeredIconName);
        const replacement = isIconRegistered
          ? await getIconSVG(registeredIconName, undefined, { class: 'label-icon' })
          : `<i class='${sanitizeText(fullMatch).replace(':', ' ')}'></i>`;
        return { fullMatch, replacement };
      } catch (error) {
        log.error(`Error processing ${registeredIconName}:`, error);
        return { fullMatch, replacement: fullMatch };
      }
    })
  );

  return replacements.reduce(
    (text, { fullMatch, replacement }) => text.replace(fullMatch, replacement),
    text
  );
}

// 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 = async (
  el,
  text = '',
  {
    style = '',
    isTitle = false,
    classes = '',
    useHtmlLabels = true,
    isNode = true,
    width = 200,
    addSvgBackground = false,
  } = {},
  config?: MermaidConfig
) => {
  log.debug(
    'XYZ createText',
    text,
    style,
    isTitle,
    classes,
    useHtmlLabels,

Review comment (suggestion):
**issue**: I'm not sure how big of a deal this is, but this doesn't seem safe to me, since if one of the SVGs has `fa:xxxxx` in it (maybe even as a comment), it will replace it.

Maybe something like the following is better:

```ts
const pendingReplacements = Array<string>();

text.replace(/(fa[bklrs]?):fa-([\w-]+)/g, (fullMatch, prefix, iconName) => {
  replacements.push((async() => {
    const registeredIconName = `${prefix}:${iconName}`;
    if (await isIconAvailable(registeredIconName)) {
      return await getIconSVG(registeredIconName, undefined, { class: 'label-icon' });
    } else {
      // fall back to FontAwesome CSS
      return `<i class='${s.replace(':', ' ')}'></i>`;
    }
  })());
});

const replacements = await Promise.all(pendingReplacements);
return text.replace(/(fa[bklrs]?):fa-([\w-]+)/g, () => replacements.shift());
```

If we're using the same regex twice, make sure to reset `lastIndex` inbetween calls.

Code after:
  text.replace(/(fa[bklrs]?):fa-([\w-]+)/g, (fullMatch, prefix, iconName) => {
    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'
// When not using htmlLabels => to set classes to 'title-row' when isTitle=true otherwise 'title-row'
export const createText = async (
  el,
  text = '',
  {
    style = '',
    isTitle = false,
    classes = '',
    useHtmlLabels = true,
    isNode = true,
    width = 200,
    addSvgBackground = false,
  } = {},
  config?: MermaidConfig
) => {
  log.debug(
    'XYZ createText',
    text,
    style,
    isTitle,
    classes,
    useHtmlLabels,
    isNode,
    'addSvgBackground: ',
    addSvgBackground
  );
  if (useHtmlLabels) {
    // TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?

    const htmlText = markdownToHTML(text, config);
    const decodedReplacedText = await replaceIconSubstring(decodeEntities(htmlText));