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, };