From 5cfa919672ebeaaf5b45bc21a32d7cf49dd8cba2 Mon Sep 17 00:00:00 2001 From: Alois Klink Date: Sun, 27 Nov 2022 18:19:13 +0000 Subject: [PATCH 1/3] fix: support parsing indented mmd YAML from HTML In order to parse the YAML front-matter in a Mermaid diagram, the YAML seperators **MUST NOT** be indented, e.g.: ````markdown ```mermaid --- title: This is fine. --- ``` ```mermaid --- title: This is not fine, because the `---` are indented. --- ``` ```` However, this makes it very difficult to write nice Mermaid diagrams in HTML code-blocks. This commit uses [`ts-dedent`](https://www.npmjs.com/package/ts-dedent) to automatically remove the indentation from Mermaid diagrams when parsed from HTML. Mermaid diagrams from mermaidAPI.render() are **NOT** dedented, as that API is called from JavaScript code, and therefore users can easily `dedent` their own diagrams. --- packages/mermaid/package.json | 1 + packages/mermaid/src/mermaid.ts | 8 ++++---- pnpm-lock.yaml | 16 +++++++++++----- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/packages/mermaid/package.json b/packages/mermaid/package.json index 4b24fb661f..ac905fd06d 100644 --- a/packages/mermaid/package.json +++ b/packages/mermaid/package.json @@ -64,6 +64,7 @@ "moment-mini": "^2.24.0", "non-layered-tidy-tree-layout": "^2.0.2", "stylis": "^4.1.2", + "ts-dedent": "^2.2.0", "uuid": "^9.0.0" }, "devDependencies": { diff --git a/packages/mermaid/src/mermaid.ts b/packages/mermaid/src/mermaid.ts index 993f2f9443..b0a7e4c3e8 100644 --- a/packages/mermaid/src/mermaid.ts +++ b/packages/mermaid/src/mermaid.ts @@ -2,6 +2,8 @@ * Web page integration module for the mermaid framework. It uses the mermaidAPI for mermaid * functionality and to render the diagrams to svg code! */ +import dedent from 'ts-dedent'; + import { MermaidConfig } from './config.type'; import { log } from './logger'; import utils from './utils'; @@ -147,8 +149,7 @@ const initThrowsErrors = function ( txt = element.innerHTML; // transforms the html to pure text - txt = utils - .entityDecode(txt) + txt = dedent(utils.entityDecode(txt)) // removes indentation, required for YAML parsing .trim() .replace(//gi, '
'); @@ -288,8 +289,7 @@ const initThrowsErrorsAsync = async function ( txt = element.innerHTML; // transforms the html to pure text - txt = utils - .entityDecode(txt) + txt = dedent(utils.entityDecode(txt)) // removes indentation, required for YAML parsing .trim() .replace(//gi, '
'); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ebb71f65ee..cc91b753ce 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -211,6 +211,9 @@ importers: stylis: specifier: ^4.1.2 version: 4.1.2 + ts-dedent: + specifier: ^2.2.0 + version: 2.2.0 uuid: specifier: ^9.0.0 version: 9.0.0 @@ -3634,7 +3637,7 @@ packages: /axios/0.21.4_debug@4.3.2: resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==} dependencies: - follow-redirects: 1.15.2_debug@4.3.2 + follow-redirects: 1.15.2 transitivePeerDependencies: - debug dev: true @@ -6309,7 +6312,7 @@ packages: resolution: {integrity: sha512-XGozTsMPYkm+6b5QL3Z9wQcJjNYxp0CYn3U1gO7dwD6PAqU1SVWZxI9CCg3z+ml3YfqdPnrBehaBrnH2AGKbNA==} dev: true - /follow-redirects/1.15.2_debug@4.3.2: + /follow-redirects/1.15.2: resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} engines: {node: '>=4.0'} peerDependencies: @@ -6317,8 +6320,6 @@ packages: peerDependenciesMeta: debug: optional: true - dependencies: - debug: 4.3.2 dev: true /foreground-child/2.0.0: @@ -6911,7 +6912,7 @@ packages: engines: {node: '>=8.0.0'} dependencies: eventemitter3: 4.0.7 - follow-redirects: 1.15.2_debug@4.3.2 + follow-redirects: 1.15.2 requires-port: 1.0.0 transitivePeerDependencies: - debug @@ -10613,6 +10614,11 @@ packages: resolution: {integrity: sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==} dev: true + /ts-dedent/2.2.0: + resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} + engines: {node: '>=6.10'} + dev: false + /ts-node/10.9.1_cbe7ovvae6zqfnmtgctpgpys54: resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} hasBin: true From 4cc3b17d362b3c8edff9ac0ab7d7248a309fa988 Mon Sep 17 00:00:00 2001 From: Alois Klink Date: Sun, 27 Nov 2022 18:25:37 +0000 Subject: [PATCH 2/3] docs(demos): fix indentation of YAML front-matter Mermaid diagrams that have YAML front-matter can now be indented in HTML code, see commit: 5cfa9196 (fix: support parsing indented mmd YAML from HTML, 2022-11-27) Some diagrams previously had a mix of tabs/spaces for indentation. In order for `dedent` to work, these diagrams had to be converted to using a consistent indentation. --- demos/classchart.html | 8 +++--- demos/er.html | 61 +++++++++++++++++++++---------------------- demos/flowchart.html | 12 ++++----- demos/git.html | 6 ++--- demos/journey.html | 6 ++--- demos/state.html | 20 +++++++------- 6 files changed, 56 insertions(+), 57 deletions(-) diff --git a/demos/classchart.html b/demos/classchart.html index 031f3b608f..b879618b62 100644 --- a/demos/classchart.html +++ b/demos/classchart.html @@ -17,10 +17,10 @@

Class diagram demos

----
-title: Demo Class Diagram
----
-		classDiagram
+    ---
+    title: Demo Class Diagram
+    ---
+    classDiagram
       accTitle: Demo Class Diagram
       accDescr: This class diagram show the abstract Animal class, and 3 classes that inherit from it: Duck, Fish, and Zebra.
 
diff --git a/demos/er.html b/demos/er.html
index 06fbf020e7..b5d1966f72 100644
--- a/demos/er.html
+++ b/demos/er.html
@@ -19,43 +19,42 @@
 
   
     
+      ---
+      title: This is a title
+      ---
+      erDiagram
+        %% title This is a title
+        %% accDescription Test a description
 
----
-title: This is a title
----
-erDiagram
-  %% title This is a title
-  %% accDescription Test a description
+        "Person . CUSTOMER"||--o{ ORDER : places
 
-  "Person . CUSTOMER"||--o{ ORDER : places
+        ORDER ||--|{ "€£LINE_ITEM ¥" : contains
 
-  ORDER ||--|{ "€£LINE_ITEM ¥" : contains
+        "Person . CUSTOMER" }|..|{ "Address//StreetAddress::[DELIVERY ADDRESS]" : uses
 
-  "Person . CUSTOMER" }|..|{ "Address//StreetAddress::[DELIVERY ADDRESS]" : uses
+        "Address//StreetAddress::[DELIVERY ADDRESS]" {
+            int customerID FK
+            string line1 "this is the first address line comment"
+            string line2
+            string city
+            string region
+            string state
+            string postal_code
+            string country
+            }
 
-  "Address//StreetAddress::[DELIVERY ADDRESS]" {
-      int customerID FK
-      string line1 "this is the first address line comment"
-      string line2
-      string city
-      string region
-      string state
-      string postal_code
-      string country
-      }
-
-      "a_~`!@#$^&*()-_=+[]{}|/;:'.?¡⁄™€£‹¢›∞fi§‡•°ª·º‚≠±œŒ∑„®†ˇ¥Á¨ˆˆØπ∏“«»åÅßÍ∂΃ϩ˙Ó∆Ô˚¬Ò…ÚæÆΩ¸≈π˛çÇ√◊∫ı˜µÂ≤¯≥˘÷¿" {
-        string name "this is an entity with an absurd name just to show characters that are now acceptable as long as the name is in double quotes"
-      }
+            "a_~`!@#$^&*()-_=+[]{}|/;:'.?¡⁄™€£‹¢›∞fi§‡•°ª·º‚≠±œŒ∑„®†ˇ¥Á¨ˆˆØπ∏“«»åÅßÍ∂΃ϩ˙Ó∆Ô˚¬Ò…ÚæÆΩ¸≈π˛çÇ√◊∫ı˜µÂ≤¯≥˘÷¿" {
+              string name "this is an entity with an absurd name just to show characters that are now acceptable as long as the name is in double quotes"
+            }
 
-      "€£LINE_ITEM ¥" {
-        int orderID FK
-        int currencyId FK
-        number price
-        number quantity
-        number adjustment
-        number final_price
-      }
+            "€£LINE_ITEM ¥" {
+              int orderID FK
+              int currencyId FK
+              number price
+              number quantity
+              number adjustment
+              number final_price
+            }
     
diff --git a/demos/flowchart.html b/demos/flowchart.html index 7251e586e5..60e6160c38 100644 --- a/demos/flowchart.html +++ b/demos/flowchart.html @@ -17,9 +17,9 @@

Comparison "graph vs. flowchart"

Sample 1

graph

----
-title: This is a complicated flow
----
+    ---
+    title: This is a complicated flow
+    ---
     graph LR
       accTitle: This is a complicated flow
       accDescr: This is the descriptoin for the complicated flow.
@@ -224,9 +224,9 @@ 

flowchart

Sample 2

graph

----
-title: What to buy
----
+    ---
+    title: What to buy
+    ---
     graph TD
       accTitle: What to buy
       accDescr: Options of what to buy with Christmas money
diff --git a/demos/git.html b/demos/git.html
index 99c53d7d0a..5e683152aa 100644
--- a/demos/git.html
+++ b/demos/git.html
@@ -16,9 +16,9 @@
   
     

Git diagram demo

----
-title: Simple Git diagram
----
+    ---
+    title: Simple Git diagram
+    ---
     gitGraph:
     options
     {
diff --git a/demos/journey.html b/demos/journey.html
index dadcfb13c0..96c89a2151 100644
--- a/demos/journey.html
+++ b/demos/journey.html
@@ -16,9 +16,9 @@
   
     

Journey diagram demo

----
-title: My working day 
----
+    ---
+    title: My working day 
+    ---
      journey
       accTitle: Very simple journey demo
       accDescr: 2 main sections: work and home, each with just a few tasks
diff --git a/demos/state.html b/demos/state.html
index 3d070f3795..9251e837cb 100644
--- a/demos/state.html
+++ b/demos/state.html
@@ -17,11 +17,11 @@
     

State diagram demos

Very simple showing change from State1 to State2

----
-title: Very simple diagram
----
-		stateDiagram
-		  accTitle: This is the accessible title
+    ---
+    title: Very simple diagram
+    ---
+    stateDiagram
+      accTitle: This is the accessible title
       accDescr:This is an accessible description
       State1 --> State2
     
@@ -47,13 +47,13 @@

And these are how they are applied:

----
-title: Very simple diagram
----
-		stateDiagram
+    ---
+    title: Very simple diagram
+    ---
+    stateDiagram
       direction TB
 
-		  accTitle: This is the accessible title
+      accTitle: This is the accessible title
       accDescr: This is an accessible description
 
       classDef notMoving fill:white

From accba3f408d1f1a9a68a05fff9899e2d51627fe7 Mon Sep 17 00:00:00 2001
From: Alois Klink 
Date: Thu, 1 Dec 2022 22:43:03 +0000
Subject: [PATCH 3/3] chore: improve errors for bad YAML frontmatter

Adds a custom error message for any mermaid diagram that starts with
a `---`. Normally, these are expected to be part of a YAML front-matter
block, but indentation issues or a missing closing `---` may cause
these to be not parsed correctly.
---
 .../src/diagram-api/diagram-orchestration.ts  | 27 +++++++++++++++++++
 packages/mermaid/src/mermaidAPI.spec.ts       | 13 +++++++++
 packages/mermaid/src/utils.spec.js            |  4 +--
 3 files changed, 42 insertions(+), 2 deletions(-)

diff --git a/packages/mermaid/src/diagram-api/diagram-orchestration.ts b/packages/mermaid/src/diagram-api/diagram-orchestration.ts
index a26edb3031..6c7ab69074 100644
--- a/packages/mermaid/src/diagram-api/diagram-orchestration.ts
+++ b/packages/mermaid/src/diagram-api/diagram-orchestration.ts
@@ -125,6 +125,33 @@ export const addDiagrams = () => {
     },
     (text) => text.toLowerCase().trim() === 'error'
   );
+  registerDiagram(
+    '---',
+    // --- diagram type may appear if YAML front-matter is not parsed correctly
+    {
+      db: {
+        clear: () => {
+          // Quite ok, clear needs to be there for --- to work as a regular diagram
+        },
+      },
+      styles: errorStyles, // should never be used
+      renderer: errorRenderer, // should never be used
+      parser: {
+        parser: { yy: {} },
+        parse: () => {
+          throw new Error(
+            'Diagrams beginning with --- are not valid. ' +
+              'If you were trying to use a YAML front-matter, please ensure that ' +
+              "you've correctly opened and closed the YAML front-matter with unindented `---` blocks"
+          );
+        },
+      },
+      init: () => null, // no op
+    },
+    (text) => {
+      return text.toLowerCase().trimStart().startsWith('---');
+    }
+  );
 
   registerDiagram(
     'c4',
diff --git a/packages/mermaid/src/mermaidAPI.spec.ts b/packages/mermaid/src/mermaidAPI.spec.ts
index 55d46ae7c6..092661dc6c 100644
--- a/packages/mermaid/src/mermaidAPI.spec.ts
+++ b/packages/mermaid/src/mermaidAPI.spec.ts
@@ -629,6 +629,19 @@ describe('mermaidAPI', function () {
       expect(mermaid.parseError).toEqual(undefined);
       expect(() => mermaidAPI.parse('this is not a mermaid diagram definition')).toThrow();
     });
+    it('throws for a nicer error for a invalid definition starting with `---`', function () {
+      expect(mermaid.parseError).toEqual(undefined);
+      expect(() =>
+        mermaidAPI.parse(`
+      ---
+      title: a malformed YAML front-matter
+      `)
+      ).toThrow(
+        'Diagrams beginning with --- are not valid. ' +
+          'If you were trying to use a YAML front-matter, please ensure that ' +
+          "you've correctly opened and closed the YAML front-matter with unindented `---` blocks"
+      );
+    });
     it('does not throw for a valid definition', function () {
       expect(() => mermaidAPI.parse('graph TD;A--x|text including URL space|B;')).not.toThrow();
     });
diff --git a/packages/mermaid/src/utils.spec.js b/packages/mermaid/src/utils.spec.js
index 54262f10ee..bdf94d9926 100644
--- a/packages/mermaid/src/utils.spec.js
+++ b/packages/mermaid/src/utils.spec.js
@@ -238,9 +238,9 @@ Alice->Bob: hi`;
     const type = detectType(str);
     expect(type).toBe('gitGraph');
   });
-  it('should not allow frontmatter with leading spaces', function () {
+  it('should handle malformed frontmatter (with leading spaces) with `---` error graphtype', function () {
     const str = '    ---\ntitle: foo\n---\n  gitGraph TB:\nbfs1:queue';
-    expect(() => detectType(str)).toThrow('No diagram type detected for text');
+    expect(detectType(str)).toBe('---');
   });
 });
 describe('when finding substring in array ', function () {