Skip to content

feat: add support for inline markdown links in text rendering#7470

Open
taoqf wants to merge 19 commits intomermaid-js:developfrom
taoqf:develop
Open

feat: add support for inline markdown links in text rendering#7470
taoqf wants to merge 19 commits intomermaid-js:developfrom
taoqf:develop

Conversation

@taoqf
Copy link

@taoqf taoqf commented Mar 10, 2026

📑 Summary

Brief description about the content of your PR.

Resolves #

📏 Design Decisions

Describe the way your implementation works or what design decisions you made if applicable.

📋 Tasks

Make sure you

  • 📖 have read the contribution guidelines
  • 💻 have added necessary unit/e2e tests.
  • 📓 have added documentation. Make sure MERMAID_RELEASE_VERSION is used for all new features.
  • 🦋 If your PR makes a change that should be noted in one or more packages' changelogs, generate a changeset by running pnpm changeset and following the prompts. Changesets that add features should be minor and those that fix bugs should be patch. Please prefix changeset messages with feat:, fix:, or chore:.

@netlify
Copy link

netlify bot commented Mar 10, 2026

Deploy Preview for mermaid-js ready!

Name Link
🔨 Latest commit 72441f4
🔍 Latest deploy log https://app.netlify.com/projects/mermaid-js/deploys/69b94bb1fde96f0008e7136d
😎 Deploy Preview https://deploy-preview-7470--mermaid-js.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@changeset-bot
Copy link

changeset-bot bot commented Mar 10, 2026

🦋 Changeset detected

Latest commit: 72441f4

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
mermaid Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@taoqf
Copy link
Author

taoqf commented Mar 10, 2026

#1279

@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 10, 2026

Open in StackBlitz

@mermaid-js/examples

npm i https://pkg.pr.new/@mermaid-js/examples@7470

mermaid

npm i https://pkg.pr.new/mermaid@7470

@mermaid-js/layout-elk

npm i https://pkg.pr.new/@mermaid-js/layout-elk@7470

@mermaid-js/layout-tidy-tree

npm i https://pkg.pr.new/@mermaid-js/layout-tidy-tree@7470

@mermaid-js/mermaid-zenuml

npm i https://pkg.pr.new/@mermaid-js/mermaid-zenuml@7470

@mermaid-js/parser

npm i https://pkg.pr.new/@mermaid-js/parser@7470

@mermaid-js/tiny

npm i https://pkg.pr.new/@mermaid-js/tiny@7470

commit: 72441f4

@codecov
Copy link

codecov bot commented Mar 10, 2026

Codecov Report

❌ Patch coverage is 0.40650% with 245 lines in your changes missing coverage. Please review.
✅ Project coverage is 3.44%. Comparing base (00aef12) to head (72441f4).

Files with missing lines Patch % Lines
packages/mermaid/src/diagrams/sequence/svgDraw.js 0.00% 189 Missing ⚠️
packages/mermaid/src/rendering-util/createText.ts 0.00% 18 Missing ⚠️
...mermaid/src/rendering-util/handle-markdown-text.ts 0.00% 15 Missing ⚠️
packages/mermaid/src/diagrams/flowchart/flowDb.ts 0.00% 10 Missing ⚠️
packages/mermaid/src/rendering-util/splitText.ts 0.00% 8 Missing ⚠️
...c/rendering-util/rendering-elements/shapes/util.ts 0.00% 4 Missing ⚠️
packages/mermaid/src/diagrams/sequence/styles.js 0.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           develop   #7470      +/-   ##
==========================================
- Coverage     3.45%   3.44%   -0.02%     
==========================================
  Files          518     518              
  Lines        51036   51203     +167     
  Branches       795     795              
==========================================
  Hits          1762    1762              
- Misses       49274   49441     +167     
Flag Coverage Δ
unit 3.44% <0.40%> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
packages/mermaid/src/diagrams/flowchart/types.ts 100.00% <ø> (ø)
packages/mermaid/src/rendering-util/types.ts 100.00% <100.00%> (ø)
packages/mermaid/src/diagrams/sequence/styles.js 0.00% <0.00%> (ø)
...c/rendering-util/rendering-elements/shapes/util.ts 0.00% <0.00%> (ø)
packages/mermaid/src/rendering-util/splitText.ts 1.08% <0.00%> (-0.02%) ⬇️
packages/mermaid/src/diagrams/flowchart/flowDb.ts 0.00% <0.00%> (ø)
...mermaid/src/rendering-util/handle-markdown-text.ts 0.90% <0.00%> (-0.15%) ⬇️
packages/mermaid/src/rendering-util/createText.ts 0.00% <0.00%> (ø)
packages/mermaid/src/diagrams/sequence/svgDraw.js 0.00% <0.00%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@argos-ci
Copy link

argos-ci bot commented Mar 10, 2026

The latest updates on your projects. Learn more about Argos notifications ↗︎

Build Status Details Updated (UTC)
default (Inspect) ⚠️ Changes detected (Review) 2 changed Mar 17, 2026, 12:53 PM

Copy link
Collaborator

@knsv-bot knsv-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[sisyphus-bot]

Thanks for this contribution, @taoqf! Adding inline markdown link support is a useful feature. I have some concerns about security, theming, and test coverage that I'd like to work through before this can land — especially since this touches shared rendering-util/ code that affects all diagram types.

File Triage

Tier Count Files
Tier 1 (full read) 5 createText.ts, handle-markdown-text.ts, handle-markdown-text.spec.ts, splitText.ts, types.ts
Tier 2 (diff + context) 2 svgDraw.js, sequenceDiagram.spec.js
Tier 3 (diff only) 2 changeset, src/docs/syntax/sequenceDiagram.md
Skip (generated) 2 docs/config/setup/mermaid/interfaces/LayoutData.md, docs/syntax/sequenceDiagram.md

What's working well

🎉 [praise] Good use of @braintree/sanitize-url for URL sanitization, and the tests explicitly verify that javascript: and data: URLs are replaced with about:blank. This is the right library for the job.

🎉 [praise] Thorough unit tests in handle-markdown-text.spec.ts — the markdownToLines and markdownToHTML test cases cover single links, multiple links, mixed formatting, and malicious URLs. Nice coverage.

🎉 [praise] The splitText.ts changes correctly propagate the href field through the word-splitting recursion, preserving link context when text wraps.

Things to address

Blocking

🔴 [blocking] No E2E visual regression tests. This PR changes shared rendering code in rendering-util/ (createText.ts, handle-markdown-text.ts, splitText.ts, types.ts) which affects all diagram types that use markdown text — not just sequence diagrams. Additionally, the sequence diagram renderer (svgDraw.js) now renders clickable <a> elements, which is a visual change. Per project conventions, renderer and shared rendering-util changes require E2E tests using imgSnapshotTest() in cypress/integration/rendering/. At minimum, add a sequence diagram E2E test showing links rendering correctly.

Security

🟡 [important] Unescaped href in markdownToHTML string interpolation. In handle-markdown-text.ts, the new link handling builds HTML via template literal:

return `<a href="${href}" target="_blank">${text}</a>`;

While sanitizeUrl() blocks dangerous URL schemes (javascript:, data:, etc. — including mixed-case, entity-encoded, and whitespace-padded variants), it does not HTML-encode special characters. A URL containing " could theoretically break out of the href attribute and inject additional attributes. DOMPurify sanitizes the final output (which mitigates this), but building malformed HTML before sanitization is a fragile pattern — it relies on DOMPurify parsing the broken HTML exactly the same way a browser would (mXSS risk).

Suggested fix: HTML-encode the href before interpolation:

const safeHref = href.replace(/&/g, '&amp;').replace(/"/g, '&quot;');
return `<a href="${safeHref}" target="_blank">${text}</a>`;

Note: The D3 paths in createText.ts and svgDraw.js are safe — D3's .attr() properly encodes attribute values.

Important

🟡 [important] Hardcoded link color #0d47a1. This hex value appears in two places:

  • createText.ts (the diff's updateTextContentAndStyles function): .attr('fill', '#0d47a1')
  • svgDraw.js (the drawText function): .attr('fill', '#0d47a1')

This hardcoded color will look wrong on dark themes and won't respond to theme customization. It should use a theme variable instead. Look at how other diagrams reference themeVariables.textColor or similar — the link color could map to an existing variable or a new one. At minimum, the color should be consistent with the theme's color palette.

🟡 [important] Broad scope with narrow testing. The changes to handle-markdown-text.ts, createText.ts, splitText.ts, and types.ts affect ALL diagram types that render markdown labels (flowchart, class, state, mindmap, etc.), but the tests only exercise sequence diagrams. A link in a flowchart node label (node["\Check docs`"]) would also go through the modified markdownToLines/createText` pipeline. Even if you don't add tests for every diagram, it would be great to acknowledge this scope in the PR description and verify manually that a few other diagram types don't regress.

🟡 [important] Duplicate link rendering logic. Links are rendered in two independent places:

  1. handle-markdown-text.ts + createText.ts — the shared markdown pipeline (used by all diagram types with markdown labels)
  2. svgDraw.js — sequence-diagram-specific regex-based rendering

This duplication means link rendering behavior could diverge (different regex patterns, different sanitization, different styling). Is the svgDraw.js path needed, or does the shared pipeline already handle sequence diagram messages? If sequence diagrams use a separate text rendering path that bypasses createText.ts, this is understandable — but it should be documented in the PR description.

Nit

🟢 [nit] The changeset says minor bump, which is correct for a new feature.

Summary

Severity Count
🔴 blocking 1
🟡 important 4
🟢 nit 1
💡 suggestion 0
🎉 praise 3

The core feature is well-implemented — the markdown parsing, URL sanitization, and word splitting are solid. The main concerns are: (1) the missing E2E tests for a shared rendering change, (2) the hardcoded link color bypassing the theme system, (3) the HTML interpolation pattern in markdownToHTML, and (4) explaining the dual rendering paths. Looking forward to seeing this updated!

@knsv knsv requested a review from aloisklink March 11, 2026 18:31
@taoqf taoqf marked this pull request as draft March 12, 2026 00:58
@taoqf taoqf marked this pull request as ready for review March 12, 2026 02:37
@taoqf taoqf requested a review from knsv-bot March 13, 2026 07:36
@taoqf taoqf marked this pull request as draft March 13, 2026 09:57
@taoqf
Copy link
Author

taoqf commented Mar 13, 2026

@aloisklink Is this pr ready to merge?

I had added link support for flowchart too. But it need a ` surround whole text(make them markdown format)

    flowchart LR
      A["`Visit [Google](https://google.com)`"]
      B["`Check [GitHub](https://github.com)`"]
      C["`Open [Example](https://example.com)`"]
      
      A -- "`Click [here](https://link1.com)`" --> B
      B -. "`More [info](https://link2.com)`" .-> C
      C == "`Try this [link](https://link3.com)`" ==> A

sequenceDiagram do not need `

	sequenceDiagram
		participant A as Christmas [shopping](https://ebay.com)
		participant B as Go [shopping](https://ebay.com)
		participant C as Let me think
		participant D as Laptop
		participant E as iPhone
		participant F as Car
		link B: BuyBuyBuy @ https://ebay.com

		A->>B: [Get money](https://earnmoney.com)
		B->>C: After shopping
		C->>C: Decision making
		C-->>D: Option: One
		C-->>E: Option: Two
		C-->>F: Option: Three

@taoqf taoqf marked this pull request as ready for review March 13, 2026 14:56
@taoqf
Copy link
Author

taoqf commented Mar 16, 2026

@aloisklink @knsv-bot @tobie @xuanxu @dbussink Will anyone review this?

@knsv
Copy link
Collaborator

knsv commented Mar 17, 2026

Thanks, I will do another round or reviews!

@knsv knsv mentioned this pull request Mar 17, 2026
@knsv
Copy link
Collaborator

knsv commented Mar 17, 2026

Thanks for the thorough update, @taoqf! You've addressed several items from the first review — the hardcoded link color is gone, D3 paths now HTML-encode hrefs, and there's significantly more test coverage across sequence diagrams, flowcharts, and the markdown pipeline. Let me walk through what's resolved and what remains.

What's improved since the last review

🎉 [praise] The hardcoded #0d47a1 link color is completely removed. Links now use text-decoration: underline and inherit theme-based text colors. The styles.js CSS selector change from text.actor > tspan to
text.actor tspan ensures theme colors cascade to nested tspans inside elements. This is the right approach.

🎉 [praise] The D3 rendering paths in both createText.ts and svgDraw.js now HTML-encode hrefs before interpolation:
const safeHref = linkHref.replace(/&/g, '&').replace(/"/g, '"');
This addresses the primary security concern from the first review for the SVG rendering paths.

🎉 [praise] Excellent test coverage added — 95 lines of handle-markdown-text.spec.ts tests (link formatting, multi-word links, multiple links, bold/italic mixed with links, malicious URL sanitization for both
markdownToLines and markdownToHTML), 54 lines of flow-md-string.spec.js tests (markdown links in nodes and edge labels, surrounding text, multiple links), and 245 lines of sequenceDiagram.spec.js tests
covering messages, notes, loops, alt, opt, par, and actor descriptions with links.

🎉 [praise] Extending the feature to flowcharts (via flowDb.ts, shapes/util.ts, flowchart/types.ts) broadens the value of this PR and addresses the "narrow scope" concern from the first review.

Things to address

🟡 [important] Contradictory sanitization bypass between flowDb.ts and shapes/util.ts. Both files skip sanitizeText() for markdown content, but each claims the other handles it:

  • flowDb.ts:166: "For markdown type, don't sanitize to preserve markdown syntax... Sanitization will happen during rendering in shapes/util.ts"
  • shapes/util.ts:76: "For markdown text, don't sanitize as it would break markdown syntax... The text has already been sanitized in flowDb.ts"

Neither actually sanitizes. The text IS handled safely downstream — markdownToLines parses it via marked, text content goes through D3's .text() (sets textContent, not innerHTML), and URLs go through
sanitizeUrl(). So this isn't an active XSS vector. But the contradictory comments create a false sense of security for future maintainers. It would be great to fix the comments to accurately state:
"Sanitization is deferred to the rendering layer — markdownToLines/createText handle text safely via D3's .text() and sanitizeUrl() for hrefs."

🟡 [important] markdownToHTML still interpolates href without HTML encoding. handle-markdown-text.ts:143:
return <a href="${href}" target="_blank">${text}</a>;

The D3 rendering paths were fixed with replace(/"/g, '"'), but markdownToHTML (used for HTML label mode) still interpolates the raw sanitizeUrl() output. DOMPurify mitigates this since it sanitizes the
final HTML output, but applying the same encoding here would be more robust:
const safeHref = href.replace(/&/g, '&').replace(/"/g, '"');
return <a href="${safeHref}" target="_blank">${text}</a>;

There are still no e2e tests for this, but thanks to the thorough unit tests, that is not a blocker.

So no blockers from me, but I would still want @aloisklink's eyes on the security aspects, just in case.

@taoqf
Copy link
Author

taoqf commented Mar 17, 2026

I did not change architecture-beta's behavior, no idea about argos check

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants