diff --git a/cypress/integration/rendering/gantt.spec.js b/cypress/integration/rendering/gantt.spec.js index 73ff4ee00..4abde9d44 100644 --- a/cypress/integration/rendering/gantt.spec.js +++ b/cypress/integration/rendering/gantt.spec.js @@ -583,4 +583,106 @@ describe('Gantt diagram', () => { {} ); }); + + it("should render when there's a semicolon in the title", () => { + imgSnapshotTest( + ` + gantt + title ;Gantt With a Semicolon in the Title + dateFormat YYYY-MM-DD + section Section + A task :a1, 2014-01-01, 30d + Another task :after a1 , 20d + section Another + Task in sec :2014-01-12 , 12d + another task : 24d + `, + {} + ); + }); + + it("should render when there's a semicolon in a section is true", () => { + imgSnapshotTest( + ` + gantt + title Gantt Digram + dateFormat YYYY-MM-DD + section ;Section With a Semicolon + A task :a1, 2014-01-01, 30d + Another task :after a1 , 20d + section Another + Task in sec :2014-01-12 , 12d + another task : 24d + `, + {} + ); + }); + + it("should render when there's a semicolon in the task data", () => { + imgSnapshotTest( + ` + gantt + title Gantt Digram + dateFormat YYYY-MM-DD + section Section + ;A task with a semiclon :a1, 2014-01-01, 30d + Another task :after a1 , 20d + section Another + Task in sec :2014-01-12 , 12d + another task : 24d + `, + {} + ); + }); + + it("should render when there's a hashtag in the title", () => { + imgSnapshotTest( + ` + gantt + title #Gantt With a Hashtag in the Title + dateFormat YYYY-MM-DD + section Section + A task :a1, 2014-01-01, 30d + Another task :after a1 , 20d + section Another + Task in sec :2014-01-12 , 12d + another task : 24d + `, + {} + ); + }); + + it("should render when there's a hashtag in a section is true", () => { + imgSnapshotTest( + ` + gantt + title Gantt Digram + dateFormat YYYY-MM-DD + section #Section With a Hashtag + A task :a1, 2014-01-01, 30d + Another task :after a1 , 20d + section Another + Task in sec :2014-01-12 , 12d + another task : 24d + `, + {} + ); + }); + + it("should render when there's a hashtag in the task data", () => { + imgSnapshotTest( + ` + gantt + title Gantt Digram + dateFormat YYYY-MM-DD + section Section + #A task with a hashtag :a1, 2014-01-01, 30d + Another task :after a1 , 20d + section Another + Task in sec :2014-01-12 , 12d + another task : 24d + `, + {} + ); + }); }); diff --git a/cypress/integration/rendering/sequencediagram.spec.js b/cypress/integration/rendering/sequencediagram.spec.js index 27e03da9c..10432f057 100644 --- a/cypress/integration/rendering/sequencediagram.spec.js +++ b/cypress/integration/rendering/sequencediagram.spec.js @@ -792,6 +792,34 @@ context('Sequence diagram', () => { }); }); context('links', () => { + it('should support actor links', () => { + renderGraph( + ` + sequenceDiagram + link Alice: Dashboard @ https://dashboard.contoso.com/alice + link Alice: Wiki @ https://wiki.contoso.com/alice + link John: Dashboard @ https://dashboard.contoso.com/john + link John: Wiki @ https://wiki.contoso.com/john + Alice->>John: Hello John
+ John-->>Alice: Great

day! + `, + { securityLevel: 'loose' } + ); + cy.get('#actor0_popup').should((popupMenu) => { + const style = popupMenu.attr('style'); + expect(style).to.undefined; + }); + cy.get('#root-0').click(); + cy.get('#actor0_popup').should((popupMenu) => { + const style = popupMenu.attr('style'); + expect(style).to.match(/^display: block;$/); + }); + cy.get('#root-0').click(); + cy.get('#actor0_popup').should((popupMenu) => { + const style = popupMenu.attr('style'); + expect(style).to.match(/^display: none;$/); + }); + }); it('should support actor links and properties EXPERIMENTAL: USE WITH CAUTION', () => { //Be aware that the syntax for "properties" is likely to be changed. imgSnapshotTest( diff --git a/demos/gantt.html b/demos/gantt.html index 88f52ef5c..9c82371ab 100644 --- a/demos/gantt.html +++ b/demos/gantt.html @@ -30,6 +30,21 @@
+
+      gantt
+        title #; Gantt Diagrams Allow Semicolons and Hashtags #;!
+        accTitle: A simple sample gantt diagram
+        accDescr: 2 sections with 2 tasks each, from 2014
+        dateFormat  YYYY-MM-DD
+        section #;Section
+        #;A task           :a1, 2014-01-01, 30d
+        #;Another task     :after a1  , 20d
+        section #;Another
+        Task in sec      :2014-01-12  , 12d
+        another task      : 24d
+    
+
+
     gantt
       title Airworks roadmap
diff --git a/demos/sequence.html b/demos/sequence.html
index b2733a384..8eecae610 100644
--- a/demos/sequence.html
+++ b/demos/sequence.html
@@ -23,6 +23,10 @@
 			participant Alice
 			participant Bob
 			participant John as John
Second Line + link Alice: Dashboard @ https://dashboard.contoso.com/alice + link Alice: Wiki @ https://wiki.contoso.com/alice + link John: Dashboard @ https://dashboard.contoso.com/john + link John: Wiki @ https://wiki.contoso.com/john autonumber 10 10 rect rgb(200, 220, 100) rect rgb(200, 255, 200) @@ -62,6 +66,26 @@

+    ---
+    title: With forced menus
+    config:
+      sequence:
+        forceMenus: true
+    ---
+    sequenceDiagram
+      participant Alice
+      participant John
+      link Alice: Dashboard @ https://dashboard.contoso.com/alice
+      link Alice: Wiki @ https://wiki.contoso.com/alice
+      link John: Dashboard @ https://dashboard.contoso.com/john
+      link John: Wiki @ https://wiki.contoso.com/john
+      Alice->>John: Hello John, how are you?
+      John-->>Alice: Great!
+      Alice-)John: See you later!
+  
+
+
     sequenceDiagram
       accTitle: Sequence diagram title is here
       accDescr: Hello friends
diff --git a/packages/mermaid/src/diagrams/common/common.ts b/packages/mermaid/src/diagrams/common/common.ts
index caf43bc68..8609a175a 100644
--- a/packages/mermaid/src/diagrams/common/common.ts
+++ b/packages/mermaid/src/diagrams/common/common.ts
@@ -18,13 +18,18 @@ export const getRows = (s?: string): string[] => {
   return str.split('#br#');
 };
 
-/**
- * Removes script tags from a text
- *
- * @param txt - The text to sanitize
- * @returns The safer text
- */
-export const removeScript = (txt: string): string => {
+const setupDompurifyHooksIfNotSetup = (() => {
+  let setup = false;
+
+  return () => {
+    if (!setup) {
+      setupDompurifyHooks();
+      setup = true;
+    }
+  };
+})();
+
+function setupDompurifyHooks() {
   const TEMPORARY_ATTRIBUTE = 'data-temp-href-target';
 
   DOMPurify.addHook('beforeSanitizeAttributes', (node: Element) => {
@@ -33,8 +38,6 @@ export const removeScript = (txt: string): string => {
     }
   });
 
-  const sanitizedText = DOMPurify.sanitize(txt);
-
   DOMPurify.addHook('afterSanitizeAttributes', (node: Element) => {
     if (node.tagName === 'A' && node.hasAttribute(TEMPORARY_ATTRIBUTE)) {
       node.setAttribute('target', node.getAttribute(TEMPORARY_ATTRIBUTE) || '');
@@ -44,6 +47,18 @@ export const removeScript = (txt: string): string => {
       }
     }
   });
+}
+
+/**
+ * Removes script tags from a text
+ *
+ * @param txt - The text to sanitize
+ * @returns The safer text
+ */
+export const removeScript = (txt: string): string => {
+  setupDompurifyHooksIfNotSetup();
+
+  const sanitizedText = DOMPurify.sanitize(txt);
 
   return sanitizedText;
 };
diff --git a/packages/mermaid/src/diagrams/gantt/parser/gantt.jison b/packages/mermaid/src/diagrams/gantt/parser/gantt.jison
index b4daab5dc..d6027fee9 100644
--- a/packages/mermaid/src/diagrams/gantt/parser/gantt.jison
+++ b/packages/mermaid/src/diagrams/gantt/parser/gantt.jison
@@ -27,11 +27,10 @@ accDescr\s*"{"\s*                                { this.begin("acc_descr_multili
 
 \%\%(?!\{)*[^\n]*                                               /* skip comments */
 [^\}]\%\%*[^\n]*                                                /* skip comments */
-\%\%*[^\n]*[\n]*           /* do nothing */
+\%\%*[^\n]*[\n]*                                                /* do nothing */
 
 [\n]+                   return 'NL';
 \s+                     /* skip whitespace */
-\#[^\n]*                /* skip comments */
 \%%[^\n]*               /* skip comments */
 
 /*
@@ -86,10 +85,10 @@ weekday\s+friday                return 'weekday_friday'
 weekday\s+saturday              return 'weekday_saturday'
 weekday\s+sunday                return 'weekday_sunday'
 \d\d\d\d"-"\d\d"-"\d\d          return 'date';
-"title"\s[^#\n;]+               return 'title';
+"title"\s[^\n]+               return 'title';
 "accDescription"\s[^#\n;]+      return 'accDescription'
-"section"\s[^#:\n;]+            return 'section';
-[^#:\n;]+                       return 'taskTxt';
+"section"\s[^\n]+            return 'section';
+[^:\n]+                       return 'taskTxt';
 ":"[^#\n;]+                     return 'taskData';
 ":"                             return ':';
 <>                         return 'EOF';
diff --git a/packages/mermaid/src/diagrams/gantt/parser/gantt.spec.js b/packages/mermaid/src/diagrams/gantt/parser/gantt.spec.js
index e7ce1ffa4..ae5f74249 100644
--- a/packages/mermaid/src/diagrams/gantt/parser/gantt.spec.js
+++ b/packages/mermaid/src/diagrams/gantt/parser/gantt.spec.js
@@ -28,8 +28,12 @@ describe('when parsing a gantt diagram it', function () {
   });
   it('should handle a title definition', function () {
     const str = 'gantt\ndateFormat yyyy-mm-dd\ntitle Adding gantt diagram functionality to mermaid';
+    const semi = 'gantt\ndateFormat yyyy-mm-dd\ntitle ;Gantt diagram titles support semicolons';
+    const hash = 'gantt\ndateFormat yyyy-mm-dd\ntitle #Gantt diagram titles support hashtags';
 
     expect(parserFnConstructor(str)).not.toThrow();
+    expect(parserFnConstructor(semi)).not.toThrow();
+    expect(parserFnConstructor(hash)).not.toThrow();
   });
   it('should handle an excludes definition', function () {
     const str =
@@ -53,7 +57,23 @@ describe('when parsing a gantt diagram it', function () {
       'excludes weekdays 2019-02-01\n' +
       'section Documentation';
 
+    const semi =
+      'gantt\n' +
+      'dateFormat yyyy-mm-dd\n' +
+      'title Adding gantt diagram functionality to mermaid\n' +
+      'excludes weekdays 2019-02-01\n' +
+      'section ;Documentation';
+
+    const hash =
+      'gantt\n' +
+      'dateFormat yyyy-mm-dd\n' +
+      'title Adding gantt diagram functionality to mermaid\n' +
+      'excludes weekdays 2019-02-01\n' +
+      'section #Documentation';
+
     expect(parserFnConstructor(str)).not.toThrow();
+    expect(parserFnConstructor(semi)).not.toThrow();
+    expect(parserFnConstructor(hash)).not.toThrow();
   });
   it('should handle multiline section titles with different line breaks', function () {
     const str =
@@ -73,7 +93,23 @@ describe('when parsing a gantt diagram it', function () {
       'section Documentation\n' +
       'Design jison grammar:des1, 2014-01-01, 2014-01-04';
 
+    const semi =
+      'gantt\n' +
+      'dateFormat YYYY-MM-DD\n' +
+      'title Adding gantt diagram functionality to mermaid\n' +
+      'section Documentation\n' +
+      ';Design jison grammar:des1, 2014-01-01, 2014-01-04';
+
+    const hash =
+      'gantt\n' +
+      'dateFormat YYYY-MM-DD\n' +
+      'title Adding gantt diagram functionality to mermaid\n' +
+      'section Documentation\n' +
+      '#Design jison grammar:des1, 2014-01-01, 2014-01-04';
+
     expect(parserFnConstructor(str)).not.toThrow();
+    expect(parserFnConstructor(semi)).not.toThrow();
+    expect(parserFnConstructor(hash)).not.toThrow();
 
     const tasks = parser.yy.getTasks();
 
diff --git a/packages/mermaid/src/diagrams/sequence/svgDraw.js b/packages/mermaid/src/diagrams/sequence/svgDraw.js
index f81147c10..ef8ed6f00 100644
--- a/packages/mermaid/src/diagrams/sequence/svgDraw.js
+++ b/packages/mermaid/src/diagrams/sequence/svgDraw.js
@@ -10,22 +10,6 @@ export const drawRect = function (elem, rectData) {
   return svgDrawCommon.drawRect(elem, rectData);
 };
 
-const addPopupInteraction = (id, actorCnt) => {
-  addFunction(() => {
-    const arr = document.querySelectorAll(id);
-    // This will be the case when running in sandboxed mode
-    if (arr.length === 0) {
-      return;
-    }
-    arr[0].addEventListener('mouseover', function () {
-      popupMenuUpFunc('actor' + actorCnt + '_popup');
-    });
-    arr[0].addEventListener('mouseout', function () {
-      popupMenuDownFunc('actor' + actorCnt + '_popup');
-    });
-  });
-};
-
 export const drawPopup = function (elem, actor, minMenuWidth, textAttrs, forceMenus) {
   if (actor.links === undefined || actor.links === null || Object.keys(actor.links).length === 0) {
     return { height: 0, width: 0 };
@@ -44,7 +28,6 @@ export const drawPopup = function (elem, actor, minMenuWidth, textAttrs, forceMe
   g.attr('id', 'actor' + actorCnt + '_popup');
   g.attr('class', 'actorPopupMenu');
   g.attr('display', displayValue);
-  addPopupInteraction('#actor' + actorCnt + '_popup', actorCnt);
   var actorClass = '';
   if (rectData.class !== undefined) {
     actorClass = ' ' + rectData.class;
@@ -90,36 +73,14 @@ export const drawPopup = function (elem, actor, minMenuWidth, textAttrs, forceMe
   return { height: rectData.height + linkY, width: menuWidth };
 };
 
-export const popupMenu = function (popid) {
+const popupMenuToggle = function (popid) {
   return (
     "var pu = document.getElementById('" +
     popid +
-    "'); if (pu != null) { pu.style.display = 'block'; }"
+    "'); if (pu != null) { pu.style.display = pu.style.display == 'block' ? 'none' : 'block'; }"
   );
 };
 
-export const popdownMenu = function (popid) {
-  return (
-    "var pu = document.getElementById('" +
-    popid +
-    "'); if (pu != null) { pu.style.display = 'none'; }"
-  );
-};
-
-const popupMenuUpFunc = function (popupId) {
-  var pu = document.getElementById(popupId);
-  if (pu != null) {
-    pu.style.display = 'block';
-  }
-};
-
-const popupMenuDownFunc = function (popupId) {
-  var pu = document.getElementById(popupId);
-  if (pu != null) {
-    pu.style.display = 'none';
-  }
-};
-
 export const drawText = function (elem, textData) {
   let prevTextHeight = 0;
   let textHeight = 0;
@@ -329,6 +290,9 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) {
 
   if (!isFooter) {
     actorCnt++;
+    if (Object.keys(actor.links || {}).length && !conf.forceMenus) {
+      g.attr('onclick', popupMenuToggle(`actor${actorCnt}_popup`)).attr('cursor', 'pointer');
+    }
     g.append('line')
       .attr('id', 'actor' + actorCnt)
       .attr('x1', center)
@@ -345,7 +309,6 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) {
 
     if (actor.links != null) {
       g.attr('id', 'root-' + actorCnt);
-      addPopupInteraction('#root-' + actorCnt, actorCnt);
     }
   }
 
@@ -1053,8 +1016,6 @@ export default {
   insertClockIcon,
   getTextObj,
   getNoteRect,
-  popupMenu,
-  popdownMenu,
   fixLifeLineHeights,
   sanitizeUrl,
 };