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

5460 - Initial Release for review #5461

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions demos/dev/example.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
flex: 1;
}
</style>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css">
</head>
<body>
<pre id="diagram" class="mermaid">
Expand Down
3 changes: 3 additions & 0 deletions packages/mermaid/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,12 @@
"dayjs": "^1.11.10",
"dompurify": "^3.0.11",
"elkjs": "^0.9.2",
"highlight.js": "^11.9.0",
"katex": "^0.16.9",
"khroma": "^2.1.0",
"lodash-es": "^4.17.21",
"marked": "^12.0.1",
"marked-highlight": "^2.1.1",
"mdast-util-from-markdown": "^2.0.0",
"stylis": "^4.3.1",
"ts-dedent": "^2.2.0",
Expand Down
9 changes: 9 additions & 0 deletions packages/mermaid/src/diagrams/common/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ const processSet = (input: string): string => {
export const isMathMLSupported = () => window.MathMLElement !== undefined;

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

/**
* Whether or not a text has KaTeX delimiters
Expand All @@ -303,6 +304,14 @@ export const katexRegex = /\$\$(.*)\$\$/g;
*/
export const hasKatex = (text: string): boolean => (text.match(katexRegex)?.length ?? 0) > 0;

/**
* 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;

/**
* Computes the minimum dimensions needed to display a div containing MathML
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,18 @@
%x acc_title
%x acc_descr
%x acc_descr_multiline
%x text
%x string
%x md_string
%%

[\n]+ return 'NEWLINE';
<md_string>[^`"]+ { return "MD_STR";}
<md_string>[`]["] { this.popState();}
<*>["][`] { this.begin("md_string");}
<string>[^"]+ return "STR";
<string>["] this.popState();
<*>["] this.pushState("string");
\s+ /* skip all whitespace */
<ID,ALIAS,LINE>((?!\n)\s)+ /* skip same-line whitespace */
<INITIAL,ID,ALIAS,LINE>\#[^\n]* /* skip comments */
Expand Down Expand Up @@ -73,7 +82,9 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
"off" return 'off';
"," return ',';
";" return 'NEWLINE';
[^\+\->:\n,;]+((?!(\-x|\-\-x|\-\)|\-\-\)))[\-]*[^\+\->:\n,;]+)* { yytext = yytext.trim(); return 'ACTOR'; }
<text>"]" { this.popState(); return 'SQE'; }
<*>"[" { this.pushState("text"); return 'SQS'; }
([A-Za-z0-9!"\#$%&'*+\.`?\\_\/]|\-(?=[^\>\-\.])|=(?!=))+ return 'ACTOR';
"->>" return 'SOLID_ARROW';
"-->>" return 'DOTTED_ARROW';
"->" return 'SOLID_OPEN_ARROW';
Expand Down Expand Up @@ -241,6 +252,10 @@ 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 noteStatementText
{
console.log($3);
$$ = [$3, {type:'addNote', placement:$2, actor:$3.actor, text:$3.text }];}
;

links_statement
Expand Down Expand Up @@ -321,4 +336,15 @@ text2
: TXT {$$ = yy.parseMessage($1.trim().substring(1)) }
;

%%
text3
: STR
{ $$ = {text: $STR, type: 'string'};}
| MD_STR
{ $$ = {text: $MD_STR, type: 'markdown'};}
;

noteStatementText
: ACTOR SQS text3 SQE
{$$ = {actor: $ACTOR, text: $text3 }}
;
%%
23 changes: 22 additions & 1 deletion packages/mermaid/src/diagrams/sequence/sequenceDb.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getConfig } from '../../diagram-api/diagramAPI.js';
import { log } from '../../logger.js';
import { ImperativeState } from '../../utils/imperativeState.js';
import { sanitizeText } from '../common/common.js';
import {
clear as commonClear,
Expand All @@ -10,7 +11,6 @@ import {
setAccTitle,
setDiagramTitle,
} from '../common/commonDb.js';
import { ImperativeState } from '../../utils/imperativeState.js';

const state = new ImperativeState(() => ({
prevActor: undefined,
Expand Down Expand Up @@ -268,6 +268,25 @@ export const parseBoxData = function (str) {
};
};

export const parseNoteStatement = function (str) {
try {
const _str = str.trim();
const _text = _str.match(/^:?json:/) !== null
? JSON.stringify(JSON.parse(_str.replace(/^:json:/, '').trim()),null,2)
: _str;
const message = {
text:
_text,
wrap:
false
};
return message;
} catch (exception) {
let error = new Error('Invalid JSON');
throw error;
}
}

export const LINETYPE = {
SOLID: 0,
DOTTED: 1,
Expand Down Expand Up @@ -639,6 +658,7 @@ export default {
clear,
parseMessage,
parseBoxData,
parseNoteStatement,
LINETYPE,
ARROWTYPE,
PLACEMENT,
Expand All @@ -649,4 +669,5 @@ export default {
getAccDescription,
hasAtLeastOneBox,
hasAtLeastOneBoxWithTitle,

};
12 changes: 8 additions & 4 deletions packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// @ts-nocheck TODO: fix file
import { select } from 'd3';
import svgDraw, { drawKatex, ACTOR_TYPE_WIDTH, drawText, fixLifeLineHeights } from './svgDraw.js';
import svgDraw, { drawKatex, ACTOR_TYPE_WIDTH, drawText, fixLifeLineHeights, drawMarkdown } from './svgDraw.js';
import { log } from '../../logger.js';
import common, { calculateMathMLDimensions, hasKatex } from '../common/common.js';
import common, { calculateMathMLDimensions, hasKatex, hasMarkdown } 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 +263,7 @@ 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 @@ -1340,7 +1340,11 @@ 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(
: hasMarkdown(msg.message)
? await utils.calculateMarkdownDimensions(
msg.message,
noteFont(conf))
: utils.calculateTextDimensions(
shouldWrap ? utils.wrapLabel(msg.message, conf.width, noteFont(conf)) : msg.message,
noteFont(conf)
);
Expand Down
42 changes: 41 additions & 1 deletion packages/mermaid/src/diagrams/sequence/svgDraw.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import common, { calculateMathMLDimensions, hasKatex, renderKatex } from '../common/common.js';
import * as svgDrawCommon from '../common/svgDrawCommon.js';
import { ZERO_WIDTH_SPACE, parseFontSize } from '../../utils.js';
import { ZERO_WIDTH_SPACE, parseFontSize, renderMarkdown } from '../../utils.js';
import { sanitizeUrl } from '@braintree/sanitize-url';
import * as configApi from '../../config.js';

Expand Down Expand Up @@ -258,6 +258,46 @@ export const drawText = function (elem, textData) {
return textElems;
};

export const drawMarkdown = async function (elem, textData, msgModel = null) {
let textElem = elem.append('foreignObject');
const lines = await renderMarkdown(textData.text, configApi.getConfig());

const divElem = textElem
.append('xhtml:div')
.attr('style', 'width: fit-content;')
.attr('xmlns', 'http://www.w3.org/1999/xhtml')
.html(lines);
const dim = divElem.node().getBoundingClientRect();

textElem.attr('height', Math.round(dim.height)).attr('width', Math.round(dim.width));

if (textData.class === 'noteText') {
const rectElem = elem.node().firstChild;

rectElem.setAttribute('height', dim.height + 2 * textData.textMargin);
const rectDim = rectElem.getBBox();

textElem
.attr('x', Math.round(rectDim.x + rectDim.width / 2 - dim.width / 2))
.attr('y', Math.round(rectDim.y + rectDim.height / 2 - dim.height / 2));
} else if (msgModel) {
let { startx, stopx, starty } = msgModel;
if (startx > stopx) {
const temp = startx;
startx = stopx;
stopx = temp;
}

textElem.attr('x', Math.round(startx + Math.abs(startx - stopx) / 2 - dim.width / 2));
if (textData.class === 'loopText') {
textElem.attr('y', Math.round(starty));
} else {
textElem.attr('y', Math.round(starty - dim.height));
}
}
return [textElem];
};

export const drawLabel = function (elem, txtObject) {
/**
* @param {any} x
Expand Down
75 changes: 66 additions & 9 deletions packages/mermaid/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import {
curveBumpX,
curveBumpY,
curveBundle,
curveCardinal,
curveCardinalClosed,
curveCardinalOpen,
curveCardinal,
curveCatmullRom,
curveCatmullRomClosed,
curveCatmullRomOpen,
curveCatmullRom,
curveLinear,
curveLinearClosed,
curveMonotoneX,
Expand All @@ -23,18 +23,20 @@ import {
curveStepBefore,
select,
} from 'd3';
import common from './diagrams/common/common.js';
import { sanitizeDirective } from './utils/sanitizeDirective.js';
import { log } from './logger.js';
import { detectType } from './diagram-api/detectType.js';
import assignWithDepth from './assignWithDepth.js';
import type { MermaidConfig } from './config.type.js';
import hljs from 'highlight.js';
import memoize from 'lodash-es/memoize.js';
import merge from 'lodash-es/merge.js';
import { Marked } from "marked";
import { markedHighlight } from "marked-highlight";
import assignWithDepth from './assignWithDepth.js';
import type { MermaidConfig } from './config.type.js';
import { detectType } from './diagram-api/detectType.js';
import { directiveRegex } from './diagram-api/regexes.js';
import common, { hasMarkdown } from './diagrams/common/common.js';
import { log } from './logger.js';
import type { D3Element } from './mermaidAPI.js';
import type { Point, TextDimensionConfig, TextDimensions } from './types.js';

import { sanitizeDirective } from './utils/sanitizeDirective.js';
export const ZERO_WIDTH_SPACE = '\u200b';

// Effectively an enum of the supported curve types, accessible by name
Expand Down Expand Up @@ -754,6 +756,60 @@ export const calculateTextDimensions: (
(text, config) => `${text}${config.fontSize}${config.fontWeight}${config.fontFamily}`
);

/**
* 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) => {
const { fontSize = 12, fontFamily = 'Arial', fontWeight = 400 } = config;
const [, _fontSizePx="12px"] = parseFontSize(fontSize);
text = await renderMarkdown(text, config);
const divElem = document.createElement('div');
divElem.innerHTML = text;
divElem.id = 'markdown-temp';
divElem.style.visibility = 'hidden';
divElem.style.position = 'absolute';
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;
};

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

const marked = new Marked(
markedHighlight({
langPrefix: 'hljs language-',
highlight(code, lang, info) {
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
return hljs.highlight(code, { language }).value;
}
})
);

return marked.parse(text);
}

export class InitIDGenerator {
private count = 0;
public next: () => number;
Expand Down Expand Up @@ -870,6 +926,7 @@ export default {
calculateTextHeight,
calculateTextWidth,
calculateTextDimensions,
calculateMarkdownDimensions,
cleanAndMerge,
detectInit,
detectDirective,
Expand Down