Skip to content

Commit

Permalink
add markdown support to sequence diagram notes
Browse files Browse the repository at this point in the history
  • Loading branch information
Dennis Schaller committed Aug 9, 2024
1 parent 47601ac commit 3209ab6
Show file tree
Hide file tree
Showing 8 changed files with 406 additions and 11 deletions.
49 changes: 49 additions & 0 deletions demos/sequence.html
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,55 @@ <h1>Sequence diagram demos</h1>
Alice<<->>Bob: Wow, we said that at the same time!
Bob<<-->>Alice: Bidirectional Arrows are so cool
</pre>

<hr />

<pre class="mermaid">
sequenceDiagram
actor Alice
actor John
Alice-xJohn: Hello John, how are you?
note over Alice,John: [ This is some `markdown` text
```json
{
"title": "This is a note",
"description": "This is a note with a description"
}
```
]
John--xAlice: Great!
note left of Alice: [ This is some `markdown` text
```json
{
"title": "This is a note",
from: "Alice",
to: "John",
"subtitle": "A very pretty one note",
"description": "This is a note with a description"
}
```
]
Alice--xJohn: Awesome!
note right of John: [ This is some `markdown` text
```json
{
"title": "This is a note",
"subtitle": "A very pretty one",
"description": "This is a note with a description"
}
```
There can even be `more markdown`:
```
const note = "pretty";
if (note === "pretty") {
console.log("This is a note with a description");
} else {
// do something else
}
```
]
</pre>

<script type="module">
import mermaid from './mermaid.esm.mjs';
mermaid.initialize({
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,16 @@
"express": "^4.19.1",
"globals": "^15.4.0",
"globby": "^14.0.1",
"highlight.js": "^11.10.0",
"husky": "^9.0.11",
"jest": "^29.7.0",
"jison": "^0.4.18",
"js-yaml": "^4.1.0",
"jsdom": "^24.0.0",
"langium-cli": "3.0.3",
"lint-staged": "^15.2.2",
"marked": "^14.0.0",
"marked-highlight": "^2.1.4",
"markdown-table": "^3.0.3",
"nyc": "^15.1.0",
"path-browserify": "^1.0.1",
Expand Down
91 changes: 91 additions & 0 deletions packages/mermaid/src/diagrams/common/common.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import DOMPurify from 'dompurify';
import hljs from 'highlight.js';
import { Marked } from 'marked';
import { markedHighlight } from 'marked-highlight';
import type { MermaidConfig } from '../../config.type.js';
import { parseFontSize } from '../../utils.js';

// Remove and ignore br:s
export const lineBreakRegex = /<br\s*\/?>/gi;
Expand Down Expand Up @@ -367,6 +371,93 @@ export const renderKatex = async (text: string, config: MermaidConfig): Promise<
);
};

export const markdownRegex = /(```((.|\n)*)```)|(`(.*)`)/g;

/**
* Whether or not a text has markdown delimiters
*
* @param text - The text to test
* @returns Whether or not the text has markdown delimiters
*/
export const hasMarkdown = (text: string): boolean => (text.match(markdownRegex)?.length ?? 0) > 0;

/**
* Attempts to render and return the markdown portion of a string with Marked
*
* @param text - The text to test
* @param config - Configuration for Mermaid
* @returns String containing markdown rendered as html, or an error message if it is not and stylesheets aren't present
*/
export const renderMarkdown = async (text: string): Promise<string> => {
if (!hasMarkdown(text)) {
return text;
}

const marked = new Marked(
markedHighlight({

Check failure on line 397 in packages/mermaid/src/diagrams/common/common.ts

View workflow job for this annotation

GitHub Actions / build-mermaid

Argument of type 'import("/home/runner/work/mermaid/mermaid/node_modules/marked/lib/marked", { with: { "resolution-mode": "import" } }).MarkedExtension' is not assignable to parameter of type 'import("/home/runner/work/mermaid/mermaid/packages/mermaid/node_modules/marked/lib/marked", { with: { "resolution-mode": "import" } }).MarkedExtension'.

Check failure on line 397 in packages/mermaid/src/diagrams/common/common.ts

View workflow job for this annotation

GitHub Actions / build-mermaid

Argument of type 'import("/home/runner/work/mermaid/mermaid/node_modules/marked/lib/marked", { with: { "resolution-mode": "import" } }).MarkedExtension' is not assignable to parameter of type 'import("/home/runner/work/mermaid/mermaid/packages/mermaid/node_modules/marked/lib/marked", { with: { "resolution-mode": "import" } }).MarkedExtension'.
langPrefix: 'hljs language-',
highlight(code, lang) {
const language = hljs.getLanguage(lang);
const highlighted = hljs.highlight(code, { language: language?.name ?? 'plaintext' });
return highlighted.value;
},
})
);

marked.use({
hooks: {
processAllTokens: (tokens) =>
tokens.map((token) =>
token?.lang

Check failure on line 411 in packages/mermaid/src/diagrams/common/common.ts

View workflow job for this annotation

GitHub Actions / build-mermaid

Property 'lang' does not exist on type 'Token'.

Check failure on line 411 in packages/mermaid/src/diagrams/common/common.ts

View workflow job for this annotation

GitHub Actions / build-mermaid

Property 'lang' does not exist on type 'Token'.
? token
: {
...token,
lang: hljs.highlightAuto(token.text, [

Check failure on line 415 in packages/mermaid/src/diagrams/common/common.ts

View workflow job for this annotation

GitHub Actions / build-mermaid

Property 'text' does not exist on type 'Token'.

Check failure on line 415 in packages/mermaid/src/diagrams/common/common.ts

View workflow job for this annotation

GitHub Actions / build-mermaid

Property 'text' does not exist on type 'Token'.
'json',
'javascript',
'typescript',
'html',
'xml',
'java',
]).language,
}
),
},
});

return marked.parse(text);
};

/**
* This calculates the dimensions of the given text, font size, font family, font weight, and
* margins.
*
* @param text - The text to calculate the width of
* @param config - The config for fontSize, fontFamily, fontWeight, and margin all impacting
* the resulting size
* @returns The dimensions for the given text
*/
export const calculateMarkdownDimensions = async (text: string, config: TextDimensionConfig) => {

Check failure on line 440 in packages/mermaid/src/diagrams/common/common.ts

View workflow job for this annotation

GitHub Actions / build-mermaid

Cannot find name 'TextDimensionConfig'.

Check failure on line 440 in packages/mermaid/src/diagrams/common/common.ts

View workflow job for this annotation

GitHub Actions / build-mermaid

Cannot find name 'TextDimensionConfig'.
const { fontSize = 12, fontFamily = 'Arial', fontWeight = 400 } = config;
const [, _fontSizePx = '12px'] = parseFontSize(fontSize);
text = await renderMarkdown(text, config);

Check failure on line 443 in packages/mermaid/src/diagrams/common/common.ts

View workflow job for this annotation

GitHub Actions / build-mermaid

Expected 1 arguments, but got 2.

Check failure on line 443 in packages/mermaid/src/diagrams/common/common.ts

View workflow job for this annotation

GitHub Actions / build-mermaid

Expected 1 arguments, but got 2.
const divElem = document.createElement('div');
divElem.innerHTML = text;
divElem.id = 'markdown-temp';
divElem.style.visibility = 'hidden';
divElem.style.position = 'absolute';
divElem.style.padding = '1em';
divElem.style.fontSize = _fontSizePx;
divElem.style.fontFamily = fontFamily;
divElem.style.fontWeight = '' + fontWeight;
divElem.style.top = '0';
const body = document.querySelector('body');
body?.insertAdjacentElement('beforeend', divElem);
const dim = { width: divElem.clientWidth, height: divElem.clientHeight };
divElem.remove();
return dim;
};

export default {
getRows,
sanitizeText,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@
%x acc_title
%x acc_descr
%x acc_descr_multiline
%x text
%%

<text>"]" { this.popState(); return 'SQE'; }
<text>[^\]]+ { return 'SQC'; }
<*>(":"\s*[\n\s]*\[) { this.pushState("text"); return 'SQS'; }
[\n]+ return 'NEWLINE';
\s+ /* skip all whitespace */
<ID,ALIAS,LINE>((?!\n)\s)+ /* skip same-line whitespace */
Expand Down Expand Up @@ -84,12 +88,13 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
\-\-[x] return 'DOTTED_CROSS';
\-[\)] return 'SOLID_POINT';
\-\-[\)] return 'DOTTED_POINT';
":"(?:(?:no)?wrap:)?[^#\n;]+ return 'TXT';
":"(?![\n\s]*\[)(?:(?:no)?wrap:)?[^#\n;]+ return 'TXT';
"+" return '+';
"-" return '-';
<<EOF>> return 'NEWLINE';
. return 'INVALID';


/lex

%left '^'
Expand Down Expand Up @@ -142,7 +147,7 @@ statement
| autonumber 'NEWLINE' {$$ = {type:'sequenceIndex', sequenceVisible:true, signalType:yy.LINETYPE.AUTONUMBER}; }
| 'activate' actor 'NEWLINE' {$$={type: 'activeStart', signalType: yy.LINETYPE.ACTIVE_START, actor: $2.actor};}
| 'deactivate' actor 'NEWLINE' {$$={type: 'activeEnd', signalType: yy.LINETYPE.ACTIVE_END, actor: $2.actor};}
| note_statement 'NEWLINE'
| note_statement
| links_statement 'NEWLINE'
| link_statement 'NEWLINE'
| properties_statement 'NEWLINE'
Expand Down Expand Up @@ -243,8 +248,24 @@ note_statement
$2[0] = $2[0].actor;
$2[1] = $2[1].actor;
$$ = [$3, {type:'addNote', placement:yy.PLACEMENT.OVER, actor:$2.slice(0, 2), text:$4}];}
| 'note' placement actor SQS mdtext SQE 'NEWLINE'
{
$$ = [$3, {type:'addNote', placement:$placement, actor:$actor.actor, text:$mdtext }];}
| 'note' 'over' actor_pair SQS mdtext SQE 'NEWLINE'
{
// Coerce actor_pair into a [to, from, ...] array
$2 = [].concat($3, $3).slice(0, 2);
$2[0] = $2[0].actor;
$2[1] = $2[1].actor;
$$ = [$3, {type:'addNote', placement:yy.PLACEMENT.OVER, actor:$2.slice(0, 2), text:$mdtext}];}
;

mdtext
: SQC
{ $$ = {type: 'text', text: $SQC} }
;


links_statement
: 'links' actor text2
{
Expand Down
31 changes: 24 additions & 7 deletions packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
// @ts-nocheck TODO: fix file
import { select } from 'd3';
import svgDraw, { drawKatex, ACTOR_TYPE_WIDTH, drawText, fixLifeLineHeights } from './svgDraw.js';
import svgDraw, {
drawKatex,
drawMarkdown,
ACTOR_TYPE_WIDTH,
drawText,
fixLifeLineHeights,
} from './svgDraw.js';
import { log } from '../../logger.js';
import common, { calculateMathMLDimensions, hasKatex } from '../common/common.js';
import common, {
calculateMathMLDimensions,
hasKatex,
hasMarkdown,
calculateMarkdownDimensions,
} from '../common/common.js';
import * as svgDrawCommon from '../common/svgDrawCommon.js';
import { getConfig } from '../../diagram-api/diagramAPI.js';
import assignWithDepth from '../../assignWithDepth.js';
Expand Down Expand Up @@ -263,7 +274,11 @@ const drawNote = async function (elem: any, noteModel: NoteModel) {
textObj.textMargin = conf.noteMargin;
textObj.valign = 'center';

const textElem = hasKatex(textObj.text) ? await drawKatex(g, textObj) : drawText(g, textObj);
const textElem = hasKatex(textObj.text)
? await drawKatex(g, textObj)
: hasMarkdown(textObj.text)
? await drawMarkdown(g, textObj)
: drawText(g, textObj);

const textHeight = Math.round(
textElem
Expand Down Expand Up @@ -1354,10 +1369,12 @@ const buildNoteModel = async function (msg, actors, diagObj) {

let textDimensions: { width: number; height: number; lineHeight?: number } = hasKatex(msg.message)
? await calculateMathMLDimensions(msg.message, getConfig())
: utils.calculateTextDimensions(
shouldWrap ? utils.wrapLabel(msg.message, conf.width, noteFont(conf)) : msg.message,
noteFont(conf)
);
: hasMarkdown(msg.message)
? await calculateMarkdownDimensions(msg.message, getConfig())
: utils.calculateTextDimensions(
shouldWrap ? utils.wrapLabel(msg.message, conf.width, noteFont(conf)) : msg.message,
noteFont(conf)
);
const noteModel = {
width: shouldWrap
? conf.width
Expand Down
Loading

0 comments on commit 3209ab6

Please sign in to comment.