diff --git a/.changeset/bright-ads-exist.md b/.changeset/bright-ads-exist.md new file mode 100644 index 000000000..ef2f76f4c --- /dev/null +++ b/.changeset/bright-ads-exist.md @@ -0,0 +1,5 @@ +--- +'mermaid': patch +--- + +Fixes for consistent edge id creation & handling edge cases for animate edge feature diff --git a/.changeset/chatty-elephants-warn.md b/.changeset/chatty-elephants-warn.md new file mode 100644 index 000000000..225047ece --- /dev/null +++ b/.changeset/chatty-elephants-warn.md @@ -0,0 +1,5 @@ +--- +'mermaid': patch +--- + +Fix for issue #6195 - allowing @ signs inside node labels diff --git a/.changeset/stupid-dots-do.md b/.changeset/stupid-dots-do.md new file mode 100644 index 000000000..594fa9536 --- /dev/null +++ b/.changeset/stupid-dots-do.md @@ -0,0 +1,5 @@ +--- +'mermaid': patch +--- + +fix: Gantt, Sankey and User Journey diagram are now able to pick font-family from mermaid config. diff --git a/cypress/platform/knsv2.html b/cypress/platform/knsv2.html index dfcd8aa4f..90ecd20ea 100644 --- a/cypress/platform/knsv2.html +++ b/cypress/platform/knsv2.html @@ -78,9 +78,11 @@ font-family: monospace; font-size: 72px; } + pre { width: 100%; } + /* tspan { font-size: 6px !important; } */ @@ -103,16 +105,20 @@ -
+    
       flowchart LR
-        A --> B
+        AB["apa@apa@"] --> B(("`apa@apa`"))
+    
+
+      flowchart
+        D(("for D"))
     
       flowchart LR
         A e1@==> B
         e1@{ animate: true}
     
-
+    
 flowchart LR
   A e1@--> B
   classDef animate stroke-width:2,stroke-dasharray:10\,8,stroke-dashoffset:-180,animation: edge-animation-frame 6s linear infinite, stroke-linecap: round
@@ -126,7 +132,7 @@ flowchart LR
   class e1 animate
     

Mermaid - edge-animation-slow

-
+    
 flowchart LR
   A e1@--> B
 e1@{ animation: fast}
@@ -428,7 +434,10 @@ kanban
       window.callback = function () {
         alert('A callback was triggered');
       };
-      mermaid.initialize({
+      function callback() {
+        alert('It worked');
+      }
+      await mermaid.initialize({
         // theme: 'base',
         // theme: 'default',
         // theme: 'forest',
@@ -440,9 +449,11 @@ kanban
         // layout: 'fixed',
         // htmlLabels: false,
         flowchart: { titleTopMargin: 10 },
+
         // fontFamily: 'Caveat',
         // fontFamily: 'Kalam',
         // fontFamily: 'courier',
+        fontFamily: 'arial',
         sequence: {
           actorFontFamily: 'courier',
           noteFontFamily: 'courier',
@@ -454,10 +465,9 @@ kanban
         fontSize: 12,
         logLevel: 0,
         securityLevel: 'loose',
+        callback,
       });
-      function callback() {
-        alert('It worked');
-      }
+
       mermaid.parseError = function (err, hash) {
         console.error('In parse error:');
         console.error(err);
diff --git a/docs/config/setup/interfaces/mermaid.LayoutData.md b/docs/config/setup/interfaces/mermaid.LayoutData.md
index 552a16a8d..46c9134e8 100644
--- a/docs/config/setup/interfaces/mermaid.LayoutData.md
+++ b/docs/config/setup/interfaces/mermaid.LayoutData.md
@@ -20,7 +20,7 @@
 
 #### Defined in
 
-[packages/mermaid/src/rendering-util/types.ts:147](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L147)
+[packages/mermaid/src/rendering-util/types.ts:148](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L148)
 
 ---
 
@@ -30,7 +30,7 @@
 
 #### Defined in
 
-[packages/mermaid/src/rendering-util/types.ts:146](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L146)
+[packages/mermaid/src/rendering-util/types.ts:147](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L147)
 
 ---
 
@@ -40,4 +40,4 @@
 
 #### Defined in
 
-[packages/mermaid/src/rendering-util/types.ts:145](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L145)
+[packages/mermaid/src/rendering-util/types.ts:146](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L146)
diff --git a/packages/mermaid/src/diagrams/flowchart/flowDb.ts b/packages/mermaid/src/diagrams/flowchart/flowDb.ts
index e96384110..7074464dc 100644
--- a/packages/mermaid/src/diagrams/flowchart/flowDb.ts
+++ b/packages/mermaid/src/diagrams/flowchart/flowDb.ts
@@ -133,10 +133,10 @@ export class FlowDB implements DiagramDB {
     const edge = this.edges.find((e) => e.id === id);
     if (edge) {
       const edgeDoc = doc as EdgeMetaData;
-      if (edgeDoc?.animate) {
+      if (edgeDoc?.animate !== undefined) {
         edge.animate = edgeDoc.animate;
       }
-      if (edgeDoc?.animation) {
+      if (edgeDoc?.animation !== undefined) {
         edge.animation = edgeDoc.animation;
       }
       return;
@@ -251,6 +251,7 @@ export class FlowDB implements DiagramDB {
       text: '',
       labelType: 'text',
       classes: [],
+      isUserDefinedId: false,
     };
     log.info('abc78 Got edge...', edge);
     const linkTextObj = type.text;
@@ -270,8 +271,19 @@ export class FlowDB implements DiagramDB {
       edge.stroke = type.stroke;
       edge.length = type.length > 10 ? 10 : type.length;
     }
-    if (id) {
+    if (id && !this.edges.some((e) => e.id === id)) {
       edge.id = id;
+      edge.isUserDefinedId = true;
+    } else {
+      const existingLinks = this.edges.filter((e) => e.start === edge.start && e.end === edge.end);
+      if (existingLinks.length === 0) {
+        edge.id = getEdgeId(edge.start, edge.end, { counter: 0, prefix: 'L' });
+      } else {
+        edge.id = getEdgeId(edge.start, edge.end, {
+          counter: existingLinks.length + 1,
+          prefix: 'L',
+        });
+      }
     }
 
     if (this.edges.length < (this.config.maxEdges ?? 500)) {
@@ -302,9 +314,18 @@ You have to call mermaid.initialize.`
 
     log.info('addLink', _start, _end, id);
 
+    // for a group syntax like A e1@--> B & C, only the first edge should have an the userDefined id
+    // the rest of the edges should have auto generated ids
     for (const start of _start) {
       for (const end of _end) {
-        this.addSingleLink(start, end, linkData, id);
+        //use the id only for last node in _start and and first node in _end
+        const isLastStart = start === _start[_start.length - 1];
+        const isFirstEnd = end === _end[0];
+        if (isLastStart && isFirstEnd) {
+          this.addSingleLink(start, end, linkData, id);
+        } else {
+          this.addSingleLink(start, end, linkData, undefined);
+        }
       }
     }
   }
@@ -1077,6 +1098,7 @@ You have to call mermaid.initialize.`
       }
       const edge: Edge = {
         id: getEdgeId(rawEdge.start, rawEdge.end, { counter: index, prefix: 'L' }, rawEdge.id),
+        isUserDefinedId: rawEdge.isUserDefinedId,
         start: rawEdge.start,
         end: rawEdge.end,
         type: rawEdge.type ?? 'normal',
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-node-data.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-node-data.spec.js
index 284f1b225..60620d8b2 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/flow-node-data.spec.js
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-node-data.spec.js
@@ -251,7 +251,7 @@ describe('when parsing directions', function () {
     expect(data4Layout.nodes[0].shape).toEqual('squareRect');
     expect(data4Layout.nodes[0].label).toEqual('This is a
multiline string'); }); - it(' should be possible to use } in strings', function () { + it('should be possible to use } in strings', function () { const res = flow.parser.parse(`flowchart TB A@{ label: "This is a string with }" @@ -264,7 +264,7 @@ describe('when parsing directions', function () { expect(data4Layout.nodes[0].shape).toEqual('squareRect'); expect(data4Layout.nodes[0].label).toEqual('This is a string with }'); }); - it(' should be possible to use @ in strings', function () { + it('should be possible to use @ in strings', function () { const res = flow.parser.parse(`flowchart TB A@{ label: "This is a string with @" @@ -277,7 +277,7 @@ describe('when parsing directions', function () { expect(data4Layout.nodes[0].shape).toEqual('squareRect'); expect(data4Layout.nodes[0].label).toEqual('This is a string with @'); }); - it(' should be possible to use @ in strings', function () { + it('should be possible to use @ in strings', function () { const res = flow.parser.parse(`flowchart TB A@{ label: "This is a string with}" @@ -291,7 +291,7 @@ describe('when parsing directions', function () { expect(data4Layout.nodes[0].label).toEqual('This is a string with}'); }); - it(' should be possible to use @ syntax to add labels on multi nodes', function () { + it('should be possible to use @ syntax to add labels on multi nodes', function () { const res = flow.parser.parse(`flowchart TB n2["label for n2"] & n4@{ label: "labe for n4"} & n5@{ label: "labe for n5"} `); @@ -317,7 +317,90 @@ describe('when parsing directions', function () { expect(data4Layout.nodes[3].label).toEqual('for E'); expect(data4Layout.nodes[4].label).toEqual('for D'); }); - it.skip(' should be possible to use @ syntax to add labels with trail spaces', function () { + + it('should be possible to use @ syntax in labels', function () { + const res = flow.parser.parse(`flowchart TD + A["@A@"] --> B["@for@ B@"] & C@{ label: "@for@ c@"} & E{"\`@for@ E@\`"} & D(("@for@ D@")) + H1{{"@for@ H@"}} + H2{{"\`@for@ H@\`"}} + Q1{"@for@ Q@"} + Q2{"\`@for@ Q@\`"} + AS1>"@for@ AS@"] + AS2>"\`@for@ AS@\`"] + `); + + const data4Layout = flow.parser.yy.getData(); + expect(data4Layout.nodes.length).toBe(11); + expect(data4Layout.nodes[0].label).toEqual('@A@'); + expect(data4Layout.nodes[1].label).toEqual('@for@ B@'); + expect(data4Layout.nodes[2].label).toEqual('@for@ c@'); + expect(data4Layout.nodes[3].label).toEqual('@for@ E@'); + expect(data4Layout.nodes[4].label).toEqual('@for@ D@'); + expect(data4Layout.nodes[5].label).toEqual('@for@ H@'); + expect(data4Layout.nodes[6].label).toEqual('@for@ H@'); + expect(data4Layout.nodes[7].label).toEqual('@for@ Q@'); + expect(data4Layout.nodes[8].label).toEqual('@for@ Q@'); + expect(data4Layout.nodes[9].label).toEqual('@for@ AS@'); + expect(data4Layout.nodes[10].label).toEqual('@for@ AS@'); + }); + + it('should handle unique edge creation with using @ and &', function () { + const res = flow.parser.parse(`flowchart TD + A & B e1@--> C & D + A1 e2@--> C1 & D1 + `); + + const data4Layout = flow.parser.yy.getData(); + expect(data4Layout.nodes.length).toBe(7); + expect(data4Layout.edges.length).toBe(6); + expect(data4Layout.edges[0].id).toEqual('L_A_C_0'); + expect(data4Layout.edges[1].id).toEqual('L_A_D_0'); + expect(data4Layout.edges[2].id).toEqual('e1'); + expect(data4Layout.edges[3].id).toEqual('L_B_D_0'); + expect(data4Layout.edges[4].id).toEqual('e2'); + expect(data4Layout.edges[5].id).toEqual('L_A1_D1_0'); + }); + + it('should handle redefine same edge ids again', function () { + const res = flow.parser.parse(`flowchart TD + A & B e1@--> C & D + A1 e1@--> C1 & D1 + `); + + const data4Layout = flow.parser.yy.getData(); + expect(data4Layout.nodes.length).toBe(7); + expect(data4Layout.edges.length).toBe(6); + expect(data4Layout.edges[0].id).toEqual('L_A_C_0'); + expect(data4Layout.edges[1].id).toEqual('L_A_D_0'); + expect(data4Layout.edges[2].id).toEqual('e1'); + expect(data4Layout.edges[3].id).toEqual('L_B_D_0'); + expect(data4Layout.edges[4].id).toEqual('L_A1_C1_0'); + expect(data4Layout.edges[5].id).toEqual('L_A1_D1_0'); + }); + + it('should handle overriding edge animate again', function () { + const res = flow.parser.parse(`flowchart TD + A e1@--> B + C e2@--> D + E e3@--> F + e1@{ animate: true } + e2@{ animate: false } + e3@{ animate: true } + e3@{ animate: false } + `); + + const data4Layout = flow.parser.yy.getData(); + expect(data4Layout.nodes.length).toBe(6); + expect(data4Layout.edges.length).toBe(3); + expect(data4Layout.edges[0].id).toEqual('e1'); + expect(data4Layout.edges[0].animate).toEqual(true); + expect(data4Layout.edges[1].id).toEqual('e2'); + expect(data4Layout.edges[1].animate).toEqual(false); + expect(data4Layout.edges[2].id).toEqual('e3'); + expect(data4Layout.edges[2].animate).toEqual(false); + }); + + it.skip('should be possible to use @ syntax to add labels with trail spaces', function () { const res = flow.parser.parse( `flowchart TB n2["label for n2"] & n4@{ label: "labe for n4"} & n5@{ label: "labe for n5"} ` diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow.jison b/packages/mermaid/src/diagrams/flowchart/parser/flow.jison index c3ac2b04b..7b2f4386a 100644 --- a/packages/mermaid/src/diagrams/flowchart/parser/flow.jison +++ b/packages/mermaid/src/diagrams/flowchart/parser/flow.jison @@ -141,7 +141,7 @@ that id. .*direction\s+RL[^\n]* return 'direction_rl'; .*direction\s+LR[^\n]* return 'direction_lr'; -[^\s]+\@(?=[^\{]) { return 'LINK_ID'; } +[^\s\"]+\@(?=[^\{\"]) { return 'LINK_ID'; } [0-9]+ return 'NUM'; \# return 'BRKT'; ":::" return 'STYLE_SEPARATOR'; diff --git a/packages/mermaid/src/diagrams/flowchart/types.ts b/packages/mermaid/src/diagrams/flowchart/types.ts index 00acb6751..54156091b 100644 --- a/packages/mermaid/src/diagrams/flowchart/types.ts +++ b/packages/mermaid/src/diagrams/flowchart/types.ts @@ -53,6 +53,7 @@ export interface FlowText { } export interface FlowEdge { + isUserDefinedId: boolean; start: string; end: string; interpolate?: string; diff --git a/packages/mermaid/src/diagrams/gantt/styles.js b/packages/mermaid/src/diagrams/gantt/styles.js index 626ed4e0f..5b53a1b07 100644 --- a/packages/mermaid/src/diagrams/gantt/styles.js +++ b/packages/mermaid/src/diagrams/gantt/styles.js @@ -1,7 +1,7 @@ const getStyles = (options) => ` .mermaid-main-font { - font-family: var(--mermaid-font-family, "trebuchet ms", verdana, arial, sans-serif); + font-family: ${options.fontFamily}; } .exclude-range { @@ -45,7 +45,7 @@ const getStyles = (options) => .sectionTitle { text-anchor: start; - font-family: var(--mermaid-font-family, "trebuchet ms", verdana, arial, sans-serif); + font-family: ${options.fontFamily}; } @@ -86,13 +86,13 @@ const getStyles = (options) => .taskText { text-anchor: middle; - font-family: var(--mermaid-font-family, "trebuchet ms", verdana, arial, sans-serif); + font-family: ${options.fontFamily}; } .taskTextOutsideRight { fill: ${options.taskTextDarkColor}; text-anchor: start; - font-family: var(--mermaid-font-family, "trebuchet ms", verdana, arial, sans-serif); + font-family: ${options.fontFamily}; } .taskTextOutsideLeft { @@ -248,7 +248,7 @@ const getStyles = (options) => text-anchor: middle; font-size: 18px; fill: ${options.titleColor || options.textColor}; - font-family: var(--mermaid-font-family, "trebuchet ms", verdana, arial, sans-serif); + font-family: ${options.fontFamily}; } `; diff --git a/packages/mermaid/src/diagrams/sankey/sankeyDiagram.ts b/packages/mermaid/src/diagrams/sankey/sankeyDiagram.ts index 6fed435ac..54aa40a78 100644 --- a/packages/mermaid/src/diagrams/sankey/sankeyDiagram.ts +++ b/packages/mermaid/src/diagrams/sankey/sankeyDiagram.ts @@ -4,11 +4,13 @@ import parser from './parser/sankey.jison'; import db from './sankeyDB.js'; import renderer from './sankeyRenderer.js'; import { prepareTextForParsing } from './sankeyUtils.js'; +import sankeyStyles from './styles.js'; const originalParse = parser.parse.bind(parser); parser.parse = (text: string) => originalParse(prepareTextForParsing(text)); export const diagram: DiagramDefinition = { + styles: sankeyStyles, parser, db, renderer, diff --git a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts index a981a346e..5e3f04173 100644 --- a/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts +++ b/packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts @@ -136,7 +136,6 @@ export const draw = function (text: string, id: string, _version: string, diagOb svg .append('g') .attr('class', 'node-labels') - .attr('font-family', 'sans-serif') .attr('font-size', 14) .selectAll('text') .data(graph.nodes) diff --git a/packages/mermaid/src/diagrams/sankey/styles.js b/packages/mermaid/src/diagrams/sankey/styles.js new file mode 100644 index 000000000..eafb62b05 --- /dev/null +++ b/packages/mermaid/src/diagrams/sankey/styles.js @@ -0,0 +1,6 @@ +const getStyles = (options) => + `.label { + font-family: ${options.fontFamily}; + }`; + +export default getStyles; diff --git a/packages/mermaid/src/diagrams/user-journey/styles.js b/packages/mermaid/src/diagrams/user-journey/styles.js index 998a73894..9cdbcd12e 100644 --- a/packages/mermaid/src/diagrams/user-journey/styles.js +++ b/packages/mermaid/src/diagrams/user-journey/styles.js @@ -1,7 +1,6 @@ const getStyles = (options) => `.label { - font-family: 'trebuchet ms', verdana, arial, sans-serif; - font-family: var(--mermaid-font-family); + font-family: ${options.fontFamily}; color: ${options.textColor}; } .mouth { @@ -79,8 +78,7 @@ const getStyles = (options) => text-align: center; max-width: 200px; padding: 2px; - font-family: 'trebuchet ms', verdana, arial, sans-serif; - font-family: var(--mermaid-font-family); + font-family: ${options.fontFamily}; font-size: 12px; background: ${options.tertiaryColor}; border: 1px solid ${options.border2}; diff --git a/packages/mermaid/src/rendering-util/types.ts b/packages/mermaid/src/rendering-util/types.ts index 1f84c66c3..b11d2f314 100644 --- a/packages/mermaid/src/rendering-util/types.ts +++ b/packages/mermaid/src/rendering-util/types.ts @@ -125,6 +125,7 @@ export interface Edge { pattern?: string; thickness?: 'normal' | 'thick' | 'invisible' | 'dotted'; look?: string; + isUserDefinedId?: boolean; } export interface RectOptions {